Skip to content

fix(sidebar): make the repaint-skip opt-in, not the default - #10

Merged
sixvolts merged 2 commits into
mainfrom
ci/wiki-sidebar-flash
Sep 5, 2026
Merged

fix(sidebar): make the repaint-skip opt-in, not the default#10
sixvolts merged 2 commits into
mainfrom
ci/wiki-sidebar-flash

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

CI caught the previous two cuts of this guard swallowing legitimate updates:
sidebar-wiki.spec.ts failed on expanding a book, create/delete updating the
rail, reparent, and sort. The guard diffed only the category data, but the
things that change on those paths live OUTSIDE it — expansion state in
sidebarTreeExpanded, page trees in sidebarWikiPagesCache — so a real mutation
looked identical and got skipped.

Chasing every data source into the fingerprint is the wrong direction; I missed
two doing it. Invert instead: refreshSidebarChildren(force = true) repaints by
default, and only the autosave-driven sidebarRefresh listener passes force=false
to opt into the diff-and-skip. Every mutation caller (notesChanged clearing the
cache, caret toggle, drag, page events) keeps its bare call and always repaints.

So the flash fix survives — the one path that fires on a 500ms autosave still
skips a no-op repaint — while nothing else can be silently suppressed. Safe by
default: a new caller repaints unless it explicitly says not to.

esprima parses workspace.js. The e2e suite is the real check; it failed on the
last cut and is why this exists.


Opened automatically after the tiers check passed on 1795bcd2.

The check is attached to this branch head, so this is mergeable now.

Typing in a wiki page (or a chat, or a note) flashes the sidebar rail. The
earlier fix landed in notes.js; this is the same bug in the shared rail.

refreshSidebarChildren repaints every expanded category by calling
renderCategoryChildren, which opens with host.innerHTML = "" — a hard
teardown-and-rebuild, no diffing. And it fires on the sidebarRefresh /
notesChanged events that chat.js and notes.js dispatch off their own 500ms
autosaves. So an autosave in ANY panel tears down and repaints the wiki rows
too, even though the wiki data never changed — a visible flash on roughly every
debounce while typing.

The wiki save path itself was already clean: its debounced flushSave only
touches its own page-list row via renderPageList, and it dispatches
notesChanged only on structural events (create/delete/rename), never on body
keystrokes. The flash was the shared rail reacting to other panels.

Guard the repaint: fetchCategoryChildren returns plain data, so a structural
JSON compare against the cached copy is a sound proxy for "would render
identically". Update the cache always, repaint only on a difference. Mirrors
the notes.js fix.

The other renderCategoryChildren call site (category expand into a fresh
Loading… list) is a deliberate user action and correctly still repaints.

Verified: esprima parses app.js / workspace.js / wiki.js, with untouched app.js
as the control. Visual confirmation is the operator on desktop — the flash is a
repaint a DOM-shape test cannot see.
CI caught the previous two cuts of this guard swallowing legitimate updates:
sidebar-wiki.spec.ts failed on expanding a book, create/delete updating the
rail, reparent, and sort. The guard diffed only the category data, but the
things that change on those paths live OUTSIDE it — expansion state in
sidebarTreeExpanded, page trees in sidebarWikiPagesCache — so a real mutation
looked identical and got skipped.

Chasing every data source into the fingerprint is the wrong direction; I missed
two doing it. Invert instead: refreshSidebarChildren(force = true) repaints by
default, and only the autosave-driven sidebarRefresh listener passes force=false
to opt into the diff-and-skip. Every mutation caller (notesChanged clearing the
cache, caret toggle, drag, page events) keeps its bare call and always repaints.

So the flash fix survives — the one path that fires on a 500ms autosave still
skips a no-op repaint — while nothing else can be silently suppressed. Safe by
default: a new caller repaints unless it explicitly says not to.

esprima parses workspace.js. The e2e suite is the real check; it failed on the
last cut and is why this exists.
@sixvolts
sixvolts merged commit 015bc07 into main Sep 5, 2026
1 check passed
@sixvolts
sixvolts deleted the ci/wiki-sidebar-flash branch September 5, 2026 22:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant