Commit Graph
367 Commits
Author SHA1 Message Date
Miguel Palhas af8acbfbf2 test(maestro): verify umbrella binary routes each subcommand to its crate
ci / web (push) Successful in 1m10s
ci / rust (push) Failing after 3m17s
ci / flake (push) Successful in 7m6s
Closes #10

The compile_error guard and per-feature build already had CI coverage
(the "feature-gate matrix" job). What was missing was proof that a
subcommand actually reaches its own crate's dispatch at runtime, not
just that clap parses it.

A malformed --config file makes cli/daemon/tui fail identically at
config load, before touching tmux, the network, or a terminal. That
turns "clap parsed it but the handler never ran" (a usage error, exit
2) into a distinguishable outcome from "it dispatched into the real
crate" (a `toml decode` runtime error via main's error printer, exit
1). The absence-side tests are feature-gated to only exist in a
single-feature build, so they run under CI's existing per-feature
matrix rather than the default all-features build.
2026-09-04 19:47:32 +01:00
Miguel PalhasandClaude Sonnet 5 49ab7e9c81 fix(maestro): suffix branch name on new-branch worktree collision
ci / rust (push) Failing after 3m26s
ci / web (push) Successful in 43s
ci / flake (push) Successful in 3m54s
git worktree add -b fails outright when the branch already exists, even
if the target directory is free. A random fast-forward handle commonly
collides with a branch an earlier, already-removed worktree left behind.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 13:29:49 +01:00
Miguel PalhasandClaude Opus 5 706942ddd7 feat(maestro): install activity hooks for codex too
ci / web (push) Successful in 51s
ci / flake (push) Successful in 3m52s
ci / rust (push) Successful in 6m47s
The hook installer only ever wrote ~/.claude/settings.json, so a codex
session reported no activity and never posted its first prompt — leaving
it on the daemon's generated handle with no haiku name.

Adds a Target enum (claude-code, codex) over the hook file's location and
event set. One script and one marker serve both; codex swaps Notification
for PermissionRequest and has no StopFailure, so it never reports Error.
Both agents spell the prompt event UserPromptSubmit and put the text in
the payload's `prompt`, so autoname needs no change.

Codex will not run a hook it has not been told to trust; `maestro hooks
install` says so, and the approval stays the operator's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 10:44:14 +01:00
Miguel Palhas 8325bc6d9a Merge remote-tracking branch 'origin/main' into blitz/command-bar
ci / web (push) Successful in 37s
ci / flake (push) Successful in 2m52s
ci / rust (push) Successful in 5m44s
2026-09-03 22:58:41 +01:00
Miguel PalhasandClaude Opus 5 8f499dbee3 fix(maestro): arbitrate pane size across clients
ci / web (push) Successful in 52s
ci / flake (push) Successful in 2m35s
ci / rust (push) Successful in 5m57s
Every attached client pushed its own dimensions straight to
`tmux resize-window`, so two clients of different sizes resized the
shared window past each other on every report — 198 resizes in 90
seconds on a four-session daemon. Each flip repaints a full-screen agent
at a new height and orphans the rows it drew at the old one, leaving a
stale input box the operator types into and reads as a stuck pane.

The window now goes to the smallest attached client per axis, tmux's own
rule for multiple clients, and only when that size actually changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 22:28:40 +01:00
Miguel Palhas ba02b11f13 feat(maestro): serve the configured leader to the web client
The TUI read its leader from `[tui].leader`; the web used `DEFAULT_LEADER`,
a module constant. Rebinding one left the other on `ctrl-a`, so the two
escape hatches hanging off the leader (`leader ctrl-k`, `leader leader`)
moved on one client and not the other.

`GET /api/info` now carries `leader`. The web binds that when it arrives and
rebuilds the leader-owning `CommandSet`, so the `?` overlay and the command
bar render the configured chord; an absent or empty field — an older daemon,
or an unreachable `/api/info` — leaves it on `ctrl-a`.

Chose the existing `[tui].leader` served as-is over a new shared key: one
knob, no precedence rules between two spellings, and no migration for
operators who already set it. The name is a wart — it is the leader for every
client now, not the TUI's — and `docs/COMMAND-BAR.md` and `docs/INSTALL.md`
say so.
2026-09-03 18:33:01 +01:00
Miguel PalhasandClaude Sonnet 5 331039df11 fix(maestro): leader twice sends one literal leader byte
ctrl-a is consumed as the leader on every press, so it never reaches the
shell and readline's beginning-of-line was gone with no way back — unlike
Ctrl-K, which already has an escape hatch. Fix it the way tmux does:
pressing the leader twice sends the literal leader byte to the focused
pane and clears the pending chord, on both web and TUI, built from the
configured leader so a rebound leader carries it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 18:14:11 +01:00
Miguel Palhas c1c283c0f8 feat(maestro): give readline's kill-to-end-of-line back via the leader
ctrl-k now opens the command bar in both modes (#153), taking readline's
kill-to-end-of-line away from every pane. Leader then ctrl-k — ctrl-a
ctrl-k by default — sends the literal 0x0B byte to the focused pane
instead, built from the configured leader so rebinding it rebinds the
chord too.

Chording a second binding off the leader exposed a gap in ChordState:
a key bound both alone and as a chord prefix (ctrl-a alone, and ctrl-a
ctrl-k) would get stuck waiting on the next keystroke, discarding its
own meaning the moment that keystroke didn't continue the chord. Feed
now resolves the held prefix to its own binding when that happens and
hands the breaking key back via take_replay for the caller to process
fresh — otherwise, pressing leader then any other key would never
leave Insert mode again.
2026-09-03 17:55:50 +01:00
Miguel PalhasandClaude Opus 5 1b260b5c20 feat(maestro): drill into a session's actions with tab
`tab` on a highlighted session opens its action page inside the command
bar: kill, rename, spawn similar, still fuzzy-searchable, with the
overlay titled after the session. Escape or backspace at an empty query
returns to the result list with the previous query intact.

Each action raises the flow the TUI already has — `KillConfirm`, the
rename prompt pre-filled, and the spawn form, now seeded from that
session's directory and command rather than the focused one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 17:35:24 +01:00
Miguel Palhas 9d6b69cc49 feat(maestro): add the TUI command bar's > and ~ sigils
`>` narrows to commands from the live keymap for the current mode, matched
on the action's help_label only — the chord is displayed, never matched, so
results cannot depend on a user's rebinds. Rows dedupe by action, not by
chord: the default map binds both `enter` and `tab` to AttachHighlighted,
and AttachHighlighted and ToggleAttachHighlighted share a label because they
are the same match arm.

`~` narrows to recent dirs plus the project list. Enter takes the
fast-forward path pinned to that directory, with no recency fallback, so a
directory outside the project scan opens the pre-filled form rather than
spawning elsewhere.

Both fall out of parsing the query line, as in web/src/commandBar.ts: a
sigil is only a sigil in first position, and backspacing past one is just
the query becoming mixed again.
2026-09-03 17:17:27 +01:00
Miguel Palhas dc783778c8 feat(maestro): add the ctrl-k command bar to the TUI
Opens from Normal and Insert mode as an ordinary keymap entry, so it is
rebindable and the help overlay picks it up from the live keymap. An
empty query lists sessions most recently focused first, matching the web
bar's `orderSessionsForBar`; typing ranks through `matcher.rs` with the
matched ranges highlighted, grouped under kind headers.

Enter attaches through the same path the sidebar's attach uses, Escape
closes and leaves focus and mode where they were.
2026-09-03 16:42:21 +01:00
Miguel Palhas 83741d7203 feat(maestro): add Rust match-and-rank matcher for command bar
Ports the docs/COMMAND-BAR.md scoring algorithm (dynamic program over
query/text positions, word-boundary bonuses, whole-field adjustment,
four-level tie-break) to crates/maestro-tui, matching web/src/matcher.ts.
Fixtures F1-F12 from the doc are ported as tests, asserting order,
scores, field and ranges.
2026-09-03 16:27:11 +01:00
Miguel Palhas b618b7dd27 merge origin/blitz/command-bar into command-bar/147-rust-matcher 2026-09-03 16:23:45 +01:00
Miguel Palhas f37ff681ea feat(maestro): fill the row for the session in the pane
ci / web (push) Successful in 34s
ci / flake (push) Successful in 2m51s
ci / rust (push) Successful in 6m54s
The web list still said "open in the focused pane" with a 1px ring, the last
edge left in it, and the TUI said it with the ● glyph alone. Both now use a
background.

Web: `--accent-fill`, the same 30% recipe `--preview-tint` uses, so the two
tiers differ by hue rather than by strength. The row rules are also rewritten
as one ordered run of equal-specificity `:where()` selectors, which is what
`docs/SIDEBAR.md` claims the ladder is — `.is-open` moves in from the bottom
of the multi-view block, where source order had been deciding its precedence
by accident.

TUI: `palette.pane_row_bg()`, blended partway from the card surface to the
selection surface, so the sidebar reads unfilled / half filled / filled. It
borrows the selection surface's foregrounds, since those are the ones picked
for a filled row, and the cursor still wins on its own row. Blended rather
than shipped as a third `Surface` — that would be eight more colours in each
of seventeen presets — with palette tests asserting it stays a visible step
from both surfaces and stays legible in every preset.
2026-09-03 15:31:44 +01:00
Miguel Palhas 9d9aef119d feat(maestro): stamp last-focused time on the session (#145)
Add Session::last_focused_at, shipped on the wire like every other
field. Both POST /api/sessions/:id/focus (focused: true) and opening
/api/attach/:id stamp it and emit SessionUpdated, so a client that
only attaches sorts the same as one that reports focus explicitly.
Losing focus does not clear it, and a never-focused session has None,
which is what lets the command bar's empty-query ordering
(docs/COMMAND-BAR.md) sort it after every session that has focused.
2026-09-03 14:05:36 +01:00
Miguel PalhasandClaude Opus 5 6075a177f5 perf(maestro): drop the sidebar preview delay
The pane waited 150 ms on hover and 60 ms on a keyboard step before
following the sidebar highlight, so every preview paid for the fast walk
it might have been. Replace both with a leading-edge 120 ms gap: the
first move shows at once, a burst is coalesced to its start and its end.

The row's own hover no longer eases either — `background` was on a 120 ms
transition, which is what read as lag on the row itself.

The sidebar also rebuilt its whole tree on every `SessionUpdated`, a
couple of times a minute per live session, dropping `:hover` from the row
under the pointer. Refreshes that change nothing structural now patch the
activity rail and meta line into the standing rows instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 13:36:51 +01:00
Miguel Palhas f443f625a8 Merge remote-tracking branch 'origin/main' into coda
ci / web (push) Successful in 30s
ci / flake (push) Successful in 2m48s
ci / rust (push) Successful in 6m3s
2026-09-03 13:21:53 +01:00
Miguel PalhasandClaude Opus 5 655b7b790e fix(maestro): let Escape reach the session in the TUI
Insert mode bound `esc` to LeaveInsert, so full-screen apps in a session
(vim, Claude Code) never saw Escape — the only way out of Insert is now
the leader, which rebinding still follows.

A key bound to `Noop` in Insert mode is also forwarded to the pty now.
`Noop` documents itself as the way to suppress a default binding, and in
Insert mode an unbound key belongs to the session, so this is the escape
hatch for taking any other default back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 11:32:46 +01:00
Miguel Palhas 582a7ca888 Merge remote-tracking branch 'origin/main' into oboe
# Conflicts:
#	web/src/catalog.ts
2026-09-03 11:11:40 +01:00
Miguel Palhas 6635cff46e feat(maestro): preview the highlighted session in the TUI pane
The sidebar highlight now shows its session in the pane after a 120ms
debounce, attaching if nothing already is, without the tab taking it on
and without reporting focus to the daemon. Preview-only attaches stay
warm behind the current one, bounded at three.

docs/PREVIEW.md.
2026-09-03 10:46:45 +01:00
Miguel Palhas 05e7293af2 Merge remote-tracking branch 'origin/main' into accordion
ci / web (push) Successful in 32s
ci / flake (push) Successful in 2m45s
ci / rust (push) Successful in 6m3s
2026-09-03 10:34:09 +01:00
Miguel PalhasandClaude Opus 5 b1527f5fc1 fix(maestro): align TUI and web shortcuts
Spawn was `s`/`S` in the TUI and `n`/`N` in the web client; the TUI now
uses `n`/`N` too. The web sidebar gains `c`, `r` and `x` — collapse,
rename, kill — which the TUI already bound.

docs/SIDEBAR.md now carries the shared table, so a new binding has one
place to land on both clients.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 08:25:35 +01:00
Miguel Palhas 7eeb43774a feat(maestro): clear AwaitingInput after 1s of focus
ci / web (push) Successful in 30s
ci / flake (push) Successful in 2m32s
ci / rust (push) Successful in 5m35s
Was 5s, which felt slow on a pane the operator is already looking at.
2026-09-03 08:23:36 +01:00
Miguel PalhasandClaude Opus 5 b36d571b21 perf(maestro): buffer TUI output and skip unchanged frames
ci / web (push) Successful in 32s
ci / flake (push) Successful in 2m19s
ci / rust (push) Successful in 5m26s
The TUI repainted on every 33ms tick whether or not anything had
changed — measured at ~725 bytes/s of escape sequences with nothing
attached and nothing happening. Over a remote terminal that is constant
traffic for no visible change.

Output was also unbuffered: `CrosstermBackend::new(std::io::stdout())`
issues one `write!` per cell run, 3551 of them for a 200x50 repaint,
each taking the global stdout lock. A BufWriter collapses a frame into
one flush (measured: 3551 writes -> 2).

Frames are now keyed on the catalog version, the active pane's screen
revision, an input epoch and the terminal size, and skipped when the key
is unchanged. Session cards carry an advancing duration, so a frame
still never stands for longer than a second.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 08:11:17 +01:00
Miguel PalhasandClaude Opus 5 b163f24f63 fix(maestro): reinstall hooks when an older set is on disk
ci / web (push) Successful in 42s
ci / flake (push) Successful in 2m41s
ci / rust (push) Successful in 5m41s
The daemon's auto-install skipped on presence, so a hook installed
before #131 never gained UserPromptSubmit and no session was ever named
after its prompt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:28:26 +01:00
Miguel Palhas 0babde197d Merge branch 'fix/tui-daemon-url-resolution'
ci / web (push) Failing after 6s
ci / flake (push) Successful in 2m36s
ci / rust (push) Successful in 5m13s
2026-09-02 21:09:44 +01:00
Miguel PalhasandClaude Opus 5 9a48bf51f1 fix(tui): resolve daemon URL from config
The TUI arm of the umbrella binary hardcoded http://127.0.0.1:7070 instead
of going through MaestroConfigFile::resolve_daemon_url, which every CLI
subcommand uses. On a host whose [daemon].bind_addr is not the default port,
`maestro list` worked and `maestro tui` could not connect.

RunConfig.daemon_url is now the override rather than the final URL; the TUI
resolves it against the config file it already loads for its UI settings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:07:54 +01:00
Miguel Palhas 9dad3e6472 Merge remote-tracking branch 'origin/main' into animato
ci / web (push) Successful in 22s
ci / flake (push) Successful in 4m47s
ci / rust (push) Successful in 5m59s
# Conflicts:
#	crates/maestro-daemon/Cargo.toml
#	crates/maestro-daemon/src/local_api.rs
#	crates/maestro/Cargo.toml
2026-09-02 18:37:53 +01:00
Miguel PalhasandClaude Opus 5 9a8f666975 perf(maestro): disable Nagle on the daemon listener
Keystrokes and PTY echo are tiny frames. With Nagle on, each can sit up to
40ms waiting for an ACK before the kernel sends it — invisible over
loopback, plainly felt from another device on the LAN.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 18:23:28 +01:00
Miguel PalhasandClaude Opus 5 4fc90aabfb feat(maestro): serve the web UI from the daemon
The browser reached the daemon through maestro-web, a second binary that
served the SPA and reverse-proxied /api/* — two TCP connections and a
WebSocket frame re-encode on every keystroke and every byte of PTY output.
On one machine that hop buys nothing.

The daemon now embeds the bundle and serves it off its own listener, behind
a default-on `web` feature. `--no-default-features` builds without pnpm or
node; MAESTRO_WEB_DIST still takes a prebuilt bundle.

The router's fallback answers an unmatched /api/* with 404 rather than the
SPA shell, so a client expecting JSON gets a status it can read instead of
HTML.

Removes maestro-web and the [web] config section. An existing [web] block
is ignored, not rejected. The installer deletes a leftover
maestro-web.service on upgrade.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 18:23:18 +01:00
Miguel Palhas d69107b514 Merge remote-tracking branch 'origin/main' into haiky
ci / web (push) Successful in 1m14s
ci / rust (push) Successful in 4m28s
ci / flake (push) Successful in 6m52s
# Conflicts:
#	crates/maestro-daemon/src/session.rs
#	crates/maestro-protocol/src/lib.rs
#	crates/maestro-protocol/src/routes.rs
2026-09-02 18:18:12 +01:00
Miguel Palhas 7dcd131700 fix(maestro): isolate and widen the naming run
Skipping the operator's MCP servers, settings and hooks takes a measured
naming run from 12s to 7s, so the timeout is a margin rather than a race.

Refs #131
2026-09-02 18:13:42 +01:00
Miguel Palhas fa7d5fb505 Merge remote-tracking branch 'origin/main' into activity
ci / web (push) Successful in 39s
ci / rust (push) Successful in 3m45s
ci / flake (push) Successful in 4m16s
2026-09-02 18:08:00 +01:00
Miguel Palhas bd252fc954 feat(maestro): TUI spawn form stops naming sessions
The name field starts empty behind a hint, ^R reroll is gone, and the
fetched handle seeds the worktree branch instead. Fast-forward spawns with
no name at all. A card whose name the daemon generated renders muted.

Refs #131
2026-09-02 18:05:05 +01:00
Miguel Palhas 66f386b87b Merge remote-tracking branch 'origin/main' into activity 2026-09-02 18:03:40 +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 PalhasandClaude Sonnet 5 aed9a5eec0 feat(maestro): AoE-style bare q/tab/esc keybinds in the TUI
Normal mode only exists while the sidebar is focused (Insert mode always
pairs with Focus::Pane), so bare `q` and `tab` there never reach a pty:
`q` quits, `tab` focuses the highlighted pane (same as Enter). `esc` is
added as a second, fixed way out of Insert alongside the leader.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 17:58:24 +01:00
Miguel Palhas e7b323b412 feat(maestro): name a session from its first prompt
POST /api/sessions/:id/prompt claims a session's one naming attempt and runs
claude -p --model haiku in the background; the answer is reduced to a short
slug and replaces the generated handle, settling the session at Haiku. A
chosen name, a rename mid-run, a missing binary or a timeout all leave the
handle alone. The prompt goes to the child on stdin and is never logged.

Refs #131
2026-09-02 17:57:17 +01:00
Miguel Palhas 268dbb328d feat(maestro): first-prompt hook behind haiku-autoname
Adds a UserPromptSubmit entry that forwards the hook payload verbatim to
/api/sessions/:id/prompt, gated by a haiku-autoname feature that the daemon,
the CLI and the umbrella binary all forward. With it off no hook points at
the route.

Refs #131
2026-09-02 17:53:32 +01:00
Miguel Palhas 3dee4a4e47 feat(maestro): daemon assigns the session handle
The spawn form no longer prefills a name, so create() fills one in: a
requested name is marked User, an absent one gets a generated handle marked
Generated. Rename always settles to User.

Refs #131
2026-09-02 17:49:09 +01:00
Miguel Palhas 13903aa601 feat(maestro): name-source metadata and prompt route types
Adds NameSource (User/Generated/Haiku), the metadata keys behind it, the
POST /api/sessions/:id/prompt body, and display helpers both sidebars will
read to decide whether a name is provisional.

Refs #131
2026-09-02 17:46:58 +01:00
Miguel Palhas e12baa23c6 fix(maestro): fast-forward spawn prefers the focused session's project
ci / flake (push) Successful in 10m50s
ci / web (push) Successful in 1m6s
ci / rust (push) Successful in 5m17s
N picked the globally freshest project by git-mtime, ignoring the session
the operator was actually on. Plain n's spawn form already seeds from the
focused session (spawnSimilarProjectSeed); fast-forward now matches that,
falling back to recency only when nothing is focused.
2026-09-02 17:36:11 +01:00
Miguel PalhasandClaude Sonnet 5 c8ab4a531e fix(maestro): tool icon left of diff stat on sidebar meta line
ci / flake (push) Successful in 9m17s
ci / web (push) Successful in 1m9s
ci / rust (push) Successful in 3m55s
Meta line fragment order was command last, after the blocked-work diff
stat, so the tool icon sat right of the diff count instead of left.
Reorder to duration/detail/command/diff-stat in both web and TUI
sidebars per docs/SIDEBAR.md, and update the contract doc + TUI tests
to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 17:17:44 +01:00
naps62 21fc20b30a Merge pull request 'docs(maestro): replace the sidebar's activity dot with a rail' (#130) from indicators into main
ci / web (push) Successful in 3m40s
ci / rust (push) Successful in 5m49s
ci / flake (push) Successful in 6m12s
Reviewed-on: #130
2026-09-02 17:00:58 +01:00
Miguel Palhas abbd15a8b2 feat(maestro): TUI sidebar activity rail
ci / web (pull_request) Successful in 1m20s
ci / rust (pull_request) Successful in 5m47s
ci / flake (pull_request) Successful in 9m14s
Leads the existing indent gutter on both the name row and the meta
row with a one-column block glyph in the state's palette role,
replacing the meta line's inline dot. Folder headers gain a matching
blank column so labels stay aligned under session names.
2026-09-02 16:47:51 +01:00
Miguel Palhas adbf57fc49 Merge remote-tracking branch 'origin/main' into colors
ci / web (push) Successful in 37s
ci / rust (push) Successful in 4m19s
ci / flake (push) Successful in 7m3s
# Conflicts:
#	web/src/catalog.ts
#	web/src/styles.css
2026-09-02 16:31:58 +01:00
Miguel PalhasandClaude Fable 5 bff0eddc12 feat(maestro): graphite TUI default + five accent presets
Replaces the army-green dark/light palettes with neutral graphite and
adds ice-cyan, periwinkle, amber-phosphor, ember and orchid presets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 16:06:31 +01:00
Miguel Palhas 8cb6aeb61e Merge branch 'flugelhorn'
ci / rust (push) Successful in 5m20s
ci / web (push) Successful in 2m11s
ci / flake (push) Successful in 11m49s
2026-09-02 15:58:48 +01:00
Miguel Palhas 247060de68 feat(maestro): open the spawn form on the project picker
The picker now leads the form in both clients and takes focus on open, so
Tab walks from it into the command tiles. Opening the form also preselects
the project of the session you were last on — a worktree session seeds its
base repository — so 'n' then Enter spawns another session there.
2026-09-02 15:56:35 +01:00
Miguel PalhasandClaude Opus 5 29545d15fe feat(maestro): daemon status indicator and restart overlay in the TUI
ci / flake (push) Successful in 9m3s
ci / web (push) Successful in 1m22s
ci / rust (push) Successful in 4m25s
The status bar gains the TUI's half of the same contract the web client got:
a coloured dot plus the release the daemon is running, pinned right so a narrow
terminal truncates the key hints rather than the release.

`DaemonLink` mirrors web/src/daemonLink.ts — the events loop reports its
connects and disconnects, a probe task polls `GET /api/info` while the daemon
is unreachable, and past the grace window a modal covers the frame and locks
the keyboard (quit excepted) until it answers. A daemon that comes back on a
new commit cannot be reloaded into a running TUI binary, so the status bar says
`updated: <hash> (restart tui)` instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 15:52:44 +01:00