Unified stage layout: one workspace of lanes, rows and a session pool - #1013
Merged
Merged
Conversation
Records the design that merges Grid and Dispatch into one workspace: pool-first sessions, a stage of ragged lane rows, projects as labels, one focus truth, mode-free vocabulary, first-run onboarding, and a registry-driven starter card. Plan is the first commit per convention; implementation follows its stage order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… migration (#992) Adds the stage-over-fleet data model as additive, dormant types (ProjectRef, SessionMeta.projectId, PersistedWorkspace stage triple) and the pure read-time migration that produces it: projects minted from tabs, detached+buried folded into one pool, dispatchMode.tiled promoted to the stage, unowned rows dropped by the existing v2 ownership rule. Multi-pane tab arrangements are pooled, not reconstructed — the accepted loss recorded in the plan, pinned by a test asserting leaves land unplaced rather than by omission. Fixture is recorded from the owner's live workspace (redacted): 12 lanes / 2 rows, one-pane tabs, lane sessions owned via detached records, extension-view and terminal pool citizens, and a real focusedSessionId-points-at-a-detached-session quirk. No behavior change: nothing reads the migration yet. Stage 2 wires the read path. 18 new unit tests; dispatch/ownership/rehydrate suites unaffected (207 passed). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…te (#992) workspaceStage.ts derives the stage (stored grid, or the #977 seeded default — derived, so it can never go stale or desync from disk), projects, active project, and per-session pool affinity as SELECTORS over the still-stored v2 state, sharing one precedence with the persisted migration via exported helpers (resolveEntrySeed, projectAffinityOf, defaultSeededStage). No dual-write: stage 3 inverts the derivation when v3 becomes stored. Reference stability of the derived default stage is pinned by test — downstream lane memos churn without it. New UI code must consume these selectors, never dispatchMode?.tiled or tab trees; that is what makes stage 3 a deletion instead of a rewrite. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The stage-1 commit accidentally included npm's rewrite of the lockfile (512 lines of optional non-macOS esbuild platform entries dropped). Lockfile changes belong to deliberate dependency PRs, not worktree installs. Restored byte-for-byte from the branch base. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es deleted (#992) MainSurface renders the lane stage unconditionally; the tile-tree and TileTabs render branches and classic Dispatch (sidebar + single view) are deleted rather than reconciled. TiledDispatchLayout reads through stageOfWorkspace so a pre-bootstrap paint derives the seeded default instead of crashing on a null grid. Keyboard follows: dispatch context is always live, cmd-N always fills the focused lane, alt-arrows always move lane selection/focus; the classic single-focus helpers die with the classic layout. Bootstrap guarantees a STORED stage exactly once: fresh installs seed [1] (one row, one lane — plan §4.5), imported v2 workspaces without a grid seed [2] (migration default), and an existing grid is never reseeded. The "Default Workspace Mode" setting is now unread (removed with its UI in cleanup). Integration coverage at the two seams that can silently regress: - unifiedStage.integration.test.ts — real rehydrate over the recorded owner fixture: 12-lane stage boots intact, lanes resolve from the index, grid-only users boot onto the seeded default with leaves pooled (project-scope transition pinned), fork-bomb spawn guard holds. - useBootstrap.stage.renderer.test.tsx — the seeding guarantee itself. - TerminalDimensionOwnership rewritten for lanes: panes mount through MountedAgentTerminalOwner, retention contracts unchanged. Also restores package-lock.json (npm-install prune in stage 1's commit) and documents the node_modules corruption bisect that masqueraded as a store hang: npm ci is the worktree rule. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eted (#992) CommandSurface loses 'grid' and 'dispatch' and gains 'workspace'; the registry's mode gate is gone (every surviving surface reaches its own when). This is the palette half of "no mode names anywhere a user can see": commands are visible because a target exists, never because of which layout is active. Deleted with their modes/tree (recorded in RETIRED_COMMAND_IDS, bindings released — release notes must say so): - dispatch-mode (⌘⇧M), global-dispatch (⌘⇧G) — nothing to toggle or scope - normalize-layout, hard-normalize-layout, rotate-layout — split ratios no longer render - nav-left/right/up/down (⌥H/J/K/L + ⌥Arrows) — gestures now belong to the lane stage, inline in useKeybinds until stage 5 registers them tiled-dispatch retitled 'Stage Shape…' (id and ⌘D kept — bindings and visibility overrides must not orphan). 13 dispatch-surface commands flipped to 'workspace'. Characterization suites updated: catalog count 130→121 with the retirement ledger extended, navigation group six→two, mode-gate admission/availability tests deleted with the gate they pinned, keybinding baseline drops the four nav alias entries. 309 command tests + 1,056 workspace/app tests green; check:keybindings OK. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… membership (#992) Autosave now derives projects/activeProjectId/stage by running the SAME migration that reads the file back, so the v3 half of workspace.json is by construction what the next launch derives from the v2 half sitting beside it — the two halves cannot disagree. Session rows also stamp projectId via the shared affinity precedence: the v2 sources of that affinity (tab leaves, detached records) die in stage 3, so the field must be durable the day the sources go. Stage 2 complete: the app renders one stage, palette speaks one surface, boot guarantees a stored grid, and disk carries the v3 shape. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…h/detach and split resizing (#992) Deletes every layout feature nothing renders since the stage became the only workspace, without changing the stored state shape (stage 3b inverts the authority and deletes the tree itself). Deleted: the Tile Tabs feature with its store slot, persisted field and the setTileTabs setter threaded through eight action hooks; Bury / Revive / Kill Buried (commands, prompt, two palette modes, the activity-modal action); the grid attach/detach pair; the placement step of New Agent (now a kind picker); split resize and Focus Pane actions with their key handlers; geometry.ts, newAgentPlacement.ts and the recursive TileTree component (the file keeps renderWorkspaceLeaf); the tree and Tile Tabs branches of agent-index navigation. Control plane: placement.list / attach / detach / inspect, agents.bury, agents.restore and layout.adjust are gone, 'tiled-tabs' left the observation mode enum, and descriptions speak of lanes and the pool. Buried sessions from old files fold into the pool at every read boundary (foldBuriedIntoDetached, applied by rehydrate and window adoption). With the revive UI gone a record left in `buried` would be alive, owned and unreachable. A buried session whose project is gone re-parents to the active project, because v2 kept buried sessions unconditionally. Three keyboard reservations with no surviving owner were released. The macOS Option+Shift+Arrow record stays in useKeybinds as a comment, since that is the only place the fact is written down. Command catalog 121 -> 114, retirements recorded in RETIRED_COMMAND_IDS. Also repairs a latent failure from the surface merge: the workspace feature reference still named the deleted dispatch-mode and global-dispatch commands. Tests: tree-only cases deleted with the code they pinned; navigation, runtime isolation, MCP continuity and takeover tests re-based onto lanes; new unit and boot-level integration coverage for the buried fold. tsc -b, check:keybindings and test:contract are clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…3b-i) The `dispatchMode` envelope is gone from live state. `WorkspaceState.stage` is the lane grid and is always present, so nothing is entered or exited, no reader branches on "is Dispatch on?", and the things the envelope carried besides the grid are deleted rather than renamed: - the layout-wide `scope: 'project' | 'global'`. Every index lists every project; a row's `projectTabIds` is the only filter. Scope had to die here and not in stage 4: stage 2c deleted the command that switched it, so a file saved with `scope: 'project'` would have left every other project's agents alive, owned and unlisted with no way back. - the classic single-selection `focusedSessionId`. The focused lane's occupant is the one focus truth (U3). The close-successor picker (#261), the takeover mirrors in Spotlight/Reader and the undo refocus went with it. - enterDispatchMode, exitDispatchMode, setDispatchScope, focusDispatchSession, enterTiledDispatch, exitTiledDispatch; the enter/exit/scope actions of `dispatch.configure` (rejected by the schema, not silently accepted); the New Lane entry path (#978); the dispatchModeEnabled/globalDispatchEnabled palette flags. Boot no longer knows lanes exist. The store starts on `freshStage()`, `newTab` places a new project's first agent in the focused lane only when that lane is empty, and rehydrate publishes `migrateWorkspaceToStage(persisted).stage` in its first commit, so no state without a stage can be rendered or autosaved. useBootstrap lost two parameters and its `ensureStage` helper. The #977 entry seed now runs exactly once, in the migration. Autosave writes the in-memory stage verbatim instead of deriving the v3 half at save time; `dispatchMode` survives only as `LegacyDispatchMode` on `PersistedWorkspace`, read by the migration and never written. Published control shapes are kept for stage 7: `layout.read` still returns `dispatch: { focusedSessionId, tiled }` (minus `scope` and `classicFocusedSessionId`) and `app.observe` still reports `mode: 'tiled-dispatch'`. Tests: fixtures that said "the user is commanding X" through a tab's tree focus now use a one-lane stage (`workspace/testing/stageFixtures.ts`). The recorded v2 workspace is lifted in code by `workspace/testing/recordedDispatchWorkspace.ts` rather than edited, as a field move that never normalizes, so gridPersistence still sees the legacy `ratios` array. entryContinuity.renderer.test.tsx is deleted with the action it tested; its seed rules moved to workspaceShape.test.ts. New: newTabPlacement.renderer.test.tsx mounts the real hook. Also fixes naming.test.ts, which still listed commands deleted in stages 2c and 3a. Known gap, commented at the function and closed in stage 4: the other spawn paths still overwrite an occupied focused lane. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…on its row Stage 3b-ii of the unified stage layout (#992). The last v2 owner structures are gone from live state: `Tab` is `{ id, title }`, and `TileNode`, `tile-tree/treeOps.ts`, `detachedSessions`, `buried`, `gridRelatedSelections` and the `RATIO_*` constants are deleted. The v2 shapes survive in exactly one module, `workspace/legacyWorkspaceV2.ts`, read by the migration and by nothing that runs after boot. Ownership moved onto the row: - `SessionMeta.projectId` files a session under a project. - `SessionMeta.joinedAt` orders it there. The migration seeds it so every old list keeps its order (tile leaves get ordinals in tree order, detached rows keep `detachedAt`, buried panes keep `buriedAt`); new sessions get `Date.now()`. A session is OWNED iff its `projectId` names a project that exists. Lanes, pins and the active project are pointers, never ownership. `workspace/pool.ts` is the one place a session enters or leaves a project; a project is removed by the commit that takes its last session and is never force-removed while it holds one. Behavior changes, all recorded in plan section 9.1: - Boot spawns the FOCUSED lane's occupant only. This reverses the plan's "lane occupants spawn": the recorded owner workspace showed v2 spawning 3 tile leaves nobody looked at while all 12 working lanes woke on first use. Agents wake on first send (#691), terminals when their leaf mounts. - Wake decisions read the runtime (`processStatus === 'started'`), not "is it a detached record". Closes the gap where a leaf with a dead backend was placed un-woken, and stops re-waking agents that were already up. Reload-all restarts sessions whose runtime is not idle. - Every close is session-scoped. The three-way "Close the agent or the tab?" dialog, row promotion, `agentOnly`, `requestRootCloseConfirmation` and the 'agent' answer are deleted. Close Tab closes deepest-linked-first and a partial close leaves the project holding its survivors. - Undo entries carry rows (`ClosedSession`, `ClosedTab.sessions`), so a restored session returns to its old position. - Merge Project Tabs appends the moved block; window adoption takes the closed window's pool, not its stage, and loads history only for sessions main still holds a live backend for. - Autosave writes v3 only. An older build opening the file fails its shape check and lands in persisted-fallback with autosave locked, so a downgrade cannot overwrite a workspace it does not understand. Published control shapes are kept with the smallest honest change (placement kind 'project' added, v2 kinds unproduced, `ManagedAgent.placement` always 'dispatch'); narrowing them is stage 7. Fix found by the new main-process projection test: the analytics tile-tree walker's cycle guard was a depth cap only, and a split walks two children, so a self-referencing node was a 2^64-call tree. It now tracks visited nodes and keeps the cap for the call stack. Tests: new `pool.test.ts`, `legacyWorkspaceV2.test.ts` (including the recorded 82-ghost pool) and `workspaceProjection.test.ts`; suites whose premise was the tile tree are re-based with a comment naming what they used to pin. Casts hid 48 runtime failures after the source compiled; fixtures touched here use `satisfies WorkspaceState` where they can. Verified: `npx tsc -b` clean; full vitest sweep 521 files / 3713 tests passed, and `pool.test.ts` (14 tests, written while the sweep ran) passed on its own; check:keybindings, test:contract and the fixture checks pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…commands Stage 4 of the unified stage layout (#992): spawn and close semantics. Context-places spawn (§4.3, the operator's chosen rule): an EMPTY focused lane is filled by a spawn from it — the one continuity write U2 allows — and an occupied lane is never displaced. The fill/refuse decision lives inside the commit updater (applyDispatchSpawnFocus now takes the whole state and returns it by reference when refused), so a lane freed during the awaited spawn is fillable and one filled since is not; a refused spawn moves nothing on screen, not even the focus cursor. createLinkedAgent lost its lane capture (a lane showing the parent is occupied by definition); linked and orchestration children are pool-only. "Index badges it": a pooled spawn marks SessionRuntime.pooledSpawnAt, the index row renders a `new` chip from it, and placing the session into any lane retires it inside setTiledLaneSession — the one write every placement gesture funnels through. On the runtime, not workspace state: presentation, not truth — autosave never writes it, undo never restores it, and a per-row selector re-renders one row. Retired by placement, never by time. Clear Lane (§4.4): clearTiledLane empties the focused lane's occupant back into the pool alive; the clear-focused-lane command badges the occupant's title and admits only on a live occupant. No undo entry — undoing a clear is selecting the session back into the lane it never left. ⌥⌫ forced the runtime half of the macOS text-editing reservation: Clear Lane ships on Option+Backspace, and macOS owns that chord as delete-word in every text field. MACOS_TEXT_EDITING_CHORDS now feeds the static reservation entry AND is enforced at routing (routedCommandForEvent refuses those chords while a text field owns the target), with the pairing recorded in APPROVED_OVERLAPS. The table's old admission that it "does not stop the inline dispatch grammar from consuming Alt+Shift+Arrow in a composer" is finally false in the right direction. Create commands stopped lying: splitFocused and openExtensionViewInPane lost their inert direction arguments; titles dropped grid directions ("Split Pane Right" → "New Claude", "New Terminal Right" → "New Terminal", "New Codex Right" → "New Codex"); ids and chords are frozen (§5.4) and the "-horizontal" twins are palette-hidden but still runnable for their ⌥⇧ chords. agents.create and the control guide now describe the actual placement behavior. The related-agent strip is deleted, not fed: a pane-local second selector duplicates what every per-row index already does (children nest under their parents) and cuts against U2. gridRelatedAgents.ts, the prop chain through TileLeaf/AgentTerminalLeaf/PaneHeader, the phone's relatedAgentTabs call shape, and the #858 identity chrome are gone; PaneHeader's phone-stub safety case survives. Tests: contextPlacesSpawn (badge lifecycle), clearLane (action + command + admission), the ⌥⌫ yield cases in focusModeKeyboardOwnership, catalog baseline at 115, and re-based placement suites (dispatchTerminalPlacement, controlPlacement, extensionPlacement, mcpDomainContinuity, sessionCommands). Verified: npx tsc -b clean; full vitest sweep 3732/3736 with the four stragglers fixed (sessionCommands) or confirmed as a concurrent-run flake (store.test.ts passes clean in isolation); check:keybindings OK (39 sets, 14 reserved, 8 approved overlaps); test:contract satisfied. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Stage 5 of the unified stage layout (#992): the keyboard registry migration (#681 §7.1's filed debt). The four inline lane arrows in useKeybinds are registered commands now: dispatch-select-previous/next-agent (⌥↑/⌥↓, aliases ⌥K/⌥J) walk the focused lane's selection through its row's index; dispatch-focus-lane-left/right (⌥←/⌥→, aliases ⌥H/⌥L) move lane focus within the row. The movers live in workspace/dispatch/laneKeyboard.ts and selection writes through selectTiledLaneSession so a hibernated agent wakes before it is placed (#690). They are rebindable, visible in the shortcuts surface, and subject to collision checking like every other chord owner. The migration fixes Alt+Shift+Arrow as a side effect of correctness: the inline branch tested `alt && !cmd` without a shift check, so the shifted chords ran the index walk while the user was selecting text by word — the gap the reservation table's header admitted it could not prevent. The binding grammar is exact-match, so shifted chords match nothing and stay native; a keyboard test pins both halves. The 'Dispatch row and lane selection' reservation is retired — the commands own those chords in the defaults table, and the reservation would have reported each chord as doubly owned by its own command. 'grid' is deleted as a binding context along with activeBindingContexts' dispatchMode flag: the stage is the workspace, so the layout context is live whenever the global editor does not own the target, and the overlap matrix keeps only the dispatch/editor pair. The shortcuts surface labels the context "Workspace only" — no user-facing copy names Dispatch as a mode. The ⌘1–9 / two-digit row grammar stays inline deliberately: a contextual interaction with continuation state, not a command. Tests: routing end-to-end cases in focusModeKeyboardOwnership (bare arrows invoke the commands; shifted arrows invoke nothing), re-based context-matrix suites (bindingContexts, reservations, routerWiring, resolve), catalog baseline at 119. Verified: npx tsc -b clean; check:keybindings OK (43 sets, 13 reserved, 8 approved overlaps); command-palette, command-keybindings, settings and tile-tree suites green (43 files / 315 tests). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s and empty lanes Stage 6 of the unified stage layout (#992): the Neovim-style starter card (§4.6), the "8 most common keybinds" bundle the operator asked for. StarterHintCard renders at the two "now what?" moments: Context A in TileLeaf for a fresh agent (no user turn in its committed entries — derived, never stored, so the card vanishes the moment the first prompt lands and restored sessions never show one), and Context B extending the focused empty lane's hint with the four placement-flavored slots. Terminal lanes never get the card — AgentTerminalLeaf does not mount it, and the visibility predicate refuses non-agent kinds. The card is registry-driven, always: every slot is a command id resolved through the catalog for its title and through resolveEffectiveKeybindings — the same resolution the router performs — for its chord. A rebound command shows the user's chord; unbound commands render title-only; the Fill Lane slot resolves through a new reservedInteractionBindings accessor because the ⌘1–9 digit grammar owns chords without being a command. ⌘N now binds New Agent… (the platform convention, unclaimed by any command, reservation, or Electron role; New Window stays ⌘⇧N) — the card's second slot pointed at a chordless command, and a starter card that teaches no key teaches nothing. Tests: StarterHintCard.renderer.test.tsx pins the registry-driven contract (default chords, user-override chords shown, title-only unbound slots, exact slot lists per variant) and the freshness predicate. Verified: npx tsc -b clean; check:keybindings OK (44 sets); dispatch, tile-tree and workspace UI suites green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ocabulary from docs Stage 7 of the unified stage layout (#992): SDK/contract schema changes and documentation. The placement enum narrows to project/dispatch/reader/spotlight — the v2 ownership kinds (grid, related, detached, buried) had been held one release after nothing produced them, and this is the promised removal. Old clients parsing new observations are unaffected; new clients still switching on the removed values fail at compile time. ManagedAgentPlacement narrows to 'dispatch' for the same reason. The observation `mode` field becomes z.literal('tiled-dispatch') and is deprecated in the schema description: kept one release so clients reading it still parse, leaving with the next schema version because "which layout" is no longer a question the app can ask. ARCHITECTURE §6.2.2 is rewritten around the stage/pool model: the workspace-model diagram shows lanes referencing sessions by ID and sessions naming projects by field; the prose states the v3 ownership rule, project lifetime, and context-places. §5.4's renderer-state label updated. Both SVGs regenerated with the pinned tooling (16 UNTOUCHED diagrams fail --check in this environment — committed SVGs came from a different Chrome; the two changed diagrams render and verify). The control guide's layout entry is retitled "The workspace: lanes, rows and the agent index" and rewritten without mode vocabulary; dispatch.configure's title says "stage rows and lanes"; the README no longer says "grid, Dispatch, and buried agent". Verified: npx tsc -b clean; control, agentManagementMcp and mcp suites green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…abulary from command copy Stage 8 of the unified stage layout (#992): cleanup. defaultWorkspaceMode is deleted end to end — the Settings row, the type and default, the persistence coercion (a stale persisted value is dropped on read), the useBootstrap/useWorkspace params that had been deliberately unread since stage 3b-i, and their tests. There is one layout; a fresh install has nothing to choose. The user-facing "Dispatch" audit swept command titles and descriptions: "Dispatch row" became session, "the Dispatch list" became the index, mode-conditioned sentences were replaced with the unconditional behavior (pool plus new badge), and Merge Project Tabs' notes no longer describe buried panes becoming Dispatch agents. Command ids keep their dispatch- prefixes by design: ids are not user-facing and renaming them orphans keybindings. The retired-command-ids release note is carried by the PR description (the repo has no CHANGELOG file; the catalog ledger points at release notes). Verified: npx tsc -b clean; full vitest sweep 524 files / 3741 tests, zero failures; check:keybindings OK (44 sets, 13 reserved, 8 approved overlaps); test:contract satisfied; worktree-live-fixture, conversation-fixture and live-resume-probe checks pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Bring #992's unified stage up to current main (117 commits). Five conflicts, plus one semantic conflict the type checker caught: - globalCapabilities: keep #992's placement vocabulary and add main's 'grok' provider. - featureReference: drop BOTH side's additions. #992 added system-perf, which main deleted as superseded (24c3604); main added tile-tabs, which #992 deletes. - remote-client SessionView: take main's v2 phone header plus Reader. #992's only change there was dropping `relatedAgentTabs`, which main's rewrite no longer passes. - performance/ui/PerformancePanel: deleted, as main superseded it with the Performance Monitor. #992's edit to it only removed a tileTabs read. - catalog.test: counts re-derived from the real merged catalog, 123 = #992's 119 + Goal Loop's 2 (#1001) + the 2 generated Grok splits (#844). The governance subtrahend goes to 42 and the right-hand side stays 102. - SEMANTIC: main's new performance-monitor/agentIdentity walked tileTabs/dispatchMode/Tab.root, all deleted by #992. It is ported to the stage model, where the always-visible index wins, the same rule as resolveAgentPaneLabel. Its tests move to the v3 fixture shape. The monitor's UI test had hidden a v2 literal behind an `unknown` cast. Verified: tsc -b clean. All 48 test files main added or changed since the merge base pass on the merged tree (renderer 64/64, unit 188/188), and so does catalog.test (24/24). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Juliusolsson05
added a commit
that referenced
this pull request
Sep 19, 2026
…ckage PR Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… shape CI on the main merge failed twice: - the minimum-node gate's npm ci rejected the lockfile. The branch carried a lock pruned by an earlier worktree npm install (512 lines short of main, with an identical package.json), so main's lock is restored as-is; - the v3 projection test expected the pre-merge shape. Main's remote read model added tldrIdentity and pinned to projected sessions, and the exact expectation now includes them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Juliusolsson05
added a commit
that referenced
this pull request
Sep 19, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pick up #1022 (goal-loop input freeze), #1023, #933 (tmux inventory) and #1012. One conflict, in useKeybinds' onBlur: keep #1022's dismissGoalLoop() and drop `pendingTiledResizeIndex`, which #992 deleted along with tiled resize. #1022's overlay gate and rebind-safe dismiss chord merged intact. Verified: tsc -b clean. goal-loop + tile-tree + tldr renderer 175/175; tmux + storage + remote-client unit 112/112. That includes #933's real-workspace recovery test running against #992's model. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l v2 file Fail-first against the branch: the stage document was written as file version 2, and nothing backed up the original. The input is the live v2 workspace.json from #933's sanitized fixture, driven through the real store on the real filesystem in a temp directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…p the original Review A (persistence) found a data-loss path. The stage document kept the outer file version at 2, so every older build, including the released beta, treated a v3 file as writable. After a downgrade, a two-window close ran the old build's window handoff, which adopted the stage document, dropped every session it could not read, and saved over the pool. Writing version 3 trips the older builds' own gate (anything but 2 is unreadable, so they run read-only), which exists exactly for a newer file. This build reads 2 and 3, and the v2 payload still migrates in the renderer. Because the upgrade is one-way for older builds, the store keeps the untouched original once, as workspace.json.pre-v3-<ts>.bak, before its first v3 write, and never overwrites that backup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review A found the migration correct in a scratch probe on real files, but the repo only tested a generated loop and a hand-trimmed literal. The owner's live workspace (sanitized) and the recorded global-Dispatch document now go through the real migration. Every session survives with its identity fields unchanged, including the tmuxName that startup recovery matches on, and migrating twice changes nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner
Author
|
Review A (persistence): CHANGES REQUESTED → the blocker is fixed.
|
Found by the #1013 parity review (MAJOR). The unified stage deliberately does not write a Spotlight pick into a lane, and command targeting read only the focused lane. So after switching agents inside Spotlight, every command acted on the lane agent hidden behind it: Tail, Jump Latest, Close Focused Session, Stop Goal Loop, reload, provider switch, and the assistant and code-block pickers. On main the pick was mirrored into the grid or Dispatch focus that targeting read, so this is a regression for everyone who used the grid or classic Dispatch. commandTargetSessionIdForState now answers with an open takeover's session (Reader, then Spotlight) before the lane, which is what control.ts already reported as focused. A takeover whose session is gone falls through to the lane. The fail-first test drives the real workspace hook and the real registered Tail command. It also fixes a type import in the migration test that tsc caught. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by the #1013 parity review (MAJOR). An API v1 extension runs only inside its frame. A cold action command such as timer.start queues itself and opens the view, and the queue flushes when the frame mounts. Under the unified stage a pane open waits in the pool when the focused lane is occupied, so no frame mounted. The command never ran, each retry pooled another copy, and placing any copy later fired every queued command at once. The legacy path now opens the view with reveal: an existing view of that id is reused (focused if it is already in a lane), and otherwise the view takes the focused lane. Either way the lane's occupant returns to the pool alive, exactly as an index click does. A plain Open view command still follows context-places. The fail-first test uses the real Timer 0.3.1 manifest, its last API v1 release, now a fixture with its provenance recorded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ents From the #1013 parity review NITs. The pane header's unused runtimes prop and imports are gone, and so are the shared provider contract's four related-agent props that nothing passed or read. So is the agent identity index's per-project label pass: the index already lists every session of a live project, so it never labelled anything. Comments that still described the grid, both Dispatch layouts and Tile Tabs now describe the stage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner
Author
|
Parity sweep B (surfaces): two MAJORs fixed, the MINORs deferred to a follow-up issue.
|
From #1013 review B (MAJOR). The 16 command ids retired with the grid and Dispatch modes were listed in the catalog test and nowhere else, so their saved keybinding and visibility overrides were never pruned. The binding index puts customized entries ahead of every default and gives an unknown id the global context. A saved nav-left override therefore took ⌥H/⌥← from Focus Lane Left, the gateway answered unknown, and the chord did nothing. Settings has no row for a retired id, so the override could not be found or removed. The ids now join RETIRED_BUILT_IN_COMMAND_IDS. The catalog test imports that set and asserts it equals its own retired list, so a future retirement cannot update only one of them. The fail-first test runs a saved blob through the real settings decoder and the real router. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From #1013 review B (MAJOR). The index nested children one level deep, and orchestrationParentId names the direct parent. An agent created by an orchestration child was skipped at the top level (its parent is in the group) and never reached from below, so it had no row: no label, no ⌘N or ⌥↑/↓, and no Spotlight chip. A lane holding it read "Agent no longer available" while it ran. The grid's related-agent strip, which matched the root id, was the only way to reach it, and the stage deleted the strip. Every descendant is now emitted depth-first under its root, all at depth 1, because depth is 0 or 1 by contract (one connector cell, and the child cap counts the depth-1 run). Tree order keeps each grandchild directly below its parent. A parent cycle, which has no root, lists its rows at the top level instead of dropping them. The test builds on the owner's real sanitized workspace. The grandchild link is added by the test, and the test says so: none of the 36 workspace snapshots on this machine had one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s pooled agent From #1013 review B (MINORs 7 and 8). - The badge was cleared only by setTiledLaneSession, and its comment claimed every placement goes through there. Label navigation, agents.show, views.agentSet, Agent Activity's Focus and the Performance Monitor place through agentIndexNavigation instead, so the chip stayed on an agent that was on screen. They clear it now, and so does revealing a pooled extension view. - ⌘T never marked the badge, so a first agent that could not take an occupied lane appeared nowhere visible. It also refused a lane holding a closed session, unlike every other spawn. Both now follow the shared rule. - The mark and the clear live together in pooledSpawnBadge.ts. - The contextPlacesSpawn case that wrote the lane by hand and asserted the badge SURVIVED is replaced by one that places through the real workspace hook and asserts the clear. Every new case fails with its behaviour line removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…screen editor chrome From #1013 review B (MINOR 6a/6b). Clear Lane on ⌥⌫ yields to text fields at routing, but two paths bypassed that: - Every dispatch-context chord was forwarded to main's native capture for extension documents, and the event re-dispatched to the host targets the <iframe>. The yield cannot tell that an input inside the extension had focus, so ⌥⌫ in an extension text field emptied the lane. OS text-editing chords are no longer forwarded, so the extension keeps them. The lane arrows still forward. - The fullscreen Global Editor's hidden-workspace guard uses the router's owned Option-chord list, which lacked Backspace. With focus in app chrome, ⌥⌫ fell through and emptied the focused lane hidden behind the editor. The stale header of useKeybinds (tree chords, resize, cmd-w collapse) is replaced with the stage grammar. The Option+Shift+Arrow reservation note is kept, because it is the only record of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the grid From #1013 review B (MINORs 13, 15, 16). - MCP and control descriptions told external agents to use placement.list, placement.attach and "detached/buried" agents, and all of those were deleted with #992. An agent following them would call tools that do not exist. - The workspace interaction reference described the New Agent picker's deleted geometric placement step (arrows, Shift+arrows, Backspace). - The starter card's pair label stripped only a trailing direction word, so the index walk read "Select Previous Agent ⌥↑ / ⌥↓". It now keeps the words the two titles share ("Select Agent"). A dead test helper is removed. - The ARCHITECTURE glossary defined Dispatch Mode, Buried session and Detached session as current concepts. It now defines stage, lane, pool and project. command-style.md no longer uses retired commands as examples. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… badge From #1013 review B (MINOR 9). Orchestration children always land in the pool wearing the new badge, and the child cap hides every child past the third, so in a 5-worker run two badges were never visible. The "+N more" row now carries the badge when any child it hides is new. rowScopedRows names the hidden children, and the row selects a single boolean, so it re-renders only when that answer changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…w grammar From #1013 review B (finding 14): the lane movers and the ⌘1–9 grammar had only "the chord routes to the command id" coverage. - laneKeyboard runs on the recorded global-Dispatch workspace (4 lanes, 24 rows): ⌘N places row N; a label past the end places nothing; a row bound to one project places nothing for another project's label; ⌥↑/↓ wrap at both ends; ⌥←/→ stop at the row's edges. - The router test presses real ⌘ digits: ⌘1 ⌘2 gives row 12, ⌘1 ⌘0 gives row 10, and ⌘0 alone addresses nothing. entryContinuity.renderer.test.tsx stays deleted: it covered enterTiledDispatch, a transition that no longer exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner
Author
|
Review B (UX/keyboard, CHANGES REQUESTED): findings addressed.
Owner decisions are listed in the PR body. Every fix has a fail-first test. tsc is clean. Renderer/unit/system for |
Juliusolsson05
added a commit
that referenced
this pull request
Sep 19, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he highlighted one From the #1013 parity sweep A (nav/close). The active project is only a label now (U4), and lane focus and index selection never move it. ⌘⇧W closed activeTab, which can be a different project from the one whose agent sits in the focused lane. A single idle session closes without a dialog, so the wrong project could disappear with no warning. The command now closes the project of the command target (the focused lane's agent, or the Spotlight/Reader agent) and falls back to the highlighted project only when nothing is targeted. Fail-first through the real close executor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From the #1013 verification review (APPROVE, one minor). A full disk fails the backup write after the wx open created the file, and each retry uses a new timestamp. The earliest workspace.json.pre-v3-*.bak, the one a user would take as the original, was therefore a 0-byte file. The partial file is now removed, unless the error is EEXIST, which is a real backup that wx refused to overwrite. Fail-first with a simulated ENOSPC mid-write. The two lifecycle tool descriptions the review also flagged now say what creation does: it fills the focused lane only when that lane is empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner
Author
|
Verification of every review-fix commit: APPROVE.
I'll merge once CI is green. |
Juliusolsson05
added a commit
that referenced
this pull request
Sep 19, 2026
This was referenced Sep 19, 2026
Merged
Juliusolsson05
added a commit
that referenced
this pull request
Sep 20, 2026
…lagged Review nits on #1046. These commands are in the default picker now, so their text is read by everyone rather than by whoever went looking: - Auto-follow All Visible Agents described its scope in terms of single dispatch, tiled and grid layouts, all retired by #992/#1013. - Switch Agents to Another Provider… named Claude, Codex and OpenCode only; the modal builds its destinations from each provider's declared switch targets, which include Grok. - The taxonomy comment justified the promotion with a class-wide claim about per-agent MCP toggles, which the same test contradicts four lines below by pinning orchestration and workflow MCP as advanced. The operative rule is the owner's pick. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Juliusolsson05
added a commit
that referenced
this pull request
Sep 20, 2026
Two conflicts, both from the unified stage (#1013): - PaneHeader: main deleted the related-agent strip and with it this header's only store read. The routing-gap notice this branch adds is the remaining reader, so it keeps the store import and subscribes to exactly one rare object on the displayed session. - ARCHITECTURE 8.2.1: main rewrote the section around a per-channel IPC table and kept the old sentence saying a session without recorded ownership is broadcast to every window. That fallback is the thing this branch removes, so its paragraphs replace that sentence and the table stays. Also: the routing-recovery test's WorkspaceState fixture still named `dispatchMode`, `detachedSessions` and `buried`, all gone with the unified stage, and `stage` is now required. Verified on the merge: npx tsc -b clean; the branch's six suites pass (78 tests) and the tile-tree suites pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 20, 2026
Juliusolsson05
added a commit
that referenced
this pull request
Sep 20, 2026
…l fleet T14. The evidence is the owner's live workspace, read through Agent Code's own control MCP rather than imagined: 33 sessions, 28 of them in one project, 3 with an explicit title, 0 with a spoken name, 11 on a lane. That says what is wrong with the current modal, in order: - it has no attention signal at all, so it cannot answer the only question worth asking of 33 agents — which one needs me; - with 3 titles across 33 sessions, rows fall back to the first user prompt, so the fleet reads as paragraphs, and three rows read "agent-code"; - it lists panes, so the 22 parked sessions most likely to be closable are the hardest to reach; - its vocabulary (tab, Dispatch order, grid, tiled) was retired by #1013; - no search, and actions hidden until hover while the real job is bulk. Six stages, with the reconciliation isolated in one row model that the view and the command are the only importers of. Six unknowns are listed rather than assumed, two of them owner calls: whether this is a modal or a takeover, and whether it replaces Close Old Agents and Close Idle Orchestration Agents. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 20, 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.
What
Closes #992. One layout: the workspace is a stage — ragged rows of lanes over a pool of sessions grouped by project. Grid mode, Dispatch mode, tile trees, detached/buried buckets and the mode toggle are all gone; every command, keybinding and doc now speaks lanes, rows, the agent index and projects.
Design decisions (recorded in full in
docs/superpowers/plans/2026-09-17-unified-stage-layout.md§9.1–§9.6):projectIdnames a live project; lanes, pins and the active project are pointers, never ownership. A project exists while ≥1 session names it. Autosave writes v3 only (projects/activeProjectId/stage/sessions) — an older build opening the file fails its shape check and lands in persisted-fallback with autosave locked, so a downgrade cannot overwrite a pool it does not understand.newbadge in the index, retired by the placement itself.processStatus), not a structure.requestRootCloseConfirmationare deleted. Closing a whole project is the existing Close Tab command (close-tab), which targets the active project. There is no separate Close Project command (corrected after review B).gridbinding context died with the tree.project | dispatch | reader | spotlight;modeis a deprecated literal.ManagedAgentPlacementis'dispatch'.Retired commands (release note)
Bindings and visibility overrides for these ids no longer resolve — release notes must say so:
dispatch-mode(was ⌘⇧M),global-dispatch(was ⌘⇧G) — modes and scope are gonenav-left/right/up/down(were ⌥H/J/K/L + ⌥Arrows, grid context) — those chords now drive the registered lane grammar (seedispatch-focus-lane-*,dispatch-select-*-agent)normalize-layout,hard-normalize-layout,rotate-layout— tree-onlytiled-tabs,bury-pane,revive-pane,kill-buried-pane— hidden-but-alive is the pool's default stateattach-detached-to-grid,attach-all-detached-for-tab,detach-to-dispatch— showing a pool session is a lane selectionSplit-command ids and chords are frozen: ⌥D/⌥⇧D/⌥T/⌥⇧T/⌥C/⌥⇧C keep firing what they always fired; the duplicate "-horizontal" palette rows are hidden but stay runnable.
Review fixes (after reviews A, B and the two parity sweeps)
workspace.json.pre-v3-<ts>.bak.newbadge:agents.show, Agent Activity and the Performance Monitor.useKeybindsheader describe the stage.Owner decisions (defaults shipped as built; say the word to change any)
newbadge. It is a small accent chip, kept in memory only, and cleared only by placing the agent.Stages
One commit per plan stage: migration & v3 shape → stage always renders → one command surface → autosave v3 → 3a tree surfaces → 3b-i stage required → 3b-ii tree deletion → 4 spawn/close semantics → 5 keyboard registry → 6 starter card → 7 contracts/docs → 8 cleanup.
Verification
npx tsc -bcleancheck:keybindingsOK — 45 binding sets, 13 reserved interactions, 8 approved overlapstest:contractsatisfied; worktree-live-fixture, conversation-fixture and live-resume-probe checks passrender-architecture-diagrams.mjs --checkfails on 16 untouched diagrams with this machine's Chrome (font-metric drift vs the committed SVGs); the two diagrams this PR changed render and verify🤖 Generated with Claude Code