Six findings from reviewing the accumulated blitz/spawn-v2 diff:
- loadRecents restored the seeded cwd but never emitted it, so the leaf's
draft kept cwd:"" and the next render reseeded the form blank.
- stripDrafts did not recurse, so every half-filled draft in a split tab
was written to localStorage; the old test passed only because hydrate
discards draft on the way back in.
- showRawCwd and selectPreset grabbed focus from async init paths, so a
draft pane stole the caret seconds after mounting.
- .mv-draft inherited no layout once the form left .modal-body.
- onSuccess mounted the new session onto a leaf that may have been closed
while the POST was in flight, stranding it in no pane.
SPA attach had no reconnect at all. Any dropped frame closed the socket,
`onClosed` fired, and the pane was destroyed permanently — the TUI
reconnects forever on the same endpoint. Session death also reached the UI
on two uncoordinated channels: the row from the events socket, the pane
from `ws.onclose`, with nothing reconciling them, so either could happen
without the other.
The catalog is now the authority. `shouldRetryClose` decides from session
status: Running means the link dropped, so retry with 500ms-to-10s
backoff; Dead means the PTY stream was sealed and nothing more is coming;
absent means it is gone. Application closes (4000+, a relayed daemon
rejection) and clean closes are never retried. The shell also closes any
pane whose session has left the catalog. The policy lives in reconnect.ts
so it is tested without a DOM.
kill_session checked `has()` before `kill()`, which only opened a window
for the liveness watcher to reap in between — turning a 404 into a 502.
`kill` reports the miss itself.
with_spawn_history logged and carried on when `Arc::get_mut` failed,
producing a daemon whose recent-dirs picker is permanently empty with
nothing to show for it. It panics now: that is a construction-order
mistake, at startup, in code that runs once.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The sidebar-drawer toggle now lives left of the logo in the global header
instead of floating over the content area / tab bar. Removes the in-canvas
fab and tab-bar menu button and their styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The welcome/empty-tab branch returned before renderMobile, so with the tab
bar collapsed there was no ☰ — leaving mobile users stuck with no way to
open the sidebar drawer. Float the same ☰ over the welcome view.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On phones (single tab, one pane at a time):
- drop the pane border/padding/focus-ring so the session fills the screen
- collapse the redundant tab bar; float a ☰ to keep the sidebar drawer reachable
- disable pull-to-refresh (overscroll-behavior) so terminal scroll wins
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Below 860px the split tree projects to a one-pane-at-a-time pill strip (no
splits/drag/resize — desktop only); the tree is untouched so widening restores
the splits. Only the visible pane's session stays live; the rest park. The
sidebar becomes an off-canvas drawer toggled by a ☰ in the tab bar, with a
backdrop; tapping a session opens it and closes the drawer. Re-renders on the
breakpoint crossing without losing sessions.
Closes#35. Part of epic #37.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Save the layout tree (tabs + splits + focus + ratios) to localStorage,
debounced, on every structural/focus/resize change; restore it on boot. Load
rebuilds with fresh node ids and defensively drops malformed data — a corrupt or
versioned-out blob falls back to a single empty tab. Sessions that no longer
exist when restored aren't pruned eagerly (the session list isn't known at
boot); their panes attach, the broker closes the dead socket, and the pane
auto-closes.
Closes#34. Part of epic #37.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render the content pane as tabs of nested split trees, each session leaf a live
attach mounted through the multi-mount pool:
- #30 arbitrarily-nested H/V splits with mouse-resizable dividers; per-pane
⏻/▥/▤/✕ tools; closing a pane collapses its parent split.
- #31 tabs, each holding its own tree; add / switch / close.
- #32 drag-to-dock: drag a pane title or a sidebar session onto a pane for a
5-zone dock (edges split, centre swaps/replaces); a sidebar drag moves an
already-open session.
- #33 focused pane at full strength, others dim; sidebar shows focused (loud)
vs open-elsewhere (quiet) with a stable per-session colour mapping rows to
panes.
Opening a session fills the focused pane (single-pane parity by default);
multi-pane is opt-in via split / drag / tabs. Panes auto-close when their
session ends (guarded against eviction and intentional close). The steady
"connected" label is hidden as noise.
Closes#30, #31, #32, #33. Part of epic #37.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>