fix(workstation): keep hot tabs warm and restore rebuilt tab state - #1302
Merged
Conversation
The code-editor host mounts only the active tab, so leaving a tab and coming back rebuilt it from scratch. Review lost All Changes expansion and scroll, dropped Focus-mode diff edits while keeping the unsaved dot, and re-suspended its sidebar into a loading placeholder; search mode and the issue sub-tab reset the same way. Add a declarative tab retention policy (tabRetention.ts) with bounded pools: Review and the Project Manager list trio stay mounted-but-hidden for a grace window, driven through useRetainedTabPool by the editor host, SidebarSlot, and the Project Manager router. The sidebar registry's separate keepAlive flag is removed. Gate the Source Control worktree loader on "surface mounted" rather than "active" so the sidebar no longer paints a scope-loading overlay on return. For rebuilt tabs (and retained tabs past their grace), add a session-only per-tab view-state store (tabViewState.ts + useTabViewState), a DiffSectionList snapshot (expansion, Virtuoso scroll state, handled focus nonce), and a base-guarded diff-edit draft store (gitDiffEditDrafts.ts), all released from the close mutations. Fix two latent bugs: the list's collapse-signal effect cleared overrides on mount, and useAllChangesFiles mounted empty and pruned restored state before rows existed.
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
Switching away from a WorkStation tab and back reset it. The code-editor host mounts only the active tab, so every switch unmounted the tab's subtree and rebuilt it on return. The Review (Source Control) tab was the worst case: its keep-alive overlay from issue #16 was removed in 52e778c (
perf(source-control): lazy load diffs and release memory) without moving any of its view state into a store, so every return collapsed all files in All Changes, scrolled to the top, and re-suspended the Source Control sidebar into a loading placeholder. Focus-mode edits in the diff editor were silently discarded on switch while the tab kept showing its unsaved dot. Search mode/filters and the issue-detail sub-tab reset the same way. The sidebar kept flashing even once the pane was preserved because the worktree loader under it was gated on "Review is active", so leaving cleared the worktree list and returning refetched it, which flipped the scope identity and painted the full-pane loading overlay.There was also no single place that said which tabs are preserved: the Project Manager list trio had its own keep-alive window, and the sidebar registry carried a separate monotonic
keepAliveflag.Solution
One declarative retention policy, with reconstruct-from-store as the fallback for everything it does not cover.
src/store/workstation/tabs/tabRetention.tsmaps tab type to a bounded retention pool (grace window + warm cap). Review is in a single-slot pool; the Project Manager trio shares a two-slot pool. Everything else keeps the rebuild default. Widening or narrowing the preserved set is a table edit.useRetainedTabPoolturns a pool plus the pane's tabs into the ids that may stay mounted. The code editor computes it once and passes it to both the main pane (Review overlay plus a generic keyed hidden layer for any retained registry type) andSidebarSlot, so pane and sidebar hide/show the same instances in lockstep. Hidden layers use opacity/visibility, notdisplay:none, so scroll offsets survive. The sidebar registry'skeepAlivedescriptor flag is removed; the Project Manager router reads the trio pool from the same table.useSourceControlSetupnow follows "Review surface mounted" (active or retained) instead of "active", which removes the sidebar overlay flash.tabViewState.ts, LRU-bounded, released from all four close mutations) with auseTabViewStatehook.DiffSectionListsnapshots expansion overrides, the Virtuoso scroll state, and the handled focus nonce, and rebuilds from them;AllChangesViewpersists that per tab. Search mode/filters and the issue sub-tab use the hook.gitDiffEditDrafts.ts, keyed by file path with a base-content guard so a stale draft never restores over content that changed on disk. Save, discard, and closing agit-difftab drop the draft.useAllChangesFilesstarted empty and filled from an effect, which flashed the "no changes" placeholder and pruned restored state before rows existed.Potential risks
isActive. Only Review and the PM trio are listed; four registry renderers publish a workstation header unconditionally and must not be added to the table until guarded (documented in the policy file).keepAlivefield was removed fromTabSidebarDescriptor; the only other user is the archived Benchmark sidebar under.archive/, which is tsconfig-excluded.git-difftab discards, matching prior behaviour.Verification
Commit was built with a temporary index from a live checkout, so the pre-commit hook did not run and there is no
Pre-commit hook ran.trailer. Run manually on the 34 changed files:pnpm typecheck:fast(tsgo): clean for this change. It reports one pre-existing error inCanvasPreviewSurface.tsxfrom unrelated uncommitted work in the same checkout.eslint --max-warnings 0 --report-unused-disable-directives <files>: clean.oxlint -c .oxlintrc.json --max-warnings 0 <files>: clean.prettier --check <files>: clean.pnpm run check:test-placement: consistent across 499 directories.pnpm run check:circular: no cycle introduced; one pre-existing cycleutil/modelGrouping.ts <-> util/modelVariants.tsfrom fix(models): keep distinct codex models out of effort-variant families #1266 is unrelated.vitest runoversrc/store/workstation,src/modules/WorkStation,src/hooks/tabHost,src/modules/ProjectManager/ProjectManagerLayout: 164 files, 1146 tests passed, including 7 new test files (retention policy, pool hook with fake timers, view-state store, hook remount in jsdom, close-mutation cleanup, diff drafts, list snapshot helpers).pnpm teston the live checkout: 1512 of 1514 files passed, 11360 of 11363 tests. The 3 failures are inCanvasInlineCard/canvasBuilder.test.tsandstaticHtmlCanvas.test.ts, whose sources carry unrelated uncommitted edits in the same checkout; none of those files are in this PR.🤖 Generated with Claude Code