From 729d0dadf1c1a7dfd9be55aad32b107526a3b687 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 1 Sep 2026 18:26:05 -0400 Subject: [PATCH] =?UTF-8?q?editor:=20collect=20the=20refund=20=E2=80=94=20?= =?UTF-8?q?the=20web=20editor=20autosaves=20on=20an=20idle=20pause?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #2971's engine work was already done and its benefit was never taken up here. Both engines coalesce revision snapshots to one per editing session — `src/thoughtsync/revisions.py::should_snapshot` and `store.rs`'s namesake, the server's applied on the PATCH path AND in `sync.py`, with four integration tests covering it. So a write has cost a write, not a write plus a revision, for some time. But this editor still wrote only on `close()`. That save-on-close existed BECAUSE writes were expensive; with the reason gone, all that was left was the cost — a tab closed mid-paragraph lost the paragraph, which is the one thing a notes app must not do. Android already debounces (`BoardViewModel`); the shared Vue editor did not, so web and desktop kept paying for a trade that had been cancelled. Now: a 1s idle pause writes. EDIT MODE ONLY, deliberately. In compose, `dismiss` discards a note that was never persisted so an accidental keystroke or a type-to-compose never litters the board. An autosave there would create the row and quietly take that behaviour away. Materialising a compose on first keystroke is a separate decision (#2967), not a side effect of this one. Three details that decide whether it is safe rather than merely present: * `flush` returns without writing while a save is in flight, so an autosave landing there would silently drop everything typed since that save began. It RE-ARMS instead of skipping. * Errors are swallowed and retried on the next pause. An autosave that interrupts typing with a message is worse than one that waits, and `close` still surfaces a real failure where the person is looking. * The timer is cancelled by `close`, by `dismiss` and on unmount, so nothing fires through a component during its leave animation or after it is gone. Checked and found harmless rather than assumed: `notes.reconcile` replaces the store's item but never touches `useNoteEditor`'s `editing` ref, so the `watch(() => props.note)` that calls `setBody` does not fire on a save. Were that not true, autosaving would have reset the field and the caret every second. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01K3MMqUtzX1TJgA1oypvm1c --- frontend/src/components/NoteEditor.vue | 64 +++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/NoteEditor.vue b/frontend/src/components/NoteEditor.vue index 9dfd421..835ab93 100644 --- a/frontend/src/components/NoteEditor.vue +++ b/frontend/src/components/NoteEditor.vue @@ -1,5 +1,5 @@