From 9d1c898b84c8ffd3858b113bf3b9a155a02b942a Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Tue, 24 Mar 2026 14:48:35 +0100 Subject: [PATCH] fix: address review feedback on auto-save - Snapshot fields at start of performSave to avoid race with user edits during async save (fields could change between await and lastSaved update) - Only update lastSaved tracking on successful save, so failed saves are retried on the next debounce cycle - Stop restoring title after Untitled fallback in autoSave(), which was re-triggering the auto-save effect - Capture note count before opening editor in empty-note e2e test Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/components/NoteEditor.svelte | 31 +++++++++++++++++++--------- tests/e2e/auto-save.spec.ts | 12 +++++------ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/lib/components/NoteEditor.svelte b/src/lib/components/NoteEditor.svelte index 4720a28..ba81099 100644 --- a/src/lib/components/NoteEditor.svelte +++ b/src/lib/components/NoteEditor.svelte @@ -83,24 +83,37 @@ async function performSave() { if (isSaving) return; - if (!title.trim() && !content.trim()) return; - if (!hasUnsavedChanges()) return; + // Snapshot current fields to avoid races with user edits during await + const snap = { title, content, color, checklistMode }; + if (!snap.title.trim() && !snap.content.trim()) return; + if ( + snap.title === lastSavedTitle && + snap.content === lastSavedContent && + snap.color === lastSavedColor && + snap.checklistMode === lastSavedChecklistMode + ) return; isSaving = true; try { if (currentlyNew) { - const created = await createNote({ title, content, color, checklistMode }); + const created = await createNote(snap); if (created) { noteId = created.id; currentlyNew = false; + lastSavedTitle = snap.title; + lastSavedContent = snap.content; + lastSavedColor = snap.color; + lastSavedChecklistMode = snap.checklistMode; } } else if (noteId) { - await updateNote(noteId, { title, content, color, checklistMode }); + const updated = await updateNote(noteId, snap); + if (updated) { + lastSavedTitle = snap.title; + lastSavedContent = snap.content; + lastSavedColor = snap.color; + lastSavedChecklistMode = snap.checklistMode; + } } - lastSavedTitle = title; - lastSavedContent = content; - lastSavedColor = color; - lastSavedChecklistMode = checklistMode; } finally { isSaving = false; } @@ -335,10 +348,8 @@ clearTimeout(autoSaveTimer); if (savingPromise) await savingPromise; // For feature-triggered saves (image upload, share), use 'Untitled' fallback - const prevTitle = title; if (!title.trim()) title = 'Untitled'; await performSave(); - if (!prevTitle.trim()) title = prevTitle; return noteId; } diff --git a/tests/e2e/auto-save.spec.ts b/tests/e2e/auto-save.spec.ts index 4b46646..399cbd1 100644 --- a/tests/e2e/auto-save.spec.ts +++ b/tests/e2e/auto-save.spec.ts @@ -41,20 +41,20 @@ test.describe('Auto-save', () => { }); test('Scenario: Empty note is not auto-saved', async ({ authenticatedPage: page }) => { + // Given the current number of notes + const noteCards = page.locator('[data-testid="note-card"]'); + const countBefore = await noteCards.count(); + // When the user opens a new note but leaves it empty await page.getByTestId('new-note-btn').click(); // And waits longer than the auto-save delay await page.waitForTimeout(3000); - // Then no note card appears in the list - const noteCards = page.locator('[data-testid="note-card"]'); - const countBefore = await noteCards.count(); - - // Close the empty editor + // And closes the empty editor await page.getByTestId('close-editor-btn').click(); - // And no new note was created + // Then no new note was created const countAfter = await noteCards.count(); expect(countAfter).toBe(countBefore); });