Skip to content

Commit 0a3e8b8

Browse files
majiayu000sawka
andauthored
fix: warn before discarding unsaved changes in waveconfig view (#3099)
Fixes #2890 ## Changes Add unsaved changes confirmation when navigating away in the Wave Config view. Two navigation paths lose edits without warning: 1. **File sidebar selection**: `handleFileSelect` calls `model.loadFile(file)` directly, which resets `hasEditedAtom` and overwrites `fileContentAtom` — discarding edits silently. 2. **JSON→Visual tab switching**: `setActiveTab()` is called directly. The visual component renders from saved config (not the editor buffer), so edits appear lost. ### Fix - Added `confirmDiscardChanges()` to `WaveConfigViewModel` — checks `hasEditedAtom`, shows `window.confirm()` if there are unsaved changes. - Guarded `handleFileSelect` with the confirmation check (skips if already on the selected file). - Guarded JSON→Visual tab switch with the confirmation check. Only prompts when `hasEditedAtom` is true. Visual→JSON direction is safe (editor loads from `fileContentAtom`). `window.confirm()` is synchronous and works well in Electron. ## Test Plan - Edit JSON, switch file in sidebar → confirmation dialog appears. Cancel stays, confirm switches. - Edit JSON, click Visual tab → confirmation dialog appears. - Save changes first, then switch → no dialog. - Switch without editing → no dialog. - `npx tsc --noEmit` passes. --------- Signed-off-by: majiayu000 <1835304752@qq.com> Co-authored-by: sawka <mike@commandline.dev>
1 parent 6a287e4 commit 0a3e8b8

File tree

2 files changed

+23
-1
lines changed

2 files changed

+23
-1
lines changed

frontend/app/view/waveconfig/waveconfig-model.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,21 @@ export class WaveConfigViewModel implements ViewModel {
263263
return globalStore.get(this.hasEditedAtom);
264264
}
265265

266+
confirmDiscardChanges(): boolean {
267+
if (!this.hasChanges()) {
268+
return true;
269+
}
270+
return window.confirm("You have unsaved changes. Discard and continue?");
271+
}
272+
273+
discardChanges() {
274+
const originalContent = globalStore.get(this.originalContentAtom);
275+
globalStore.set(this.fileContentAtom, originalContent);
276+
globalStore.set(this.hasEditedAtom, false);
277+
globalStore.set(this.validationErrorAtom, null);
278+
globalStore.set(this.errorMessageAtom, null);
279+
}
280+
266281
markAsEdited() {
267282
globalStore.set(this.hasEditedAtom, true);
268283
}

frontend/app/view/waveconfig/waveconfig.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const ConfigSidebar = memo(({ model }: ConfigSidebarProps) => {
2626
const configErrorFiles = useAtomValue(model.configErrorFilesAtom);
2727

2828
const handleFileSelect = (file: ConfigFile) => {
29+
if (selectedFile?.path === file.path) return;
30+
if (!model.confirmDiscardChanges()) return;
2931
model.loadFile(file);
3032
setIsMenuOpen(false);
3133
};
@@ -231,7 +233,11 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps<WaveConfigVi
231233
{selectedFile.visualComponent && selectedFile.hasJsonView && (
232234
<div className="flex gap-0 border-b border-border">
233235
<button
234-
onClick={() => setActiveTab("visual")}
236+
onClick={() => {
237+
if (!model.confirmDiscardChanges()) return;
238+
model.discardChanges();
239+
setActiveTab("visual");
240+
}}
235241
className={cn(
236242
"px-4 pt-1 pb-1.5 cursor-pointer transition-colors text-secondary",
237243
activeTab === "visual"
@@ -241,6 +247,7 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps<WaveConfigVi
241247
>
242248
Visual
243249
</button>
250+
{/* No guard needed: visual tab saves changes immediately via RPC */}
244251
<button
245252
onClick={() => setActiveTab("json")}
246253
className={cn(

0 commit comments

Comments
 (0)