The header carried only the brand and a session tally; app actions had
already moved to the sidebar footer. The mobile ☰ it hosted moves to the
pane header (replacing the redundant back arrow) and to the welcome
state, so the session drawer stays reachable without a swipe. The DEV
`?kit` link moves to the sidebar footer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bar has nothing to switch between with one tab, so it is pure chrome. It reappears as soon as a second tab exists; spawn already opens its own tab, so the "+" button stays reachable.
Below 860px the layout no longer grows a second navigation surface. The
pane pill strip is gone, the tab bar stays collapsed however many tabs
exist, and opening or spawning takes over the current pane instead of
splitting beside it or opening a tab. The sidebar drawer is the only way
to move between sessions.
The tree itself is untouched, so a desktop-built layout comes back when
the window widens again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Session header used to print the raw command line ("claude"); mirror
the sidebar's icon-for-program treatment so both agree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Help, theme toggle, and Spawn now live pinned to the bottom of the
sidebar instead of the top-right app header, keeping them next to the
session list they act on.
Focused sidebar rows and multiview panes drew a green border/ring.
Swap to a background tint on the row and the pane titlebar instead,
so focus stays borderless.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Line 2 of a session card spelled out its blocked work ("1 uncommitted
file") and its command ("claude"). Both now render as marks: the
breakdown as diff notation, and the command's leading program as its
tool's icon in the web client.
The probe carries the line delta of the tracked changes — staged and
unstaged together, against HEAD — so `git add` never makes the counter
drop. Untracked files stay a file count; counting their lines means
reading every one off disk. A tracked change with no line delta falls
back to `~N` rather than claiming `+0 -0`.
The TUI keeps its command as text: a terminal cannot be assumed to have
a font carrying tool glyphs.
The sidebar row dot, the pane top strip and the mobile pill dot all
painted a hashed per-session colour; the focused pane added an outer
accent glow on top of its ring. Removes the colour concept entirely
along with the glow, leaving the flat accent ring as the only focus mark.
`?` toggles a dialog listing every web binding, mirroring the TUI's help
overlay. A `?` button in the header covers pointer-only clients, and the
welcome screen points at the key.
One registry (SHORTCUTS) drives the rendering, so a binding added without
an entry reads as missing. main.ts and the overlay now share one
isTypingTarget guard instead of two copies.
Removes the retained-worktree store end to end: the daemon's state-dir
store, GET/DELETE /api/worktrees, `maestro worktrees list|reclaim`, the
TUI overlay behind `w`, and the web `#/worktrees` view and header link.
The kill guard stays. A forced kill and a dirty natural exit now leave
the worktree on disk untouched instead of parking it in a store, so
removing one is an ordinary `git worktree remove`. A pristine tree is
still removed on exit. DELETE /api/sessions/:id always answers 204.
The header's debug button also goes; Ctrl+Shift+D and `?hud` still
toggle the HUD.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Right-clicking a card opens a custom context menu (rename, spawn similar,
kill). Rename swaps the name for an inline field; Enter commits, Escape and
blur cancel. The tree stops rebuilding while the field is open, so a catalog
delta cannot replace what is being typed into.
Refs #125
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
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
The form filled the full pane width, so on a 4K display every input
stretched thousands of pixels wide. Bound it to 60ch (in line with the
48ch/42ch precedents already in the type scale) and centre it with
margin auto; panes narrower than the cap still fill edge to edge.
The draft pane covers every field the modal had; the modal was a second
code path for the same job. The header Spawn button, sidebar empty state,
welcome button, and ?mock&spawn now open a draft pane via
MultiView.openDraft — reusing an empty focused pane, else splitting it.
"Spawn similar" seeds a draft with the session's cwd + command.
Modal-only CSS (backdrop, sheet/dialog chrome, keyframes) is deleted;
.modal-body form styling folds into .spawn-form, which also picks up the
select styling the worktree branch picker was borrowing from the modal.
- toggle defaults on for any target the project scan reports (repo or
worktree); only the free-text escape hatch pays for /api/gitroot
- branch mode control switches between creating a new branch (named
after the session name, as before) and picking an existing one from
GET /api/branches; branches held by another worktree stay visible but
disabled, labelled with that worktree's path
- emits create_branch: false on the wire for existing-mode picks
- branch-list load follows the form's ready/destroyed discipline so a
late response cannot corrupt a rebuilt draft
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.
Session tree nests by working directory only; the daemon chips in the
header become a session tally, and the spawn form loses its host picker.
API calls move from /api/daemons/:id/* to /api/dirs, /api/recent-dirs,
/api/gitroot, and /api/info.
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>
xterm's native touch scrolling crawled a line at a time and stalled after a
few. Map finger drags on the viewport to term.scrollLines (skipped on the alt
screen, which the app owns) and claim vertical pans via touch-action: pan-y.
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>
The working-directory picker showed raw absolute paths (`/home/naps62/...`),
unlike the sidebar tree which collapses home to `~` and shrinks leading
segments to initials. Reuse the sidebar's abbreviation so both read the same:
`/home/naps62/git.naps.pt/naps62/maestro` → `~/g/n/maestro`, with the full path
kept in the row tooltip and submitted verbatim.
- types.ts: hoist `abbreviateFolderName` (+ a local `escapeHtml`) here so both
the sidebar and spawn dialog share one implementation (catalog.ts imports
spawn.ts, so the helper can't live in catalog without a cycle).
- catalog.ts: import the shared helper instead of its own copy.
- spawn.ts: abbreviate each recent-dir row via `abbreviateFolderName(abbreviateCwd(...))`.
- styles.css: dim the collapsed stem in the picker too (`.recent-dir .folder-stem`).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sidebar UI tweaks from a live crit review:
- Statusbar: drop the redundant global "N sessions / N live" tally; each
daemon chip now carries its own green per-host session-count badge
(tabular-nums, dimmed when 0).
- Session tree: deep folder paths collapse leading segments to initials and
keep the trailing dir name (~/git.naps.pt/naps62/maestro -> ~/g/n/maestro),
with the stem dimmed and the full path in the tooltip.
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>
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>
Add an always-visible "debug" button to the topbar (next to the design-kit
shortcut) that toggles the debug HUD. Reflects the current state (lit in accent
when on) and stays in sync with the Ctrl+Shift+D keybind via the shared toggle
event.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A game-HUD-style debug overlay (FPS-counter for sessions) that surfaces the
daemon-inferred fields from #18 — activity, foreground, idle time, status — on
the active session, live off the existing events WS. Lets us validate the
inferred data before designing any "real" UI, and stays useful as a permanent
debugging tool.
- Off by default; toggled via Ctrl+Shift+D (persisted in localStorage) or `?hud`
in dev (debug.ts).
- Fixed bottom-right overlay, pointer-events:none so it never blocks the
terminal; activity value is color-coded (active/awaiting/idle).
- Generic: renders every non-client-owned Session field, so a new daemon-derived
field (#19 `detail`, later metrics) surfaces with no overlay change. Idle time
re-renders on a 1s ticker; values update on SessionUpdated.
- Pure consumer-side: no daemon/protocol/registry changes. The `?mock` fixture
seeds varied activity so the HUD can be reviewed without a daemon.
Closes#20
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rework web/src/spawn.ts from raw text boxes into a low-friction, tile-first
flow: pick host tile → pick command tile → pick recent dir → Spawn, name
already filled.
- Command tiles from the daemon's advertised presets (GET /api/web/info, #26),
with built-in fallback; `custom` reveals the free-text command input.
- Host tiles replace the <select> (label hostname, sub daemon_id); most-
recently-spawned-on host sorts first + pre-selected; overflow behind "More…".
- Recent-dirs picker per host (GET /api/daemons/:id/recent-dirs, #26), rows show
dir + last command, re-queried on host change; picking a dir can pre-select
its last command. Persistent "Other directory…" drops to the raw input +
NAP-329 autocomplete; empty history prefills raw with home_dir.
- Worktree by branch alone: toggle infers base_repo via /gitroot from the
chosen dir (#26); non-repo dir disables the toggle with an inline hint;
WorktreeSpec.base_repo filled client-side (wire unchanged).
- Name field prefilled with a generated cyberpunk handle (names.ts mirrors the
maestro-names wordlists); ↻ rerolls.
Adds api.ts getWebInfo/recentDirs/gitRoot, the WebInfo/SpawnPreset/RecentDirs/
GitRoot types, and stubs the new endpoints in the `?mock&spawn` review fixture.
Closes#27
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Focus alone was the wrong signal: dismissing the soft keyboard (e.g. Android
back button) keeps the terminal focused, so the key bar lingered. Detect the
keyboard's actual visibility from the visual-viewport shrink — while an editable
element is focused, a height past a threshold below the focus-free baseline
means the keyboard is up — and gate the bar on a new `html.kb-open` class in
addition to the per-terminal focus class.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The special-key bar previously showed on every coarse-pointer device, even
when the terminal was unfocused and the soft keyboard was down — which looked
out of place. Gate it on the terminal textarea's focus/blur (the same signal
that drives the keyboard) via a `keybar-active` class, and re-fit on toggle
since showing/hiding the bar changes the terminal's height.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>