From d7f8045e82af7ab73ae7c34888ef7cf9120afc7d Mon Sep 17 00:00:00 2001 From: Isaac Sanchez-Hawkins <266845420+isanchez404@users.noreply.github.com> Date: Fri, 8 May 2026 16:16:18 -0400 Subject: [PATCH] fix(files): stop navigation when save fails (#1168) Co-authored-by: Isaac Sanchez --- .../ui/src/components/views/FilesView.tsx | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/packages/ui/src/components/views/FilesView.tsx b/packages/ui/src/components/views/FilesView.tsx index 9cde855a..7e45f930 100644 --- a/packages/ui/src/components/views/FilesView.tsx +++ b/packages/ui/src/components/views/FilesView.tsx @@ -1312,37 +1312,37 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { const saveDraft = React.useCallback(async () => { if (!selectedFile || !files.writeFile) { toast.error(t('filesView.toast.savingNotSupported')); - return; + return false; } if (!isDirty) { - return; + return true; } setIsSaving(true); - await files.writeFile(selectedFile.path, draftContent) - .then((result) => { - if (!result?.success) { - toast.error(t('filesView.toast.writeFileFailed')); - return; - } - setFileContent(draftContent); - // Refresh stat after write so polling doesn't see a stale metadata change. - void readFileStat(selectedFile.path) - .then((stat) => { - if (stat) { - lastLoadedFileStatRef.current = stat; - } - }) - .catch(() => {}); - }) - .catch((error) => { - toast.error(error instanceof Error ? error.message : t('filesView.toast.saveFailed')); - }) - .finally(() => { - setIsSaving(false); - }); + try { + const result = await files.writeFile(selectedFile.path, draftContent); + if (!result?.success) { + toast.error(t('filesView.toast.writeFileFailed')); + return false; + } + setFileContent(draftContent); + // Refresh stat after write so polling doesn't see a stale metadata change. + void readFileStat(selectedFile.path) + .then((stat) => { + if (stat) { + lastLoadedFileStatRef.current = stat; + } + }) + .catch(() => {}); + return true; + } catch (error) { + toast.error(error instanceof Error ? error.message : t('filesView.toast.saveFailed')); + return false; + } finally { + setIsSaving(false); + } }, [draftContent, files, isDirty, readFileStat, selectedFile, t]); React.useEffect(() => { @@ -1401,7 +1401,8 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { } autoSaveTimerRef.current = setTimeout(() => { - void saveDraft().then(() => { + void saveDraft().then((saved) => { + if (!saved) return; setAutoSaveStatus('saved'); setTimeout(() => setAutoSaveStatus('idle'), 2000); }); @@ -1434,7 +1435,8 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { autoSaveTimerRef.current = null; } if (!isSaving) { - void saveDraft().then(() => { + void saveDraft().then((saved) => { + if (!saved) return; setAutoSaveStatus('saved'); setTimeout(() => setAutoSaveStatus('idle'), 2000); }); @@ -1723,6 +1725,12 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { const nextTab = pendingTabRef.current; const closePath = pendingClosePathRef.current; + const saved = await saveDraft(); + if (!saved) { + skipDirtyOnceRef.current = false; + return; + } + pendingSelectFileRef.current = null; pendingTabRef.current = null; pendingClosePathRef.current = null; @@ -1732,8 +1740,6 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { setConfirmDiscardOpen(false); - await saveDraft(); - if (closePath) { if (root) { removeOpenPath(root, closePath);