Repository navigation
feat(workspaces): settle quiet workspaces onto a per-project shelf - #663
Merged
Merged
Conversation
danyaberezun
requested review from
Olga Lavrichenko (OLavrik),
Rustam Sadykov (SBOne-Kenobi) and
Rinat S (rsolmano)
as code owners
October 6, 2026 15:47
danyaberezun
added this pull request to stack #664
October 6, 2026 16:03
danyaberezun
marked this pull request as draft
October 6, 2026 20:30
danyaberezun
marked this pull request as ready for review
October 6, 2026 20:30
danyaberezun
force-pushed
the
workspace-archive-research
branch
from
October 8, 2026 06:55
4f29eb8 to
16e29f4
Compare
danyaberezun
marked this pull request as draft
October 8, 2026 07:39
danyaberezun
marked this pull request as ready for review
October 8, 2026 07:39
danyaberezun
force-pushed
the
workspace-archive-research
branch
2 times, most recently
from
October 8, 2026 07:42
16e29f4 to
b4c46b6
Compare
danyaberezun
force-pushed
the
workspace-archive-research
branch
from
October 8, 2026 07:59
b4c46b6 to
5f57262
Compare
5 tasks done
Rinat S (rsolmano)
previously requested changes
Oct 8, 2026
Rinat S (rsolmano)
left a comment
Collaborator
There was a problem hiding this comment.
Thanks! The shelf idea and the "facts on the host, one selector decides" design look good to me. Requesting changes for a few product points and for the bulk removal. Details are inline:
- Bulk "Remove all settled…": this is the main one. The current shape can freeze the host and isn't crash-safe. I suggest splitting it into "mark now, clean up in the background" (inline on
removeSettledWorkspaces). - Default idle window of 3 days: too aggressive as a default; please use 7 (inline on
SETTLE_IDLE_DAYS). - First launch after upgrade: the backlog moves to the shelf silently, all at once; please make that visible (inline on
backfillActivity). - README: this adds a user-visible capability, but
README.mdis unchanged. Per AGENTS.md (a change that adds a user-visible capability reviews README claims), please add a line about the Settled shelf, and refresh the rail screenshot in.github/readme-assets/if it no longer matches. - (optional)
terminal.writehot path: inline onrecordWorkspaceActivity.
…efresh gaps, and re-activation
…HEAD baselines at watch start The bulk Remove used to issue N blind workspace.remove calls from the client's snapshot; it is now one workspace.removeSettled whose host side refuses a row that a session is busy in, that the user pinned live since, or whose activity stamp moved, and tears the rest down one worktree at a time. Watchers now take a HEAD baseline the moment they start, so the first commit after a host restart counts as activity instead of merely seeding the observed sha.
…close the review-fact gaps The host guard behind workspace.removeSettled now also compares the review state the client judged a row settled from, so a PR opened between preview and confirm keeps the row. Around the same facts: an activity stamp on a row that a merged/closed review currently settles is never coalesced away (it is the one fact that says "worked on since the merge"); review snapshots are fenced to the branch the lookup was made for and dropped when folder truth moves an external worktree's branch; the never-pushed fallback of the removal preview counts commits no remote-tracking ref reaches instead of the Changes panel's compare base; a history jump into another workspace judges the destination through the shared activation patch instead of inheriting the previous row's live latch; and workspace.openReview answers a client older than the settled protocol with null for a merged or closed review, since such a client reads any review as an open PR.
danyaberezun
force-pushed
the
workspace-archive-research
branch
from
October 8, 2026 16:15
5f57262 to
935502d
Compare
Collaborator
Author
|
Thanks for the review, Rinat S (@rsolmano). All five points are addressed; the branch is also rebased onto
One more change: branch-review's remote detection is now asynchronous ( |
4 of 5 tasks
3 of 5 tasks
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
The per-project workspace list only grows. Every worktree ever created stays in the rail in creation order until it is removed, and Remove is too destructive for the common case — it deletes chats, terminals and the worktree. On a daily-use machine that is dozens of rows (48 on the author's), most of them finished or dormant, with the live handful buried among them. There was no way to say "I'm done with this, keep it" and no way for the rail to notice that a workspace's PR merged two weeks ago.
A survey of how agentic IDEs handle the same list (Conductor, Vibe Kanban, T3 Code, Cursor, the Codex app, Claude Code desktop, Zed, Mux, Superset) plus the classic references (GitHub Desktop, Arc, Linear, Codespaces) showed two failure modes to avoid: hiding rows somewhere unreachable (Codex's Settings → Archived, Claude Code desktop's auto-archive with no shelf — both with bug reports to match) and leaving all the housekeeping to the user. The design chosen from a mockup gallery is T3 Code's: rows settle themselves on signals, into a visible shelf, with manual overrides.
Approach
A Settled shelf per project, derived from host-owned facts by one client selector.
WorkspacegainslastActiveAt,settledOverride/settledAtand a host-keptreviewsnapshot; which rows are live and which sit on the shelf is a client selector over those plus session state. No "settled" boolean can disagree with the rules.lastActiveAt(coalesced to one write per minute); selecting or viewing a workspace does not, so opening a settled workspace keeps it on the shelf — force-revealed and announced asWORKSPACE · settled— until you Keep active or actually work in it. Real activity also clears either override, so a pin or a park never goes stale.branch-reviewasks for an open review first (one call, as before) and pages the newest merged/closed rows only when there is none — a combined page is ordered by creation and could bury an older open PR under newer closed ones — and surfaces areliablebit; the host refreshes snapshots on list, focus and a 5-minute timer — live rows every pass, idle-settled rows at most every 30 minutes so a PR opened for a dormant branch from outside still brings it back — and only writes a reliable answer, so aghoutage never erases a known merged state. Its remote-host detection (git config/git remote) is asynchronous, so that pass never blocks the host (the synchronous reads cost ~45 ms of frozen event loop per row). The topbar PULL REQUEST chip reads the same fact (Merged / Closed tones) and falls back to the snapshot when its own lookup has none. Becauseworkspace.openReviewnow also returns merged/closed reviews, the plan pane's PR actions key offisOpenBranchReview— a merged PR no longer reads as "PR exists → push updates".git worktree removeper row (~4 s of frozen host for a worktree with a 70k-filenode_modules) and drops the record before an in-memory cleanup chain, so a bulk version would freeze the host for minutes and orphan worktrees on a mid-way stop. Make workspace removal non-blocking and crash-safe, then bring back bulk removal for the Settled shelf #688 tracks the non-blocking, crash-safe two-phase removal (mark now, async cleanup, resume on restart) that bulk removal will sit on..gitgitfile mtime), so an existing backlog settles itself on first launch; open-PR rows stay live. Because that empties most of a long rail at once, the first automatic move is announced once per browser — Moved N quiet workspaces to Settled, with the rules and a Show action that opens those shelves. The count waits for the shelf to stop changing for two seconds (the backfill lands as a burst of pushes), and rows you parked by hand never trigger it.worktreeRow(page, name)), including the two added to feat(web): redesign the topbar location strip as captioned segments #659 after this branch first rebased onto it.Changes
contracts(v78):Workspace.lastActiveAt / settledOverride / settledAt / review,OpenBranchReview.state / changedAt,workspace.settle / unsettle,AppConfig.settleIdleDays(+SETTLE_IDLE_DAYSwith default 7,isSettleIdleDays),WORKSPACE_SETTLE_PROTOCOL_VERSION.server/branch-review: all-state lookup,findBranchReviewOutcome { value, reliable }replacesfindOpenBranchReview(value-only form removed;prtests migrated); remote-host detection runs throughgitAsyncunder the lookup timeout.server/workspaces:recordWorkspaceActivity,backfillWorkspaceActivity,recordWorkspaceHead(sha-based commit proxy — the git-dir watcher also fires on index refreshes),settleWorkspace/unsettleWorkspace(Default refused),setWorkspaceReview(branch-fenced); creation stampslastActiveAt.server/host: newsettledLifecycle.ts(session-state stamp,reviewRefreshRows, per-project backfill + review refresh pass, 5-minute timer), handlers for the two methods,terminal.writestamps the workspacewriteTerminalreturns, repo-metadata callback callsrecordWorkspaceHead,pr.openstamps activity and persists the created PR against the branch it pushed;settings/persistencevalidatesettleIdleDays.web/store:selectWorkspaceSettledReason/selectWorkspacePartition(state, projectId, now)/selectAutoSettledCounts+settledReasonLabel/Title;workspaceSort(persisted bypanels/workspaceSort.ts),settledShelfExpanded/Shown,activeWorkspaceLiveLatch(set inworkspaceActivationPatch, whichactivateWorkspaceFromRoutenow reuses; re-armed by a push that advances the active row'slastActiveAt).web/panels:ProjectTreesort row, live rows with hover ✓ Settle,Settled · Nshelf with reason chips, hover ↩ Keep active, 10-row paging, force-reveal, 30-seconduseNowtick, the one-time first-move notice (settledShelfNotice.ts);WorkspacesSettingssection;workspaceActionsgainscanSettleWorkspace/settleWorkspace/unsettleWorkspace;lib.compactAge.web/shell/locationBar: Settle / Keep active in the WORKSPACE menu,· settledcaption fragment, Switch to = live siblings in rail order +Settled · N ›submenu, PR chip tones (reviewTone/reviewChipLabel), Remote row only for open reviews, snapshot fallback inLocationBar.README.md: the Settled shelf in What you get;workbench-{light,dark}.pngrecaptured on the current UI (captioned topbar from feat(web): redesign the topbar location strip as captioned segments #659, sort row, shelf).settled-shelf.spec.ts(manual overrides, topbar mirror, force-reveal, the first-move notice once per browser, pre-v78 host, idle-window setting);worktreeRow(page, name)fixture;history-jump,terminals,workspace-tabs,new-workspace.live,topbar-locationmoved off positional row lookups.Review follow-ups
Rounds one to five (automated findings plus independent re-reviews of the whole diff) hardened the lifecycle facts — idle-settled rows back in the review refresh on a 30-minute pace;
.gitmtime backfill for external worktrees; the plan pane's PR stage ignoring merged/closed reviews; the live latch carried through re-activation and history jumps; HEAD baselines seeded at watch start; open reviews asked for before settled pages; a stamp on a review-settled row never coalesced away; review snapshots branch-fenced and dropped on every branch transition; coalesced activity keeping a high-water mark; incomplete settled-review timestamps failing live-safe; pre-v78 clients answered withnullfor merged/closed reviews; andpr.openpersisting the created PR against the exact branch it pushed. Those rounds also hardened a guarded bulk removal, which has since moved out (below).Sixth round (Rinat S (@rsolmano)'s review):
git worktree removeper row, and the record is dropped before an in-memory cleanup chain, so a stop mid-way orphans worktrees and transcripts. The dialog, both methods, the host guard and its guard-only busy probes are removed from this PR; the issue carries the two-phase design, and the removed code is kept onsettled-shelf-bulk-remove.branch-reviewwas synchronous and now runs every five minutes for every candidate row; it is asynchronous now.terminal.writedisk-read nit — reading and parsing the real 26 KB / 62-rowworkspaces.jsoncosts ~42 µs per input chunk, and the suggested in-memory flag would add a second owner of override/review state that every writer must keep in sync. Answered on the thread.Screenshots
1280×820, light theme. The rail and menu pairs use the seeded project from the earlier rounds (six worktrees: one fresh, one merged PR, one closed PR, one open-but-stale PR, one parked by hand, one idle 20 days); before =
origin/redesign-top-panel-breadcrumb(since merged tomainas #659), after = this branch. The new-surface shots use a disposable clone with five worktrees, three of them backdated.Rail + topbar — before: every worktree in one flat creation-ordered list; no PR state for a merged branch.
Rail + topbar — after: sort row, live rows,
Settled · 4shelf with reason chips (BY YOU·MERGED·CLOSED·IDLE 2W); the opened merged workspace stays on the shelf, the caption readsWORKSPACE · SETTLED, the chip readsMerged #618.WORKSPACE menu — before / after: Settle ↔ Keep active beside Reveal; Switch to shows live siblings then a
Settled · 3 ›submenu with reasons.New surfaces (after only): the one-time first-move notice and what Show opens (staged by backdating three rows), and Settings → Workspaces with the week default.
The README's
workbench-{light,dark}.pngare in the diff: the same composition as before (rail · chat with its tool steps ·args.tsdiff · Specs + Changes) on the current UI, staged on a disposable clone in the isolated e2e host with a seeded transcript rather than a live model run.Related issues
None — design request from the author; the brainstorm, survey and mockups live in the workspace's gitignored
.thinkrail/context/mockups/workspace-archive/. Follow-up: #688 (non-blocking, crash-safe removal, then bulk removal for the shelf).Checklist
bun run lint,bun run typecheck,bun run test— final tree lint clean (1412 files); typecheck green for all 19 packages excluding@thinkrail/desktop, whoseelectrobun preparewaits on the running app's build lock on this machine (no desktop sources change here);bun run test20/20 tasks (server 1399 pass / 2 skip, web 1598, contracts 69). Alsocheck:deps,check:boundaries,check:seams,check:spec-surfaceand the typography/colors/spacing/provider-glyphs checks OK.bun run e2e, orbun run e2e:fullwhen touching agent behavior) — final tree (935502da): all 8 shards / 494 tests passed. The run before it had one failure —activity-breadcrumbs"oldest-first disclosure expansion preserves … anchors", a 245 px scroll-anchor deviation under 8-lane load — which passed 5/5 in isolation and in the full rerun; this PR touches no chat or scroll code. The newsettled-shelfjourneys passed in focused runs at every step. No agent execution semantics changed.SPEC.md/ top-level specs updated to reflect any boundary, contract, or behavior change