A newer daemon's unknown activity variant failed the whole Session
deserialization on older clients. Hand-written Deserialize maps any
unknown string to Idle; non-string values still error. (#110)
Line-1 actions anchor to the card's top-right corner on the highlighted
row only, reserving their width so the name truncates with an ellipsis
before it can ever reach them.
Reversed video made a highlighted card unreadable. Cards now sit on a
black card background painted edge to edge; the highlighted card swaps it
for black-on-cyan, matching the status bar's focus treatment.
The activity pass now sets activity_since only when the decided
ActivityState differs from the stored one; foreground/cwd-only diffs and
steady-state ticks leave the clock alone, so the sidebar duration counts
time in the current state instead of reading zero.
Daemon restarts still reset the clock to the restart time (stated in
recover()); the daemon cannot know how long a recovered session had
already been idle.
Per docs/SIDEBAR.md: line 2 carries the activity dot + floored duration in
the state's colour, plus the blocked-work and exited fragments; it appears
only when time-in-state exceeds 30s, work is blocked, the session is dead,
or an Error state is in force. Blocked work moves off line 1. Card lines
truncate with an ellipsis instead of wrapping.
Every tmux-spawned session now gets its UUID under MAESTRO_SESSION_ID and,
when the runner has wired its base URL from [daemon].bind_addr, that URL
under MAESTRO_URL (#103). The daemon-owned values override anything the
spawn request carried. Adopted sessions stay without them by design.
Pressing Spawn while reading a session split that pane in half. It now
opens the draft in its own tab, leaving the current tab untouched.
Scoped to the explicit Spawn affordances (the header button and the
sidebar empty state, both via OPEN_SPAWN_EVENT). "Spawn similar" and the
fast-forward key deliberately keep splitting: both act on the session in
front of you, so a pane beside it is the point. Reusing an empty pane
(#72) is unchanged — that path never split anything.
Closes#100
The sidebar nested a worktree session under the worktree, so a repo grew
a <repo>/worktrees/<branch> folder holding one session of the same name,
and the repo stopped being where you look for the work.
The daemon recorded whatever base_repo the request named, which is the
worktree itself when spawning from inside one; it now records the
resolved main checkout. The web tree groups by that instead of the cwd.
The TUI sidebar is a flat list with no directory grouping, so it needs
no mirror.
Closes#99
The runner container is root with no `nixbld` group. The installer
writes `build-users-group = nixbld` regardless and then fails on it:
error: the group 'nixbld' specified in 'build-users-group' does not exist
Writing the config first (and again after, in case the installer
replaced it) clears that and enables flakes in the same place.
Refs #98
Two defects in the job as landed, both invisible locally:
- The multi-user installer needs systemd, which the act_runner container
does not have. Single-user install instead.
- The installer does not enable flakes, so `nix build .#…` fails on a
fresh install. A machine with them in nix.conf builds fine, which is
why this passed locally and failed in CI.
Refs #98
Closes the Spawn v2 milestone: six review findings from the first blitz
plus seven pieces of operator feedback on the draft pane, and a flake
build that had been silently broken.
Form:
- The draft form is capped and centred instead of stretching to the pane.
- The worktree section sits behind a collapsed "Advanced options"
disclosure whose header says whether a worktree will be created.
- The two controls both labelled "Branch" are now "New branch name" and
"Pick a branch", matching in both clients.
- Enter submits from any field; inside a picker it chooses first.
Directory picker, rebuilt in both clients from one written set of rules:
- Projects are boxes with full paths; worktrees are a second step that
appears once a project is picked, with "+ new" as the default.
- Picking an existing worktree runs in it and leaves the create-worktree
toggle off — the daemon never adopts, so running in a tree and owning
it stay separate acts (docs/WORKTREES.md).
Fixes: the fast-forward spawn key never matched a shifted 'S'; openDraft
never reused an empty pane and dropped its seed; swapping draft panes
moved the data but not the forms; the gitroot probe fired per keystroke;
the projects rescan ignored its own TTL; a late /api/info reset a picked
preset; and CI now builds the flake packages, which had been failing
since the pnpmDeps hash went stale.
Matches the TUI's WorktreeToggle, whose Enter also toggles rather than
submitting — otherwise the two clients would disagree on this one
field despite #97 requiring the same rule everywhere else.
DirPicker mirrors the web filter/picker split: typing narrows and the
next Enter submits with the highlighted project directly (nothing was
chosen yet); an explicit Up/Down move onto a different row is
mid-selection, so Enter there only chooses it and a further Enter (now
the active pick) submits. WorktreeChoice, BranchExisting, Preset and
BranchMode already apply their highlight eagerly on Up/Down/Left/Right,
so Enter always submits there. Advanced and WorktreeToggle keep
activating on Enter, matching the web form's own button controls.
The draft pane's new-branch gutter was labelled "br:" in both branch
modes — since #94's web fix now says "New branch name" / "Pick a
branch", mirror the same distinction here: "new:" for the branch-name
input, "pick:" for the existing-branch list (which previously had no
label at all).
The new-branch name input and the existing-branch picker both had a
literal <label>Branch</label>, on top of "New branch"/"Existing branch"
mode radios — reading the form gave no clue what the field wanted.
Kept the mode radios (they name the choice, not the field) and gave
each field its own label: "New branch name" for the text input,
"Pick a branch" for the select.
A project-box or worktree-choice picker button that isn't yet the
active pick takes the Enter to choose it instead; the next Enter, once
it's active, submits. The project filter resolves to the top filtered
project rather than sending whatever cwd was last set. The raw-cwd
autocomplete's own Enter (accept the highlight) keeps working via
e.defaultPrevented.
Keeps both #93 and #96 in the draft layout: the worktree toggle and body
stay behind the Advanced disclosure, while the worktree choice step
belongs to the directory picker (#95 rule 3) and stays visible once a
project is picked.
Closes#96
Mirrors the web project-picker redesign (#95) in the TUI (#96): step one
lists one box per repository (name, full path); picking one reveals step
two — "+ new" then that repository's worktrees newest-first, defaulting
to "+ new" except on a box that is itself a linked worktree. "+ new"
creates (cwd is the repo, create-worktree on); an existing worktree runs
in it (cwd is that worktree, create-worktree off). Neither step probes
/api/gitroot — only the free-text "Other directory…" hatch does.
Replaces the flat indented project+worktree list from #91/#64 with
projects::group_projects-backed DraftGroup boxes; the filter still
matches a repository via its own name/path or any of its worktrees.
Mirrors the web spawn form: worktree/branch controls sit behind a new
"Advanced options" disclosure line (bracket [+]/[-], toggled by Enter
or Space, closed by default) instead of always showing the checkbox
and branch rows. The header states whether a worktree will be created
so #91's off-by-default for a worktree row is visible without
expanding. Collapsed fields drop out of Tab order; expanding only adds
rows below the disclosure, never moving the ones above it.
Closed by default behind a disclosure reusing the sidebar folder-header
affordance (catalog.ts), since most spawns take the default target and
the section was the largest block in the form. The collapsed header
states whether a worktree will be created, so #91's off-by-default for
a worktree row stays visible without expanding.
Fixes a latent [hidden] + display:flex bug for the new .wt-collapsible
element (author CSS beats the UA [hidden] rule at equal specificity);
.wt-body has the same bug pre-existing and is out of scope here.
A web/pnpm-lock.yaml change invalidates the pinned pnpmDeps.hash in
flake.nix silently; nix build .#maestro-web then stays broken until
someone notices by hand (#98). Add a flake job that installs nix and
builds the packaged outputs on every CI run.
The working-directory picker was a flat list mixing repositories with
their worktrees, each row repeating the project name as an abbreviated
path. It is now two steps: a grid of project boxes carrying the full
unabbreviated path, then — only once a project is picked — that
repository's worktrees with "+ new" as the default.
"+ new" creates a worktree; picking an existing one runs in it and
leaves the create-worktree toggle off, since the daemon never adopts
(docs/WORKTREES.md). That is #91's rule, now an explicit choice rather
than an inference from which row was clicked.
Also fixes `.raw-wrap[hidden]`: `display: flex` outranked the UA sheet's
`[hidden]`, so the free-text cwd input showed even when hidden.
The interaction rules are listed in spawnForm.ts for the TUI mirror (#96)
to follow.
Refs #95
Typing in the raw path field fired one /api/gitroot probe per
keystroke (#74). The draft now schedules the probe 250ms out — matching
GITROOT_PROBE_DEBOUNCE_MS in web/src/spawnForm.ts — and every keystroke
resets it; the 30Hz heartbeat polls for due probes so the burst's one
request fires after typing stops. The seq guard in resolve_gitroot is
untouched, so a late answer still cannot overwrite a newer input.