Commit Graph
15 Commits
Author SHA1 Message Date
Miguel Palhas 8c151a5fb2 Merge remote-tracking branch 'origin/main' into blitz/codex-structured-sessions
ci / web (push) Failing after 8s
ci / flake (push) Failing after 2m18s
ci / rust (push) Successful in 8m53s
# Conflicts:
#	crates/maestro-daemon/tests/worktree.rs
#	crates/maestro-e2e/tests/e2e.rs
#	web/src/attach.ts
#	web/src/connections.test.ts
#	web/src/connections.ts
2026-09-06 23:16:27 +01:00
Miguel Palhas 9533672e09 fix(maestro): require real DOM focus before reconciliation re-focuses (#140)
ConnectionPool.reconcileViews re-focused a repaired connection whenever
focusedId named it, but a same-tab sidebar preview moves the keyboard
into the sidebar without ever calling setFocus (multiview.ts's
syncFocus skips it while previewing) — so focusedId keeps naming the
pane the operator was in before the preview, even though the DOM
doesn't agree. Replacing that pane's connection during exactly that
window called onMount({focus:true}) and yanked the keyboard back out
of the sidebar mid-navigation.

Capture existing.el.contains(document.activeElement) before dispose()/
el.remove() detach it (afterward the browser has already moved
activeElement to <body>, too late to tell), and require both that and
focusedId===id to re-focus. document.body and a null activeElement
both naturally fail .contains(), so "nothing focused" and "focus
elsewhere" both correctly skip.

Regression tests: 3 new (real-focus requirement, same-tab preview
race, descendant focus, body-as-unfocused) — 498 tests total.
2026-09-05 06:49:48 +01:00
Miguel Palhas b11784e8e3 fix(maestro): mobile drawer access, focus preservation, stale rAF focus (#140)
Three more fixes from acceptance review:

- structured.ts's header omitted the .sessions-btn attach.ts's terminal
  header carries. The sidebar is off-canvas on mobile, so a structured
  session had no way to reach it — add the same button and
  requestToggleDrawer wiring.

- ConnectionPool.reconcileViews always remounted a repaired connection
  with focus:false. If the mistyped pane was the one actually focused,
  disposing it dropped DOM focus while `focusedId` still named it. Pass
  `focus: this.focusedId === id` instead — `focusedId` only ever names a
  real focus (multiview.ts skips setFocus while previewing), so a
  preview or a merely-visible split neighbour still comes back
  unfocused.

- onMount's deferred requestAnimationFrame focus had no way to tell it
  had been superseded by an unmount (or a remount elsewhere) before it
  fired, so a parked pane could steal focus a frame later. A token
  bumped on every mount/unmount lets the callback recognize it's stale
  and no-op.

Regression tests: 7 new (structured.test.ts, connections.test.ts). 495
tests total.
2026-09-05 06:41:40 +01:00
Miguel Palhas 42ffc1a352 fix(maestro): correct connection view race and approval decisions (#140)
Three fixes from acceptance review:

- ConnectionPool.mount cached a connection's kind (terminal/structured)
  permanently from whatever `info` it saw first. A deep link or restored
  layout mounts before the catalog snapshot names a session's view, so
  `info` is undefined and a structured session got an xterm connection
  forever. The pool now tracks the view each cached connection was built
  for, subscribes to the catalog itself, and replaces a mistyped
  connection in place the moment the real view resolves — without
  downgrading a resolved connection just because a later caller couldn't
  resolve the session, and without treating a session merely absent from
  a snapshot (mid-removal) as "Terminal".

- The approval card offered all three decisions unconditionally. Per
  the daemon's decision mapping (session.rs's permission_outcome), a
  decision with no matching PermissionOptionKind in the request's own
  options resolves Cancelled silently — so "Allow for session" could
  look like it worked while the daemon withdrew it. Buttons are now
  gated on the options actually offered.

- Clicking a decision disabled the buttons and never re-enabled them if
  the POST failed. The decision path is async now: a rejection restores
  the buttons and surfaces the error only if the approval is still the
  pending one — a superseded/resolved approval's stale rejection is a
  no-op, not a wrongly revived control.

Regression tests: 12 new (connections.test.ts, structured.test.ts,
structuredRender.test.ts). 488 tests total.
2026-09-05 06:32:08 +01:00
Miguel Palhas a50f098c8a feat(maestro): render the structured view in the web client (#140)
Adds a transcript surface for a Structured-view session alongside the
existing xterm attach view: a WS /api/structured/:id connection
(structured.ts) mounted by ConnectionPool whenever a session's `view`
is `"Structured"`, and pure per-row/rail renderers (structuredRender.ts,
structuredDiff.ts) covering all eight transcript row kinds.

Hierarchy follows the design reference (artifact 88ecd690): the
approval is the only lifted, card-like element; a user turn is a
tinted block with an accent rail; agent prose is plain text; tool
calls are compact bordered rows expanding to output or a coloured,
horizontally-scrolling diff computed from ACP's full old/new text.
The client renders `reduced_state` and rows exactly as received, with
no raw-event folding or turn tracking of its own.

Extracted attach.ts's pane-header title building into paneTitle.ts and
its connection interface into pane.ts (PaneConnection) so both
connection kinds share one contract and one header.
2026-09-05 06:10:40 +01:00
Miguel Palhas 1ad7845f51 fix(maestro): refresh attach header when a session is renamed
Header name/command were baked into innerHTML at connection creation
and never revisited. ConnectionPool.mount() reused the cached
connection on later calls but dropped the fresh info, so a header
never picked up haiku autoname's rename.
2026-09-04 19:38:09 +01:00
Miguel PalhasandClaude Opus 5 b2cbb23fe2 feat(maestro): open the bar with ctrl-k over a focused pane
The global listener already ran in the capture phase, but deferred to
isTypingTarget, so xterm's hidden textarea kept Ctrl-K. The bar now treats
that textarea as not a typing target, while real form fields — the spawn
form's inputs — keep their own Ctrl-K.

Leader then ctrl-k (ctrl-a ctrl-k) sends the literal 0x0B to the focused
pane through the leader chord the command set already resolves, so
readline's kill-to-end-of-line stays reachable. The pool grows a
sendToFocused for it, over a new AttachConnection.sendInput.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 17:03:19 +01:00
Miguel Palhas f6b2cf3ab4 feat(maestro): preview the highlighted session in the web pane
Moving the sidebar highlight (keyboard or hover) now shows that session in
the content pane without opening it: its own tab comes forward, or a
throwaway tab carries it. The layout, the saved layout and the daemon's
focus report are all left untouched, and the keyboard stays in the list.

Contract in docs/PREVIEW.md.
2026-09-03 10:42:53 +01:00
Miguel Palhas f7e7a77912 feat(maestro): clear AwaitingInput glow once a pane has been focused
A session's rail glows AwaitingInput even after the operator has already
looked at it. Clients now report pane focus (POST /:id/focus); the daemon
clears the glow to Idle after 5s of continuous focus and keeps it cleared
until the session does new work, so a fresh Active cycle is required to
glow again.
2026-09-02 18:03:26 +01:00
Miguel Palhas 1afe079ca0 feat(maestro): keyboard navigation for the web sidebar
Ctrl+Shift+H moves the keyboard between the session list and the pane;
j/k walk the cards, Enter opens, Escape hands the pane back. The walk
skips expanded folder headers and stops on collapsed ones, matching the
TUI.
2026-08-31 15:04:02 +01:00
Miguel PalhasandClaude Opus 5 f31377c34f fix(maestro): three defects the review flagged and left open
ci / web (pull_request) Successful in 28s
ci / rust (pull_request) Successful in 1m58s
ci / web (push) Successful in 34s
ci / rust (push) Successful in 2m44s
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>
2026-08-22 16:31:54 +01:00
naps62-yoloandClaude Opus 4.8 159b7ecd6e feat(maestro): multi-view web UI — splits, tabs, drag-dock, highlights
ci / rust (push) Successful in 5m46s
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>
2026-06-22 09:20:57 +00:00
naps62-yoloandClaude Opus 4.8 2999e888a8 refactor(maestro): multi-mount ConnectionPool; route web through layout tree
Rework the pool from single-active to a mounted-set model: several connections
can be visible at once (one per leaf), sessions no longer shown are parked
(still streaming) and bounded by an LRU buffer that never evicts visible panes.
Keep activate/parkActive/active() as compat shims and an onFocusChange/
onRequestClose surface; the HUD keeps working via active() -> focused().

main.ts now derives the single-pane view from a one-leaf layout tree via
setVisible/setFocus instead of calling activate directly — no behaviour change,
but the multi-mount path is now exercised end to end.

Refs #29 (epic #37).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 21:19:09 +00:00
naps62-yoloandClaude Opus 4.8 c02ff02849 feat(maestro): terminal-native web UI pass + design kit
ci / rust (push) Successful in 4m26s
Live-review iteration on the browser SPA, driven by crit feedback.

Sidebar / session tree
- Compact single-line leaf rows; status shown as a faint "alive" row wash
  instead of per-row dots (exited rows dim + keep the "exited" badge).
- Host is now the first level of nesting (host → label path → session); the
  inline @daemon is dropped from rows.
- Flush, borderless search input spanning the sidebar; drag-to-resize grip on
  the sidebar edge (width persisted to localStorage); narrower default.
- Per-row clone action (opens the spawn modal prefilled); right-pointing
  chevron when collapsed.

Chrome / layout
- Buttons swapped to a bracket terminal style ([ + Spawn ]); ghost/icon drop
  the brackets. Old filled styles removed.
- Squared corners globally (radius tokens) and tighter, denser spacing.
- Session tally + daemon chips moved into the centered app header.
- Shorter control height; shared --bar-h aligns the search and attach bars.

Attach / terminal
- Useful attach title (name · command @ host) over a bare UUID.
- Robust fit: compute columns from the viewport's scrollbar-excluded
  clientWidth (fixes mobile under-fit and desktop overflow); ResizeObserver +
  font-load refit; full-bleed terminal on mobile.

Spawn / empty states
- Spawn modal accepts a prefill (clone); real "+ Spawn" buttons in welcome and
  no-sessions empty states.

Dev
- New ?kit design-kit page (kit.ts/kit.css, DEV-only) showcasing components.
- Richer ?mock fixture with deeper label nesting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 12:11:43 +00:00
naps62-yoloandClaude Opus 4.8 4c49abfdf2 feat(maestro): web UI persistent sidebar + content split w/ background connection pool (NAP-338)
ci / rust (push) Successful in 5m24s
Replace the full-screen route-swap SPA with a persistent two-pane layout:

- Global top bar (brand + Spawn + theme) above a sidebar (search + status +
  session tree, highlights the active session) and a content pane (welcome or
  the attached terminal). Selecting a row swaps only the content pane; the
  sidebar and its store subscription persist.
- New `connections.ts` ConnectionPool: attach connections (terminal + WS) are
  decoupled from the UI — navigating away parks a session (keeps it streaming)
  rather than tearing it down, so switching back is instant with scrollback
  intact. LRU-bounded (MAX_LIVE=6); explicit `⏻` disconnect distinct from nav.
- `attach.ts` → mountable `createAttachConnection`; `catalog.ts` exposes
  `renderTopbar` + `renderSidebar`; `main.ts` is the shell + hash router.
- Responsive: desktop side-by-side; mobile single-pane (list ↔ terminal).
  Single session viewed at a time (split panes deferred).

Typecheck + production build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:49:16 +00:00