From 79a552f6201787a0d29f751cd62b09fe32412f3d Mon Sep 17 00:00:00 2001 From: Fabio Montefuscolo Date: Mon, 3 Aug 2026 22:12:03 +0200 Subject: [PATCH] feat: debounced caret-only saves without broadcast --- .../sidepanel-ux-improvements/tasks.md | 2 +- sidepanel/sidepanel.js | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/openspec/changes/sidepanel-ux-improvements/tasks.md b/openspec/changes/sidepanel-ux-improvements/tasks.md index 6c08189..f590c5d 100644 --- a/openspec/changes/sidepanel-ux-improvements/tasks.md +++ b/openspec/changes/sidepanel-ux-improvements/tasks.md @@ -8,7 +8,7 @@ ## 2. Caret persistence in the side panel - [x] 2.1 In `sidepanel/sidepanel.js`, pass current caret (`selectionStart`/`selectionEnd`) and `scrollTop` to `saveNote` in the existing autosave (`doSave`) -- [ ] 2.2 Track caret-only movement via `document` `selectionchange` (filtered to the editor) and editor `scroll`; debounce ~800ms into a caret-only save that calls `saveNote` without broadcasting; skip it while a content autosave is pending +- [x] 2.2 Track caret-only movement via `document` `selectionchange` (filtered to the editor) and editor `scroll`; debounce ~800ms into a caret-only save that calls `saveNote` without broadcasting; skip it while a content autosave is pending - [ ] 2.3 Flush any pending caret-only save on editor `blur` and on `visibilitychange` → hidden ## 3. Focus and caret/scroll restore diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 74e346a..4c51206 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -221,6 +221,53 @@ editor.addEventListener('blur', () => { } }); +// --------------------------------------------------------------------------- +// Caret-only persistence +// +// Caret moves (selectionchange / scroll) without content edits are saved on +// a debounce. These saves never broadcast: a remote caret update must not +// make other panels reload and lose their own caret. +// --------------------------------------------------------------------------- +let caretTimer = null; +const CARET_DEBOUNCE_MS = 800; + +function scheduleCaretSave() { + if (!currentKey || isLoadingContent) return; + // A pending content autosave will capture the caret anyway. + if (saveTimer) return; + if (caretTimer) clearTimeout(caretTimer); + caretTimer = setTimeout(doCaretSave, CARET_DEBOUNCE_MS); +} + +async function doCaretSave() { + caretTimer = null; + if (!currentKey || currentTabId == null) return; + // A content save got scheduled meanwhile — it captures the caret. + if (saveTimer) return; + // A remote content change is waiting for blur; our content is stale and + // must not be written back over it. + if (deferredReload) return; + + try { + const saved = await saveNote(currentKey, editor.value, {}, { + caretStart: editor.selectionStart, + caretEnd: editor.selectionEnd, + scrollTop: editor.scrollTop, + }); + currentNote = saved; + } catch (err) { + console.error('Side Notes: caret save failed', err); + } +} + +document.addEventListener('selectionchange', () => { + if (document.activeElement === editor) { + scheduleCaretSave(); + } +}); + +editor.addEventListener('scroll', scheduleCaretSave); + // --------------------------------------------------------------------------- // In-panel Find // ---------------------------------------------------------------------------