Skip to content

Unified stage layout: one workspace of lanes, rows and a session pool - #1013

Merged
Juliusolsson05 merged 34 commits into
mainfrom
feat/unified-stage-layout
Sep 19, 2026
Merged

Juliusolsson05 merged 34 commits into
mainfrom
feat/unified-stage-layout

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 19, 2026

Copy link
Copy Markdown
Owner

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):

  • Ownership is on the row. A session is owned iff its projectId names 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.
  • Context-places spawn. A spawn fills the focused lane only when it is empty; an occupied lane is never displaced. Everything else lands in the pool wearing a new badge in the index, retired by the placement itself.
  • Boot spawns the focused lane's occupant only. Agents wake on first send, terminals on mount; wake decisions read the runtime (processStatus), not a structure.
  • Every close is session-scoped. The three-way "Close the agent or the tab?" dialog, row promotion and requestRootCloseConfirmation are 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).
  • Clear Lane (⌥⌫): empties the focused lane; the occupant returns to the pool alive. ⌥⌫ forced the runtime half of the macOS text-editing reservation — OS-owned text chords are now yielded at routing while a text field owns the target.
  • The lane keyboard grammar is registered. ⌥↑/↓/←/→ (+H/J/K/L) are rebindable commands; the inline branch that silently swallowed Alt+Shift+Arrow is gone. The grid binding context died with the tree.
  • Starter card. A registry-driven card of the 8 key commands on a fresh agent (no user turns) and the four placement-flavored slots on an empty focused lane — live bindings, never hardcoded chords. ⌘N now binds New Agent….
  • SDK narrowed. Placement kinds are project | dispatch | reader | spotlight; mode is a deprecated literal. ManagedAgentPlacement is '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 gone
  • nav-left/right/up/down (were ⌥H/J/K/L + ⌥Arrows, grid context) — those chords now drive the registered lane grammar (see dispatch-focus-lane-*, dispatch-select-*-agent)
  • normalize-layout, hard-normalize-layout, rotate-layout — tree-only
  • tiled-tabs, bury-pane, revive-pane, kill-buried-pane — hidden-but-alive is the pool's default state
  • attach-detached-to-grid, attach-all-detached-for-tab, detach-to-dispatch — showing a pool session is a lane selection

Split-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)

  • Downgrade safety:
    • The file is written as version 3, so older builds refuse it and run read-only instead of overwriting the pool.
    • There is a one-time workspace.json.pre-v3-<ts>.bak.
    • Migration is pinned on real recorded workspaces.
  • Commands follow the agent picked inside Spotlight or Reader. Before, they hit the hidden lane agent.
  • A cold API v1 extension command reveals its view, so it runs. Before, it was pooled, never mounted, and replayed late.
  • Saved overrides for the 16 retired command ids are pruned. Before, they swallowed ⌥H/⌥← and the other lane chords.
  • Orchestration grandchildren are listed under their root. Before, they had no row, no label, and "no longer available" in a lane.
  • The new badge:
    • It is cleared by every placement path, including label navigation, agents.show, Agent Activity and the Performance Monitor.
    • ⌘T marks it.
    • The "+N more" row shows it for hidden children.
  • ⌥⌫ never clears a lane from an extension text field or from fullscreen editor chrome.
  • Agent-facing MCP and control text no longer points at deleted capabilities. The glossary and the useKeybinds header describe the stage.
  • New behavioural tests:
    • the lane keyboard grammar and the ⌘ two-digit grammar;
    • the Spotlight command target;
    • the badge lifecycle through the real workspace hook.
  • Follow-ups: fix(workspace): carry v2 bury notes and harden degenerate-shape migration #1030 (persistence edge cases, bury notes, the hibernated-terminal tmux leak) and Unified stage follow-ups: phone agent list after restart, goal loops on pooled agents, dictation with an empty lane #1031 (the phone's live-only list after restart, a goal loop on a pooled agent, dictation with an empty lane, a v3 phone test).

Owner decisions (defaults shipped as built; say the word to change any)

  1. Never displace an occupied lane. With one lane, every New Agent, ⌥D/⌥T/⌥C, ⌘N or ⌘T after the first leaves the screen unchanged. The agent appears as a badged index row.
  2. The new badge. It is a small accent chip, kept in memory only, and cleared only by placing the agent.
  3. Clear Lane on ⌥⌫. It fires only when focus is not in a text field. The composer usually holds focus, so in practice it fires from the feed, a header or the index. Options: keep it, move it to a chord that isn't a text-editing chord, or make it palette-only.
  4. Deleting the related-agent strip. There is no in-pane hop between parent and child, and the terminal view loses its "parent" button. Children and grandchildren nest in the index instead.
  5. Close Project. Closing a project is Close Tab, as before. A separately named command is optional.

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 -b clean
  • Full vitest sweep: 524 files / 3741 tests, zero failures (workflow MCP bundle built first)
  • check:keybindings OK — 45 binding sets, 13 reserved interactions, 8 approved overlaps
  • test:contract satisfied; worktree-live-fixture, conversation-fixture and live-resume-probe checks pass
  • Migration covered by golden tests including a recorded 82-ghost v2 production pool; boot behavior covered end-to-end by the unified-stage integration suite
  • Known environment note: render-architecture-diagrams.mjs --check fails 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

Juliusolsson05 and others added 16 commits September 17, 2026 17:31
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>
Juliusolsson05 and others added 4 commits September 19, 2026 00:13
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>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Review A (persistence): CHANGES REQUESTED → the blocker is fixed.

Juliusolsson05 and others added 3 commits September 19, 2026 00:26
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>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Parity sweep B (surfaces): two MAJORs fixed, the MINORs deferred to a follow-up issue.

  • MAJOR fixed (52d9f30d): switching agents inside Spotlight did not move the command target, so Tail, Jump Latest, Close Focused Session, Stop Goal Loop, reload and provider switch acted on the lane agent hidden behind Spotlight. Command targeting now answers with an open takeover's session first, matching what control.ts already reported. The fail-first test runs the real workspace hook and the real Tail command.
  • MAJOR fixed (a409c859): a cold command from an API v1 panel extension never ran while the focused lane was occupied, because the view went to the pool and no frame mounted. Each retry pooled another copy. The legacy path now reveals the view: it reuses an existing view, and otherwise the view takes the focused lane, with the occupant going back to the pool alive. The fail-first test uses the real Timer 0.3.1 manifest (its last API v1 release).
  • NITs fixed (b12b54f0): the PaneHeader leftovers, the dead related-agent props in the provider contract, the dead label pass in the identity index, and stale mode comments.
  • Deferred to Unified stage follow-ups: phone agent list after restart, goal loops on pooled agents, dictation with an empty lane #1031: the phone lists only live agents after a restart, a goal loop on a pooled agent is invisible, dictation with an empty lane goes to another lane, and there is no v3 phone test.
  • Verified OK in the sweep: Goal Loop mounting, the TLDR and Goal peeks, Performance Monitor, Agent Analytics grouping, dictation targeting, Grok, extension panes, and the PaneHeader removal (documented in plan §9.2).
  • tsc is clean. Renderer and unit tests for workspace, apps, features and shared: 1971/1971.

Juliusolsson05 and others added 7 commits September 19, 2026 00:38
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>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Review B (UX/keyboard, CHANGES REQUESTED): findings addressed.

  • 1, BLOCKER (tsc): fixed in 52d9f30d, where the type import moved to persistence.
  • 2, MAJOR (Spotlight/Reader target): fixed in 52d9f30d. The takeover answers first, for Reader too.
  • 3, MAJOR (retired overrides swallow lane chords): fixed in db7a2f9f.
    • The 16 ids are pruned.
    • The catalog test asserts both retired lists are equal.
    • The fail-first test runs a saved blob through the real decoder and router.
  • 4, MAJOR (grandchildren unreachable): fixed in eb046585.
    • Descendants are emitted depth-first under the root, still at depth 1.
    • A parent cycle is listed instead of dropped.
  • 5 (cold v1 command): fixed in a409c859.
  • 6a/6b (⌥⌫ from an extension input, fullscreen editor): fixed in 0e0d69ac. 6c is owner decision 3.
  • 7/8 (badge not cleared; ⌘T): fixed in 60a39109. The tautological test is replaced.
  • 9 (badge behind the child cap): fixed in c8f0c5ab.
  • 11 (Close Project): the PR body is corrected; see owner decision 5.
  • 12 (Spotlight label vs palette label): this predates the PR in Dispatch. Left as is and noted.
  • 13, 15, 16 (agent-facing text, starter label, stale docs): fixed in 33e62de7 and 0e0d69ac.
  • 14 (test gaps): lane grammar and ⌘ digits are covered in af0c88d7. entryContinuity stays deleted: it covered the removed enterTiledDispatch.
  • 10 (phone after restart): deferred to Unified stage follow-ups: phone agent list after restart, goal loops on pooled agents, dictation with an empty lane #1031.

Owner decisions are listed in the PR body. Every fix has a fail-first test. tsc is clean. Renderer/unit/system for src/renderer, src/mcp and src/main/control: 2363/2363 before the last two commits. Targeted suites are green after them.

Juliusolsson05 added a commit that referenced this pull request Sep 19, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Juliusolsson05 and others added 3 commits September 19, 2026 00:58
…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>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Verification of every review-fix commit: APPROVE.

  • Tests: 2528/2528 across unit, renderer and system for src/renderer, src/main/storage, src/main/remote, src/mcp and src/main/control. tsc is clean.
  • The one minor finding is fixed in 3f61d426: a backup write that fails mid-way (a full disk) no longer leaves a 0-byte .pre-v3-*.bak that looks like the original. There is a fail-first system test. The stale lifecycle tool text is fixed in the same commit.
  • Left as notes, nothing to fix:
    • rows in a parent cycle are listed at the end of their group (reachable only with corrupted links);
    • the child cap now counts grandchildren (by design);
    • Spotlight jumps to the project's first agent after a reload's id swap (same as main);
    • two memoized readers can go stale with no visible effect.

I'll merge once CI is green.

@Juliusolsson05
Juliusolsson05 merged commit 4d0374a into main Sep 19, 2026
2 checks passed
@Juliusolsson05
Juliusolsson05 deleted the feat/unified-stage-layout branch September 19, 2026 09:00
Juliusolsson05 added a commit that referenced this pull request Sep 19, 2026
… verified

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
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.

Unified workspace layout: merge Grid and Dispatch into one stage-over-fleet workspace

1 participant