* wip(layout): applyDefaultTabs command in the layout model (dark)
* wip(layout): applyDefaultTabs takes the window's proposed ids; main refuses a taken one
* wip(layout): main closes or keeps an exited split pane by the window's rule (dark)
* wip(layout): a create or split sent again after its close is refused (dark)
* wip: typed test doubles
* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)
* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)
* wip(layout): main provisions setup and default tabs even when the agent fails to start; no window takes over (dark)
* wip(layout): pane start status in frames; a failed start keeps its pane; a terminal close stands after a failed write (dark)
* wip: lint fixes (max-lines, test throws)
* test(layout): a terminal close stands after a real failed profile write; switch off still refuses
* fix(layout): checked types in the close-failure rule and its tests
* fix(layout): a failed start keeps its pane whatever failed; default tabs start once; a tab's last pane keeps its card (dark)
* test(layout): the agent's kept pane counts as the workspace's terminal; main starts it whatever failed
* fix(layout): the switch-off create path gains no await
* fix(layout): a failed start never writes back a pane the user closed (dark)
* fix(layout): a failed start's pane goes through main's create rules, so a closed tab stays closed (dark)
* test(layout): D1a's switch-on close tests assert the intent main receives (both builds)
* test(layout): the close fixture answers main's command without naming the method
* fix(layout): a layout command that changes a pane's terminal raises its binding generation (dark, layout-owner build only)
* fix(layout): main shows unshown live terminals again when a client attaches or the terminal service is replaced (dark, layout-owner build only)
Headless servers had no retry: a terminal the startup pass declined stayed tabless until the
next restart. The pass now also runs when a client authenticates and after the local terminal
provider is installed or replaced, one pass at a time. A terminal whose pane record fails to
save after main bound it is no longer reported as left without a pane.
* feat(layout): main stores each pane's last grid on its tab layout (dark, layout-owner build only)
A new main-only setPaneGrid command records the grid a pane's terminal was last sized to, on
its leaf. It is saved as the layout's optional lastGridsByLeafId, which older builds drop on
read, so a downgrade loses only the hint. It moves with its pane.
* feat(layout): main remembers a resized terminal's grid on its pane and starts the pane at it (dark, layout-owner build only)
Each accepted resize, from a window or a client, is written once per burst as the pane's last
grid. A pane main holds starts at that grid, after a size the caller measured and before its
group's size, then 120x40.
* fix(layout): a re-created pane binds as designed; the id-only ratchet reads a shared import (dark)
* fix(layout): a pane's grid resized just before quit is saved, and a grid change publishes nothing (dark, layout-owner build only)
The desktop and serve quit and orcad's shutdown write any grid still inside its one-second
burst before the final store flush; a quit with none waiting adds no await. A grid is main's
own record: it is not in a published pane, so a resize burst sends no layout frame, and its
write patches only the pane's tab.
* feat(layout): a pane's status names sleeping, resuming and held, and what a status on a bound pane means (dark, layout-owner build only)
* fix(layout): a pane's start status may sit on a pane still bound to its old terminal (dark)
* fix(layout): main starts fresh a restored pane bound to another workspace's terminal (dark, layout-owner build only)
The window's restore rule, now in main for every host: a pane whose stored terminal id was
minted for another workspace is unbound, so it starts fresh, and that terminal, if live, is
placed in its own workspace. The id check moves to shared, which the window now imports.
* fix(layout): a start refused because a newer build's terminal service may still run the pane shows it held, not failed (dark, layout-owner build only)
* fix(layout): a pane's start status ends when its pane binds again by any path (dark)
* feat(layout): main's one start of a restored pane with no live terminal, shared by every non-window trigger (dark, layout-owner build only)
Restores from the pane's own stored terminal and resumes a local agent record; concurrent
triggers share one start, and a start that lost to another starter resolves to the winner's
terminal. Not wired yet.
* feat(layout): a client's first view and a CLI start of a restored pane go through main's one starter (dark, layout-owner build only)
Twin j: the phone's first-view start of a pending row moves into its own runtime layer and,
in the layout-owner build, runs through RestoredPaneStarter, which A1's send, wait, read and
wake call as ensureRestoredPaneStarted. It restores from the pane's own stored terminal, resumes
the agent the host recorded there, and resolves once the terminal is registered. With the switch
off, the phone's start is unchanged.
* test(layout): same-id restore ends a status; a replayed split brings back a closed pane by design
* test(layout): a window restore, a client first view and a CLI send of one restored pane start one terminal (layout-owner build)
* fix(layout): the restore pass releases a pane from another workspace's terminal through main's layout command, and two files stay under the line limit (dark, layout-owner build only)
A new main-only releasePaneTerminal command unbinds a pane from the terminal it names and stops
nothing, so the pass writes no layout outside a command. The stored terminal layout schema moves
to its own file, and a pane's own size (measured, else its last grid) is one helper.
* test(layout): drop a needless spread in the pane release test
* feat(layout): main shows a restored pane whose terminal is gone as sleeping, and a window treats a status-only frame as a binding change (dark, layout-owner build only)
After a census that answered, each local pane bound to a terminal the host no longer runs gets
status 'sleeping' and keeps the id it restores from; any bind ends the status. Relay and paired
server panes are their own host's to judge. A window rebuilds nothing for a frame that only
changes a pane's status.
* test(layout): restored history reaches each start across two host restarts with no client acking the cold restore
A guard, not a fail-first test: the cold-restore ack only drops the daemon's in-memory copy
(daemon-pty-session-control.ts) and the re-anchored checkpoint carries the history
(daemon-pty-spawn-result.ts), so a host with no client keeps it. C1c moves the ack into main.
* test(layout): the activation gate and worktree-reopen tests assert the same behaviour through main's command and frame (layout-owner build)
In a layout-owner build main places live terminals and draws them through its frames, so the
gate's adoption tests now check the window makes no tab or binding of its own, adopts only what
main's frames hold, and resumes no agent over a live terminal main has not placed yet. The
worktree-reopen tests feed main's frame for the restored pane and answer layout.apply, checking
one tab bound to the live terminal and one resumed tab across reopens.
* fix(layout): a restored SSH pane reattaches over its relay, and the restore starter returns the terminal it started (layout-owner build)
An SSH terminal id names its connection, not a workspace, so the rule that drops another
workspace's terminal no longer releases it or starts the pane fresh. The starter now resolves
to the id its start registered instead of re-reading the stored layout, whose bind write can
still be in flight, and a tab saved with no layout counts as one held pane.
* fix(terminal): keep shipped exit and default-tab behaviour with the layout switch on
An exited pane closes through the legacy retirement unless the keep rule holds; the
default tab's title and color still reach the window and the phone's live tabs.
* fix(layout): the restore starter's start result types a missing terminal id as none
* fix(layout): a workspace's first tab waits for main's placement of its live terminals, so activation opens no extra shell beside one (layout-owner build)
A window activating a workspace before main's placement pass had read the host census found no
tab and asked main for the first one; main admitted it into the still-empty workspace, then the
pass added the live terminal's own tab beside it. An initial create now waits out a pass in flight.
* fix(layout): the restore starter never resolves to another workspace's live terminal, and a first view of a pane main dropped is refused (layout-owner build)
* test(layout): the first-tab placement test overrides the worktree map with its declared type
* fix(layout): the first-surface wait lives with main's placement pass and keeps the runtime file under the line limit
* fix(terminal): one id guard for every pane write; keep panes on an unvouched -1
Main's taken and closed-id guard now runs where every pane write goes, so a runtime create
replayed after its tab closed is refused before it spawns. An exit code nothing vouches for
keeps the pane, as the window does; a kept pane sends the phone no retirement. Converts the
switch-on tests that asserted the deleted window hand-off and title bridge.
* fix(layout): a workspace's first surface waits only for the placement pass running when it arrives, and the switch-off build applies commands as before
* fix(terminal): keep the closed-tab guard on every pane write, held-id checks on client commands
A paired host starts each pane of a client's tab as a create under the tab's id, so a held tab
id is refused only for a client's own layout command.
* fix(layout): a quit's pane-grid writes never hold the stop past one second, and orcad awaits nothing for them when none wait
The grid flush before the final save (desktop and serve quit, orcad stop) is now raced against a
1 s budget; a grid it misses only means that pane restarts at the default size. With the switch
off no grid is ever recorded, so the flush is null and orcad no longer awaits even that.
* fix(terminal): a provider-reported -1 keeps the pane, as the window does
Only a requested stop or a host-confirmed death closes a pane on a negative exit code. Pins that
the closed-tab guard never fires in the shipped build.
* fix(layout): a quit's grid flush swallows its own failures, keeps its budget timer live, and the quit's no-await note names it
* chore(terminal): satisfy the exhaustive-switch and test typing checks
* test(terminal): give the replay test's live process a cwd
* fix(layout): a quit writes pending pane grids before the SSH shutdown, and a workspace's first tab waits at most 0.5 s for main's placement pass
The quit's grid flush (still capped at 1 s) now runs before beginSshShutdown, so nothing is awaited
between it and the final save; with no grid waiting, both start synchronously as before. A first tab
waits for the running placement pass at most 500 ms, then is admitted. The quit test's SSH mock now
names the module the quit imports.
* test(layout): the quit tests prove the final save starts in the same turn as the SSH shutdown, in order after the PTY kill
* chore: keep main's bytes for files the merge's format hook rewrote (native-chat formatting is #31184's)
---------
Co-authored-by: E2E Test <e2e@test.local>