perf(app): dedup sync-time status fetches + consolidate search refresh#131
Merged
perf(app): dedup sync-time status fetches + consolidate search refresh#131
Conversation
Reduce the IPC traffic + redundant React work that fires on every sync phase transition. - App owns status now and passes it down. Sidebar accepts a status prop and SidebarStatus reads it instead of running its own getStatus, which used to fire 4 times per sync (once per phase change). - App's getStatus / getRuntimeInfo effect was keyed on the whole syncStatus object, so it also re-ran on every phase change. runtimeInfo is now mount-only (it never changes mid-session); status re-fetches only when the sync phase reaches done. - Sidebar's listProjectGroups effect is now keyed on status.totalSessions, so the project_groups_v aggregation runs once on mount, then once after each sync that actually changed the session count, instead of 4 times per sync. - onSyncProgress and onNewSessions now share a single 250ms debounced search refresh helper. Previously onNewSessions called doSearch immediately with no debounce, so a sync that emitted many new-sessions events back-to-back could pile up overlapping FTS queries on top of the debounced progress refreshes. Trade-off: project counts in the sidebar no longer "climb" mid-sync; they jump at done. The intermediate counts were aggregated views of partial state and the cost (one project_groups_v aggregation per phase boundary) outweighed the value. If we want progressive updates back later, the right place is a totalSessions-bucketed throttle, not raw phase events.
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
Reduce the IPC traffic + React work triggered on every sync phase transition. Renderer + IPC orchestration only; no DB, sync engine, or main-process logic touched.
statusand passes it down toSidebar.SidebarStatusreads it from props instead of running its owngetStatus.getStatus/getRuntimeInfoeffect was keyed on the wholesyncStatusobject.runtimeInfois now mount-only;statusis re-fetched only when the sync phase reachesdone.Sidebar'slistProjectGroupseffect is now keyed onstatus.totalSessions, so theproject_groups_vaggregation runs once on mount and once per sync that actually grows the session count.onSyncProgressandonNewSessionsnow share a single 250ms debounced search-refresh helper.Why
After the library redesign shipped (#122–#128), every sync phase change (
scanning→syncing→indexing→done) triggered three independent IPC paths in the renderer:useEffect([syncStatus])rangetStatus+getRuntimeInfo.Sidebar'suseEffect([syncStatus?.phase])ranlistProjectGroups— an aggregation view over the sessions table.SidebarStatus's ownuseEffect([syncStatus])rangetStatusagain.That's roughly 12 IPC round-trips per sync just to refresh status + project counts, of which two were duplicate
getStatuscalls and four were fullproject_groups_vaggregations. Separately,onNewSessionsfireddoSearchdirectly with no debounce, racing the debouncedonSyncProgressrefresh and stacking overlapping FTS queries when many session files landed at once.How it connects
SidebarStatuswas already renderinggetSyncStatusText(syncStatus, status)— it just neededstatusfrom props instead of its own state. The rendered text ("X sessions · 5m"/"Scanning…"/"Indexing N/M"/"Building index…") is unchanged.listProjectGroupsonstatus.totalSessionsmatches the actual signal: project rows can change only when the underlying session count changes, andstatusis now updated exactly when sync transitions todone. On mount,statusstartsnullso the aggregation still runs once for the initial population.scheduleSearchRefreshhelper preserves the existing 250ms trailing-debounce semantics forsyncing/indexingprogress, and now extends them toonNewSessions. The immediatedoSearch(query)onphase === 'done'is preserved so the user always sees the post-sync result without an extra 250ms wait.Trade-off
Project counts in the sidebar no longer climb mid-sync; they jump at
done. The intermediate counts came from aggregating partial state, and the cost (oneproject_groups_vaggregation per phase boundary) outweighed the value of the animation. If progressive updates are wanted later, atotalSessions-bucketed throttle is the right shape — phase-keyed refetches were just the wrong signal.Test plan
pnpm exec tsc -p packages/app/tsconfig.json --noEmitcleanpnpm --filter @spool/app exec vitest run src/— 12/12 pass'}N sessions · …${''}window.spool.syncNow()— sidebar status text walks through Scanning → Indexing N/M → Building index → final count, and project rows refresh once at done