perf(session): gate session-write subscriber on relevant field changes#1720
Merged
Conversation
The App-level Zustand subscriber that debounces buildWorkspaceSessionPayload fires on every store update (agent status, usage refreshes, runtime title ticks, …). Each fire reset the 150ms timer, and when the timer eventually expired the rebuild crossed 70-110ms with many tabs open, tripping setTimeout violation warnings. Add a shallow reference-equality gate over the fields actually consumed by the payload so the timer only resets when those fields change. The field list is co-located with WorkspaceSessionSnapshot and locked to it via a compile-time exhaustiveness check, so adding a future snapshot field will fail to typecheck rather than silently skip the gate. Co-authored-by: Orca <help@stably.ai>
Extract the subscriber into createSessionWriteSubscriber so a vitest can drive the real Zustand store and assert which mutations cause a session write. The gate against unrelated updates (agent status, cache timers, runtime title ticks) is load-bearing for setTimeout violation budgets and the failure mode is silent — without this test, future store additions could re-introduce the regression unnoticed. Six cases lock in the contract: no write while not ready, exactly one write when ready flips, no write on unrelated mutations, exactly one write on a relevant mutation, coalescing within a debounce window, and cleanup cancels a pending timer. Co-authored-by: Orca <help@stably.ai>
…ounce Replace the closed-over `state` snapshot captured at timer-schedule time with `store.getState()` inside the setTimeout callback. Today this is behaviorally equivalent because `buildWorkspaceSessionPayload` reads only SESSION_RELEVANT_FIELDS (the same fields gating the timer reset), but a future refactor that adds a non-relevant field read to the payload builder would silently start emitting stale values without this guard. Also tighten the cleanup test: mutate the store after `cleanup()` and assert no persist, so a regression where the timer is cancelled but the listener is left subscribed would now fail rather than pass. Co-authored-by: Orca <help@stably.ai>
Resolve App.tsx import conflict (drop refs already removed on main, keep session-write subscriber wiring) and add the new lastVisitedAtByWorktreeId field to SESSION_RELEVANT_FIELDS — the compile-time exhaustiveness check correctly flagged that main had added it to WorkspaceSessionSnapshot. Co-authored-by: Orca <help@stably.ai>
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
buildWorkspaceSessionPayloadchanges — eliminates wasted timer resets and 70–110mssetTimeoutviolations triggered by unrelated store updates (agent status, usage refreshes, runtime title ticks) when many tabs are open.SESSION_RELEVANT_FIELDSwithWorkspaceSessionSnapshotand add a compile-time exhaustiveness check plus a runtime test backstop so a future field added to the snapshot can't silently bypass the gate.Test plan
pnpm test src/renderer/src/lib/workspace-session.test.ts src/renderer/src/lib/session-write-subscriber.test.ts— 12 cases pass (newSESSION_RELEVANT_FIELDScases + new subscriber gate cases + existing payload tests)pnpm typecheck— verifies the_exhaustivecompile-time checksetAgentStatus,setCacheTimerStartedAt) in 5s (6,351/sec) → 0 disk writes, 0 long tasks (>50ms) viaPerformanceObserver. The gate skips every fire.activeTabIdcorrectly persisted via thegetState()rebuild path.createTab/closeTab, switch active tab) still persist all 18 expected workspace-session keys toorca-data.json.JSON.stringify(fullbuildWorkspaceSessionPayloadwas 70–110ms in the regression).Made with Orca 🐋