feat(maestro): tile-first web spawn form — host/command tiles, recents, worktree-by-branch, generated name (#27)
ci / rust (push) Successful in 4m29s

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>
This commit is contained in:
2026-06-20 21:22:25 +00:00
co-authored by Claude Opus 4.8
parent f38b948aa6
commit b3ef2e543e
8 changed files with 792 additions and 137 deletions
+30
View File
@@ -0,0 +1,30 @@
// Client-side session-handle generator for the spawn form's instant reroll
// (#23/#27). Mirrors the wordlists in `crates/maestro-names/src/lib.rs` verbatim
// — the Rust generator is the source of truth, but the web wants a zero-round-
// trip reroll, so the lists are duplicated here. Keep them in sync.
/** Musical adjectives — tempo, dynamics, and articulation markings. */
const ADJECTIVES = [
"allegro", "andante", "adagio", "presto", "vivace", "largo", "grave", "lento",
"moderato", "crescendo", "staccato", "legato", "forte", "dolce", "agitato",
"cantabile", "sostenuto", "tremolo", "rubato", "marcato", "brillante",
"maestoso", "grazioso", "espressivo", "tranquillo", "animato", "spirito",
"sotto", "vivo", "feroce",
];
/** Musical nouns — instruments and forms from the concert hall. */
const NOUNS = [
"cello", "violin", "viola", "oboe", "flute", "clarinet", "bassoon", "trumpet",
"trombone", "tuba", "horn", "timpani", "harp", "piano", "organ", "lyre",
"lute", "fugue", "sonata", "octave", "chord", "cadence", "prelude", "rondo",
"nocturne", "scherzo", "refrain", "coda", "motif", "overture",
];
function pick<T>(list: T[]): T {
return list[Math.floor(Math.random() * list.length)];
}
/** A fresh `adjective-noun` handle, e.g. `allegro-cello`. */
export function generateName(): string {
return `${pick(ADJECTIVES)}-${pick(NOUNS)}`;
}