perf(ui): stabilize snapshot slice identity so sidebar memos hit#34
Merged
Conversation
The sidebar ref-tree builds were already memoized, but refreshSnapshot replaced status/workTree/refs/submodules/meta with freshly deserialized objects on every refresh, so input identity churned on every stage toggle and watcher tick and every memo rebuilt for identical data. - lib/stable.ts: stable(prev, next) keeps the previous reference when the new value is structurally equal (jsonEqual, early-exit walk). Applied to every snapshot-fed slice and the standalone refreshStatus/ refreshRefs/refreshTree/refreshSubmodules setters; the per-tab meta patch also skips when the tab meta is unchanged. - Sidebar: drop the status dep from the Files-tab effect — since the repo_snapshot batch, every status-changing path already refreshes workTree from the same statuses walk, so the effect IPC-walked the working tree a second time on every stage toggle. It now runs only on tab show / repo switch (the lazy first load). - Sidebar: precompute folder leaf counts once per tree build (countLeaves fills TreeNode.leaves) instead of recursing per folder row on every render. Closes the last open item under TASKS - Performance - Audit follow-ups. Verified: tsc, vitest (198, +13 stable), vite build.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the last open item under TASKS → Performance → Audit follow-ups ("Sidebar: memoize ref-tree builds /
leafCount; debouncerefreshTreeoff thestatusdep").Root-caused one level down: the sidebar ref-tree builds were already
useMemo'd — they rebuilt anyway becauserefreshSnapshotreplacedstatus/workTree/refs/submodules/metawith freshly deserialized objects on every refresh, so input identity churned on every stage toggle and watcher tick.ui/src/lib/stable.ts—stable(prev, next)returns the previous reference when the new value is structurally equal (jsonEqual, early-exit walk so the something-changed case stays cheap). Applied to every snapshot-fed slice and the standalonerefreshStatus/refreshRefs/refreshTree/refreshSubmodulessetters; the per-tab meta patch also keepstabsidentity when the tab meta is unchanged. Unchanged slices keep their identity, so the sidebar ref trees, palette index, and FilesPierreTreestop rebuilding — and their zustand selector subscribers stop re-rendering — for identical data.statusdep is removed rather than debounced: since therepo_snapshotbatch, every status-changing path already refreshesworkTreefrom the same statuses walk, so the effect'srepo_treeIPC was a redundant second walk of the working tree on every stage toggle. It now runs only on tab show / repo switch (the lazy first load).countLeavesfillsTreeNode.leaves) instead of recursing per folder row on every render.Audited all consumers before stabilizing: nothing keys effects off these slices' identity as a "refresh tick" — every dependent is a pure memo/callback, so keep-if-equal is behavior-preserving.
Test plan
tsc --noEmit— cleanvitest run— 198 tests pass (+13 new forjsonEqual/stable: primitives, null vs undefined, arrays, nested objects, key-set mismatch, identity-keeping semantics)vite build— clean🤖 Generated with Claude Code