fix(desktop): preserve agent timer state across workspace switches#1679
Merged
Conversation
resetWorkspaceState() wiped the activeAgentTurnsStore on every workspace switch, losing the startedAt timestamps that anchor the elapsed-time badges. Switching A→B→A re-anchored timers to "just now" via resurrectTurn instead of restoring the original start. Add saveActiveAgentTurnsForWorkspace / restoreActiveAgentTurnsForWorkspace to snapshot and recover all four module maps (activeTurnsByAgent, clockOffsetByAgent, lastProcessed, terminalAtByAgent) around the reset. useWorkspaceInit saves the outgoing workspace before resetWorkspaceState() and restores the incoming workspace after applyWorkspace() resolves. lastActivityAt is refreshed on restore so turns saved 25+ s ago are not immediately pruned before new liveness events arrive. Tombstones are preserved so completed turns cannot be resurrected by a stale liveness frame arriving after the round-trip. The watermark is preserved so already-processed events are not replayed. The snapshot is consumed on first use; empty saves (no turns, no tombstones) discard prior snapshots. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Two fixes from Thufir review pass 1: - useWorkspaceInit: null out prevWorkspaceIdRef.current immediately after saving the outgoing workspace's snapshot. Previously the ref stayed set to the outgoing workspace ID across a cancelled/failed incoming switch; on the next re-fire the save path would see the now-empty store and call the empty-store branch, deleting the saved snapshot. Nulling after save prevents the stale-ref overwrite. - activeAgentTurnsStore: collapse restoreActiveAgentTurnsForWorkspace signature onto one line to satisfy Biome formatter (fixes Desktop Core CI failure). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Address five code-review findings: - Add clearSavedWorkspaceSnapshot() and wire it into removeWorkspace() alongside the existing relay-specific GC calls so snapshots for permanently-deleted workspaces don't sit in memory indefinitely. - Make restoreActiveAgentTurnsForWorkspace self-contained by clearing all four module maps before writing from the snapshot. Callers no longer need to pre-clear as a precondition; the function is now safe against the narrow async window between resetWorkspaceState() and the restore. - Add doc comment to resetActiveAgentTurnsStore clarifying that savedByWorkspace is intentionally excluded — snapshots must survive the reset that runs between save and restore. - Add no-notification assertion to the no-op restore test so a future unconditional notifyListeners() regression is caught immediately. - Refine the prevWorkspaceIdRef null-out comment to describe the actual rapid-switch (A→B→C before B resolves) scenario rather than the less-precise cancelled/failed framing. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This was referenced Jul 10, 2026
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.
Problem
Agent activity timer badges in the sidebar reset to zero when switching workspaces and switching back.
resetWorkspaceState()inuseWorkspaceInit.tsclears theactiveAgentTurnsStoreon every workspace switch, including thestartedAttimestamps that anchor elapsed-time display. On switching back, turns are re-created byresurrectTurn()using the liveness event's timestamp as the newstartedAt, so the timer counts from the moment of reconnection rather than the original turn start.Fix
Add
saveActiveAgentTurnsForWorkspace/restoreActiveAgentTurnsForWorkspace/clearSavedWorkspaceSnapshottoactiveAgentTurnsStore.tsthat snapshot and recover the four module-level maps:activeTurnsByAgent— turn records with originalstartedAttimestampsclockOffsetByAgent— calibrated per-agent clock-skew estimateslastProcessed— per-agent observer event watermarksterminalAtByAgent— terminal tombstones for completed turnsuseWorkspaceInit.tssaves the outgoing workspace state beforeresetWorkspaceState()and restores the incoming workspace state afterapplyWorkspace()resolves.useWorkspaces.tsxcallsclearSavedWorkspaceSnapshotinremoveWorkspacealongside the existing relay-specific GC.Edge cases
turn_completedevent arrives on reconnect →endTurn()removes the restored turnturn_startedarrives →startTurn()creates it normallylastActivityAtrefresh gives a 25 s grace window, then prune clears itprevWorkspaceIdRefnulled immediately after save → C's effect can't re-save the empty store and delete A's snapshotclearSavedWorkspaceSnapshotcalled inremoveWorkspace→ no memory leak for deleted workspacesrestorecalled with non-empty mapsTests
9 new tests in the
workspace-switch save / restoresuite (2266 total, 0 failing):anchorAt(the core bug regression)clearSavedWorkspaceSnapshotmakes restore a no-oppnpm test: 2266 pass, 0 fail.just desktop-checkandjust desktop-buildclean.