OUT-3669 | Save button appears broken on Home app IU - #212
Conversation
The Save Changes button stayed visible after saving sidebar-only changes (e.g. Actions). Change detection compares the live settings against the saved baseline, and the post-save sync set the live `content` to the raw server value while setting the baseline to the editor's normalized (canonical) serialization. When stored content wasn't already canonical, the two diverged so `content` always read as dirty and the button never went away. It only surfaced for changes that don't touch the body (Actions), since editing the body normalizes the content first. Use the editor's normalized content for both the live value and the baseline in useSegmentSettings (the post-save sync) and useSettingsMutation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryFixes the "Save Changes" button not disappearing after saving sidebar-only settings (e.g. Actions) by ensuring the editor-normalized HTML is used for both the live value and the change-detection baseline wherever settings are synced from the server.
Confidence Score: 4/5Safe to merge; the core fix in The Both files are small and focused; Important Files Changed
Sequence DiagramsequenceDiagram
participant U as User
participant SM as useSettingsMutation
participant QC as QueryClient
participant SS as useSegmentSettings
participant E as TipTap Editor
participant ST as SettingsStore
U->>SM: Save Changes clicked
SM->>ST: setSettings(variables) [optimistic]
SM->>QC: PATCH /settings
QC-->>SM: onSuccess(data)
SM->>QC: setQueryData(raw data.data)
SM->>E: setContent(data.data.content)
SM->>E: getHTML()
E-->>SM: normalizedContent
SM->>ST: "setSettings({...data.data, content: normalizedContent})"
SM->>ST: "setInitialSettings({...data.data, content: normalizedContent})"
Note over QC,SS: setQueryData triggers useQuery re-render
SS->>E: setContent(data.content)
SS->>E: getHTML()
E-->>SS: normalizedContent
SS->>ST: "setSettings({...data, content: normalizedContent})"
SS->>ST: "setInitialSettings({...data, content: normalizedContent})"
Note over ST: live content == baseline content → button hidden
Reviews (1): Last reviewed commit: "fix(OUT-3669): keep Save button in sync ..." | Re-trigger Greptile |
| const normalizedContent = editor?.getHTML() ?? data.data.content | ||
| setSettings({ ...data.data, content: normalizedContent }) | ||
| setInitialSettings({ ...data.data, content: normalizedContent }) |
There was a problem hiding this comment.
Silent fallback to raw content when editor is unavailable
editor?.getHTML() evaluates to undefined if editor is null at the time onSuccess fires, so normalizedContent falls back to the raw server string (data.data.content). In that situation both setSettings and setInitialSettings are called with un-normalized content, which is the exact condition this PR is fixing. In practice the subsequent useSegmentSettings effect (triggered by setQueryData on the same line above) will heal the state, but the mutation path silently degrades and the flicker window remains. Using a ref for the editor — as useSegmentSettings already does with editorRef.current — would keep the reference fresh and make the fallback path unnecessary.
Changes
Root cause: Change detection (
useAppControls) compares the live settings against the saved baseline (initialSettings). After a save, the post-save sync (useSegmentSettings, which re-runs via the settings query) set the livecontentto the raw server value, but set the baselinecontentto the editor's normalized/canonical serialization (editor.getHTML()). When the stored body content wasn't already in canonical form, the two diverged, so thecontentcomparison always read as dirty and the button never disappeared.This only surfaced for changes that don't touch the body (Actions), because editing the body (e.g. inserting dynamic fields) normalizes the content first — which is exactly why dynamic-field changes worked but Actions didn't.
Fix: Use the editor's normalized content for both the live value and the baseline, in:
useSegmentSettings.ts— the post-save sync (the actual culprit).useSettingsMutation.ts— the save handler itself, for consistency / no flicker.Testing Criteria
Notes
Impact & Surface Area of Change
🤖 Generated with Claude Code