perf(app): memoize message bubbles + drop pin-toggle loading flash#130
Merged
perf(app): memoize message bubbles + drop pin-toggle loading flash#130
Conversation
Three small renderer-only wins on top of the library redesign: - MessageBubble wrapped in React.memo. SessionDetail now skips Map entries for messages with no matches, so non-matching messages keep a stable undefined findRanges/offset across keystrokes. activeMatchIndex is also passed only to the message that contains the active match. With 1k+ messages, find-in-page typing previously re-rendered every bubble per keystroke; now only the matching ones re-render. - LibraryLanding stops resetting recentSessions to null on the reloadKey refetch, so pin/unpin no longer flashes the whole feed to "Loading…". bucketByDate moves into useMemo so it doesn't recompute on every unrelated render. - ProjectView splits the "show loading" reset (identity / sort / source filter) from the silent reload triggered by pin toggles, removing the same flash there. - AgentSelector wrapped in React.memo. App re-renders on every sync progress event; without memo the agent dropdown's body re-ran on each one. No IPC, sync, or DB code touched. Pin/unpin still ends in a backend refetch — the optimistic state and the refetch result match, so the visual reconciliation is silent.
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.
What
Three small renderer-only perf wins that sit on top of the recent library-first redesign. No IPC, sync, or DB code touched.
MessageBubbleis wrapped inReact.memo.SessionDetail's find-in-page memo no longer creates a Map entry for messages with zero matches, and the active match index + active-match ref are passed only to the bubble that contains the active match.LibraryLandingno longer resetsrecentSessionstonullon the reload triggered by a pin toggle, andbucketByDatemoves intouseMemo.ProjectViewsplits the effect that resetssessionstonull(real reloads: identity / sort / source filter) from the silent reload triggered by a pin toggle.AgentSelectoris wrapped inReact.memo.Why
After the library redesign shipped (#122–#128), three perf hotspots were visible without needing 100k-session libraries to reproduce:
MessageBubbleon every keystroke, because themessageFindRangesmap produced a fresh[]ref for non-matching messages andactiveMatchIndex+onActiveMatchRefwere passed unchanged to all bubbles.LibraryLandingandProjectViewset their list state tonullinside the same effect that handled refetches, so the optimistic update was immediately erased and the user saw a flicker before the refetched data came back.AgentSelectorre-rendered on every sync progress event.Appre-renders on every progress chunk during a sync;AgentSelectoris mounted in the results header and the search overlay, and without memo its body ran each time.How it connects
-1activeMatchIndexand thebindActiveFindMatchcallback. Highlight color, scroll-to-active behavior, match counts, anddata-testid="session-find-active-match"selectors are unchanged.setRecentSessions(null)/setSessions(null)lines just makes the reconciliation silent. Real reloads (identity / sort / source-filter changes inProjectView) keep their loading state via a dedicated effect.AgentSelectormemo is a strict superset: agents/activeAgent/onSelect are all stable refs across App renders that don't actually change agent state, so the shallow compare holds.Test plan
pnpm exec tsc -p packages/app/tsconfig.json --noEmitcleanpnpm --filter @spool/app exec vitest run src/— 12/12 pass