Replaces the 12.7k-line vanilla TypeScript SPA with React + Zustand +
Tailwind. 16 issues across milestone 48.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mark_session_dead_with_worktree_settlement cloned the session under the
lock, dropped it, did async work, then wrote the stale clone to the
sidecar. A haiku rename (or any other metadata update) that landed
between the clone and the write was silently overwritten, so the sidecar
reverted to name_source: Generated and the next daemon recovery
re-triggered autoname.
Re-read record.session under the lock at write time so the sidecar
always reflects the latest state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the deleted kit.ts + kit.css with DesignKit.tsx, gated behind
import.meta.env.DEV and loaded on ?kit. Shows the full tailwind.config.ts
token mapping (colours across all 6 palettes, typography, spacing,
radii, z-index) plus buttons, ActivityRail states, SessionCard states,
and terminal chrome, all switchable live through ThemeContext.
Closes#254
Deletes the vanilla shell and mounts App.tsx on #root. main.ts and the
23 views it drove are gone; App.tsx composes the ported components —
sidebar, multi-view, command bar, spawn modal, help and restart
overlays, daemon link, drawer — around the catalog and layout stores.
The pure parts of four deleted modules moved next to the components
that still use them: the spawn-form model and spawnSimilar into
components/spawn, the transcript's markdown/format helpers and the diff
into components/structured, the help overlay's grouping into
components/dialogs. Document-level event names now live in uiEvents.ts.
Deduplicated two pairs the migration left side by side: the sidebar
uses dialogs/KillDialog and common/ContextMenu. It also listens for the
command bar's kill/rename events, which nothing had picked up since
catalog.ts went away.
styles.css stays as the token and base layer, with `#app` renamed to
`#root` and the pane-body rules re-keyed off the classes the React
panes actually carry.
Closes#253
DaemonLink.tsx holds the link state machine as a Zustand store (same
shape as catalogStore/connectionStore) plus a thin controller component
wiring the catalog store's `connected` field to it. RestartOverlay.tsx
renders the blocking modal for down/updating, reusing the existing CSS.
Closes#242
Ports main.ts's parseRoute/dispatch hash parsing into a React hook:
same #/ and #/attach/:id shape, current route plus a navigate function.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends layoutStore with previewSession/commitPreview, mirroring
multiview.ts's preview controller: a throwaway tab for a session no pane
holds, byte-for-byte restore on end, and every real layout-changing action
drops an active preview first. Never schedules a save while a preview is up.
Adds usePreview, wrapping the store actions with previewSchedule.ts's
120ms leading-edge throttle, plus a touch guard and an immediate `end()`
for real changes. MultiView/TabBar render the preview hue on the pane
titlebar, canvas ring/chip, and the throwaway tab (dashed, unclickable).
Closes#250: kill confirmation/force-kill (dialogs/KillDialog), the ?
shortcuts overlay (dialogs/HelpOverlay), the general-purpose right-click
menu (common/ContextMenu, closes on scroll/resize unlike the sidebar's
own), the mobile drawer with swipe support (layout/Drawer), and the
visual-viewport hook (hooks/useViewport). Each is a standalone, tested
port of its vanilla-DOM source; wiring into the shell is a later issue.
Closes#249: layoutStore.ts wraps the existing layout.ts tree model
in Zustand, persisting to localStorage on a 400ms debounce and adding
boot reconciliation to prune leaves for sessions no longer live.
MultiView/TabBar/PaneContainer/PaneTitle render tabs, nested splits
with draggable dividers, drag-and-drop tab reordering, and dispatch
each pane to TerminalPane or StructuredPane by session view.
Preview mode (#251) and sidebar drag-to-dock stay out of scope here.
CommandBar, ResultList, ActionPage and useCommandBar over the same
model docs/COMMAND-BAR.md specifies: sigils, empty-query session order,
the drill-in action page, and the keys.
The highlight now walks the rows in the order they render. The legacy
bar indexed the highlight by display order but activated by rank order,
so Enter could act on a different row than the one lit.
Ports the vanilla spawn form/modal to src/components/spawn/:
SpawnModal, SpawnForm, DirAutocomplete, BranchPicker, and a
useFastForward hook for the one-keystroke spawn. Pure logic modules
(spawnForm.ts's buildSpawnRequest, fastForward.ts, spawnSimilar.ts,
projectList.ts, toolIcon.ts) are reused as-is rather than re-derived.
Exports supportsStructuredView from spawnForm.ts so the React form can
share the structured-view-eligibility check with the vanilla one.
Closes#246. The transcript, composer and approval controls move from
structured.ts/structuredRender.ts into components under
web/src/components/structured/, against docs/STRUCTURED-VIEW.md: eight row
kinds, both projections, the single-active approval nonce, and diff rendering
with its own line counts.
The pure layers stay shared — structuredDiff.ts computes the diff,
structuredRender.ts still sanitizes markdown — and model.ts holds the
client-side decisions (activity grouping, notice classification, offered
decisions, `/` completion) so they are testable without a DOM.
Like TerminalPane, the pane draws the body only; pane chrome reads the
connection through onStatusChange.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Add #root div to index.html so React entry point can mount
- Replace unsafe type cast in Sidebar with typed CatalogStore selector
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TerminalPane is a thin ref-based wrapper: xterm lifecycle lives in a
single mount effect keyed on sessionId, tearing the socket and terminal
down on cleanup. Preserves webglRenderer.ts's page-wide WebGL context
budget (acquire on mount, release on unmount, re-acquire on context
loss while still mounted) and reconnect.ts's exponential 500ms->10s
backoff. keys.ts's mobile modifier encoding moves into KeyBar.tsx.
Pane chrome (title, disconnect, sessions drawer) stays out of scope;
composition happens one level up.
Ports catalog.ts's tree renderer to components under
src/components/sidebar/, following docs/SIDEBAR.md: two-line cards
behind a 5px activity rail, adaptive meta line (duration, detail,
program, diff stat), collapsible folders, j/k walk, inline rename and a
right-click menu.
The nav walk reads a pure model (model.ts) rather than the DOM, so the
rule that an expanded folder header is skipped and a collapsed one is
not is testable without a render. Rail motion needs @keyframes, so it
stays in sidebar.css rather than the Tailwind config.
Closes#244
Wires tailwind.config.ts to every custom-property token in styles.css
(spacing, OKLCH colours, radii, z-index, sizing, durations) so Tailwind
utilities resolve to the same variables the vanilla CSS uses. Adds
ThemeContext/useTheme as a React port of theme.ts, keeping the same
localStorage keys and data-theme/data-palette attributes so it can run
alongside the vanilla shell.
Ports events.ts's CatalogStore and connections.ts's ConnectionPool to
Zustand stores (src/stores/catalogStore.ts, connectionStore.ts) built
via factory functions so tests can inject fake sockets/schedulers, same
as the originals. Reconnect/backoff schedule, the 6-max-parked LRU, and
the #140 view-reconciliation race handling all carry over unchanged.
The vanilla events.ts/connections.ts stay in place for the current app;
this is prep for the React port (#241).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds react, react-dom, zustand, tailwindcss, @tailwindcss/vite as a new
skeleton alongside the existing vanilla SPA. New src/index.tsx entry
point (unused until wired into index.html in a later issue); main.ts
stays the active entry.
The patch path did a DOM replaceWith and returned early, skipping the
focus-restoration logic in rebuildTranscript. Now saves and restores
tool-head focus within the surgical swap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sandbox has /tmp/.git, so the ancestor walk finds a git root
above the tempdir. Weakened the assertion: the result must not point
inside the .git-free tempdir, but may point above it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Queued prompts now appear inline in the transcript as faded user bubbles
instead of a pill badge above the composer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Block spans + literal \n text nodes inside <pre> caused empty lines
between every diff line. Drop the text nodes, add unified-diff prefixes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uploaded images now render as thumbnails in the user bubble instead of
showing only the server file path as text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reuse existing tool-group DOM elements in rebuildTranscript instead of
tearing them down — preserves CSS transition state and focus. Defer
config-menu DOM rebuild while the menu is open so <select> dropdowns
survive background state updates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "Running 1m ..." status now sits at the tail of the chat
history instead of pinned above the input, matching Claude Code's
placement.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Intermediate narration was hidden inside the collapsed tool group
while the agent was still working. The last AgentText/AgentThought
now stays visible as current status, absorbed only when the turn ends
or newer text replaces it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Structured sessions were rejected by `send_input` with NotTerminal.
Now `run_send` checks the session view and routes to
`send_structured_prompt` for structured sessions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A structured session still booting its adapter has no ACP handle and
no tmux pane, so the liveness pass misclassified it as a vanished
terminal session and reaped it. Claude Code adapters take long enough
to boot that the 2s liveness tick fires before the handshake finishes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests now assert `Starting` after create and use `wait_for_ready`
before checking post-handshake state. Unsupported command test
expects `BadRequest` instead of a persisted Dead session.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolved permission boxes vanish from the transcript instead of
lingering as "answered" cards. When a turn has 2+ tools the resolved
approval is absorbed into the tool group as a compact italic line;
otherwise it is hidden entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Register a structured session immediately with `activity: Starting`
and run the ACP handshake in a background task. The HTTP caller gets
an instant response; the user can begin typing a prompt while the
adapter boots.
- Add `ActivityState::Starting` to the protocol
- Open the event log eagerly so the structured WS can connect during
boot (empty transcript + idle state)
- `queue_acp_prompt` stashes a prompt while `Starting`; the background
task drains it on successful handshake
- `apply_structured_activity` skips overwriting `Starting`
- `subscribe_acp_projection` allows connecting during `Starting`
- TUI/CLI map `Starting` to idle colour / "starting" label
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Project boxes now use a fixed two-column grid so every card is the
same width. The worktree count line is removed — the information
lives in the worktree picker that follows a project selection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Patch deltas on existing rows now swap just the affected element via
replaceWith() instead of tearing down and rebuilding the entire
transcript. Stops the visible flash and preserves text selection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the harness/command choice above the project picker so the
operator decides what runs before where. Replace the flat command
tiles with larger centered cards that render each tool's icon from
toolIcon.ts at 24x24. The Custom escape hatch gets a three-dot
ellipsis icon and a dashed border to read as secondary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep icon cancel button, always-"Send" label, and queue status span
from main. Drop unused `active` local after label simplification.
Update test expectation to match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move max-width constraint from .structured-rows up to .structured-main
so the activity banner and composer match the transcript width. Replace
text cancel button with a stop-sign icon inside a spinning ring. Drop
the accent border-top on composer focus.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Intermediate agent text/thought rows between tool calls in the same
turn now render inside the collapsed tool group instead of after it,
preserving chronological order. The last agent text (no subsequent
tool) stays outside as the prominent final answer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Structured sessions ignored the --dangerously-skip-permissions flags in
preset commands because the daemon discards CLI flags when spawning the
ACP adapter binary. This adds an `auto_approve` bool to SpawnPreset and
CreateSessionRequest so the operator can declare it in config.toml:
{ id = "claude", command = "claude", auto_approve = true }
When set, the daemon's permission pump immediately responds AllowAlways
to every ACP permission request without surfacing it to clients.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Drop the per-tool name list from group headers (redundant with the rows
below), remove individual borders inside groups, and tighten padding so
20-tool blocks scan as a dense list instead of a wall of cards.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The attach button precedes the draft in the DOM, so under the ≤520px wrap
rule it took a line of its own above the textarea. An explicit order puts
the draft back on the first line and groups every control below it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts the stacked control strip from f78aa13. The floating card is still
gone — flush band, one hairline against the transcript that doubles as the
focus indicator — but the composer keeps its own `--surface` fill so it
still reads as distinct from the pane, and the draft, gear and Send stay on
one bottom-aligned row.
At phone width that row cannot hold a draft plus three controls, so it
wraps: the draft takes a full line and the controls drop below it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Claude Code or Codex spawn now asks for the structured view unless the
operator says otherwise: the two spawn forms open with the box ticked and
`maestro spawn` takes `--terminal` to opt out. Once the operator works the
toggle, a command change no longer re-ticks it under them.
The support predicate moves to `maestro_protocol::display` so all three
clients gate on the same resolution.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The composer was a floating bordered card inset from the pane edge, and
its send button was bottom-aligned beside the textarea, so it sank as the
draft grew.
It is now a flush band at the foot of the pane: one hairline rule against
the transcript, which doubles as the focus indicator. The textarea spans
the full width and the controls sit in a fixed strip below it, so the send
button keeps its place however tall the draft grows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>