Files
supabase/apps/studio/components/interfaces/SQLEditor/SqlEditorManualSaveNoticeDialog.tsx
Charis 0791b04eb8 feat(sql-editor): roll out manual saving by default (#48706)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Feature — progresses the SQL Editor manual saving rollout, and removes a
fully rolled out feature flag.

## What is the current behavior?

Manual saving (snippets save only on an explicit Save / Cmd+S rather
than autosaving every edit) is opt-in. It requires both the
`sqlEditorManualSave` ConfigCat flag and the user turning on the
"Disable snippet auto-saving" feature preview themselves. That flag is
now fully rolled out.

## What is the new behavior?

- `sqlEditorManualSave` is removed, so the feature preview is listed for
everyone. (Delete the flag in ConfigCat after a few months.)
- New `sqlEditorManualSaveForced` flag progresses the rollout. It forces
manual saving on regardless of what the user stored previously,
including an explicit opt-out, via a new `isForced` field on
`FeaturePreview` that `initializeFlags` resolves ahead of the
localStorage lookup — so the feature preview modal reflects the forced
state too, not just the save behavior. Turning the flag off reverts
everyone who never opted in, so it remains a working kill switch.
- Users the rollout switches over get a one-time dialog on their first
SQL Editor route, explaining that snippets no longer autosave. Dismissal
persists in `sql-editor-manual-save-notice-dismissed` (allowlisted, so
it survives sign-out).
- Users who opted into the preview themselves never see the dialog — it
records their dismissal up front instead, since the notice needs to
outlive the feature preview and once the preview is retired there's no
stored opt-in left to recognize them by.
- The preview keeps its switch so users who lose their local storage can
opt in early, but once the rollout reaches them the "Disable feature"
button is disabled with a tooltip explaining why.

### To test

Turn on `sqlEditorManualSaveForced` on via the dev toolbar.

- No `supabase-ui-sql-editor-manual-save` and no dismissal key → dialog
appears on `/project/<ref>/sql`, toolbar shows the Save button. Dismiss,
reload → no dialog.
- `supabase-ui-sql-editor-manual-save` set to `false` (previously opted
out) → still forced onto manual saving, and still gets the dialog.
- `supabase-ui-sql-editor-manual-save` set to `true` → no dialog, and
the dismissal key is written automatically.
- Flag off, no opt-in → autosave, no dialog, and the "Disable autosave"
power-off button still opens the preview modal.
- Preview modal while forced → "Disable feature" is disabled with a
tooltip; another preview (e.g. Column-level privileges) still disables
normally.

## Additional context
2026-08-04 12:16:18 -04:00

86 lines
3.0 KiB
TypeScript

import { LOCAL_STORAGE_KEYS, safeLocalStorage, useFlag } from 'common'
import { useEffect } from 'react'
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
KeyboardShortcut,
} from 'ui'
import { shouldShowManualSaveNotice } from './SqlEditorManualSaveNoticeDialog.utils'
import { useFeaturePreviewContext } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { IS_PLATFORM } from '@/lib/constants'
/**
* One-time notice for users the manual saving rollout switches over, shown the
* first time they land on a SQL Editor route. Users who opted into the preview
* themselves already know the behavior and never see it.
*/
export const SqlEditorManualSaveNoticeDialog = () => {
const { isInitialized } = useFeaturePreviewContext()
const isForced = useFlag('sqlEditorManualSaveForced')
const [isNoticeDismissed, setIsNoticeDismissed, { isSuccess: isDismissalLoaded }] =
useLocalStorageQuery(LOCAL_STORAGE_KEYS.SQL_EDITOR_MANUAL_SAVE_NOTICE_DISMISSED, false)
const hasOptedIntoPreview =
safeLocalStorage.getItem(LOCAL_STORAGE_KEYS.UI_PREVIEW_SQL_EDITOR_MANUAL_SAVE) === 'true'
// Record the dismissal up front for users who opted in themselves. The notice
// outlives the feature preview — once the preview is retired there's no stored
// opt-in left to recognize them by, and without this they'd be shown a notice
// about behavior they chose.
useEffect(() => {
if (isDismissalLoaded && hasOptedIntoPreview && !isNoticeDismissed) {
setIsNoticeDismissed(true)
}
}, [isDismissalLoaded, hasOptedIntoPreview, isNoticeDismissed, setIsNoticeDismissed])
const isOpen =
isDismissalLoaded &&
shouldShowManualSaveNotice({
isPlatform: IS_PLATFORM,
isFeaturePreviewInitialized: isInitialized,
isForced,
hasOptedIntoPreview,
isNoticeDismissed,
})
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && setIsNoticeDismissed(true)}>
<DialogContent size="medium">
<DialogHeader>
<DialogTitle>Snippets no longer save automatically</DialogTitle>
<DialogDescription>
The SQL Editor now saves your snippets only when you save them.
</DialogDescription>
</DialogHeader>
<DialogSectionSeparator />
<DialogSection className="text-sm">
<ul className="list-disc pl-6 text-foreground-light space-y-1">
<li>
Save with the Save button next to Run, or with{' '}
<KeyboardShortcut keys={['Meta', 's']} />
</li>
<li>Tabs with unsaved edits show a dot</li>
<li>Closing a tab with unsaved edits discards them</li>
</ul>
</DialogSection>
<DialogFooter>
<Button onClick={() => setIsNoticeDismissed(true)}>Understood</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}