Skip to content

perf(session): gate session-write subscriber on relevant field changes#1720

Merged
brennanb2025 merged 4 commits into
mainfrom
brennanb2025/session-write-debounce
May 12, 2026
Merged

perf(session): gate session-write subscriber on relevant field changes#1720
brennanb2025 merged 4 commits into
mainfrom
brennanb2025/session-write-debounce

Conversation

@brennanb2025
Copy link
Copy Markdown
Contributor

@brennanb2025 brennanb2025 commented May 12, 2026

Summary

  • Gate the App-level session-write debounce subscriber so it only resets the timer when a field that actually feeds buildWorkspaceSessionPayload changes — eliminates wasted timer resets and 70–110ms setTimeout violations triggered by unrelated store updates (agent status, usage refreshes, runtime title ticks) when many tabs are open.
  • Co-locate SESSION_RELEVANT_FIELDS with WorkspaceSessionSnapshot and 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 (new SESSION_RELEVANT_FIELDS cases + new subscriber gate cases + existing payload tests)
  • pnpm typecheck — verifies the _exhaustive compile-time check
  • End-to-end in dev Electron build, heavy session loaded (114 worktrees / 122 tabs / 122 terminal layouts):
    • 31,800 irrelevant store mutations (setAgentStatus, setCacheTimerStartedAt) in 5s (6,351/sec) → 0 disk writes, 0 long tasks (>50ms) via PerformanceObserver. The gate skips every fire.
    • Mixed load: 12,850 irrelevant + 257 relevant mutations in 3s → final activeTabId correctly persisted via the getState() rebuild path.
    • Real user flows (worktree click, createTab/closeTab, switch active tab) still persist all 18 expected workspace-session keys to orca-data.json.
    • Cost avoided per skipped fire on this profile: 614 KB payload, ~1.64ms just to JSON.stringify (full buildWorkspaceSessionPayload was 70–110ms in the regression).
    • 0 renderer console errors throughout.

Made with Orca 🐋

brennanb2025 and others added 4 commits May 12, 2026 12:44
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>
@brennanb2025 brennanb2025 merged commit bf1e925 into main May 12, 2026
2 checks passed
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