From 8842147164dcae345412791437ccf06a080a6d05 Mon Sep 17 00:00:00 2001 From: naps62-yolo Date: Mon, 15 Jun 2026 21:10:57 +0000 Subject: [PATCH] docs(maestro): design for spawn-form dir autocomplete + home prefill (NAP-329) --- ...026-06-15-spawn-dir-autocomplete-design.md | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-15-spawn-dir-autocomplete-design.md diff --git a/docs/superpowers/specs/2026-06-15-spawn-dir-autocomplete-design.md b/docs/superpowers/specs/2026-06-15-spawn-dir-autocomplete-design.md new file mode 100644 index 0000000..2981262 --- /dev/null +++ b/docs/superpowers/specs/2026-06-15-spawn-dir-autocomplete-design.md @@ -0,0 +1,107 @@ +# Spawn form: home-dir prefill + live directory autocomplete (NAP-329) + +## Problem + +The web spawn form's **Working directory** field is `required` but offers no +help filling it: no reliable prefill, no completion. The user wants + +1. the field prefilled with the **selected host's home dir**, updating when the + host changes; and +2. **shell-style directory completion** (`cd dir/sub`) driven by **real-time + queries to each host's daemon**, so it works across every federated host. + +Applies to both the **cwd** field and the worktree **Base repo** field. + +## Architecture context + +The web SPA talks to a single **home daemon** (served by `maestro-web`, which +reverse-proxies `/api/*`) — or, in dev, directly to the **registry** (vite +proxy). The home daemon owns its sessions and mirrors peers' catalogs; it +**brokers** cross-daemon operations (`create`/`kill`) to the owning daemon over +the registry control link (NAP-327). The registry can also drive a daemon RPC +directly (`create_session`/`kill_session`) for its own `client_api` front door. + +Directory listing must reach **the daemon whose host you're spawning on**, which +may be the home daemon itself or any peer. So we mirror the create/kill plumbing +end to end rather than only the registry leg. + +## Design + +### Listing strategy + +The daemon lists **one directory's immediate subdirectories**; the web caches +the result per `(daemonId, dir)` and filters the trailing path segment +client-side, re-querying only when the user crosses a `/`. Fewer round-trips, +instant filtering, still live. Directories only (cwd / base-repo are always +dirs); hidden dirs included (shell parity); sorted case-insensitively; capped. + +### Wire protocol (`maestro-protocol`) + +- `DirListing { path: PathBuf, entries: Vec, truncated: bool }` — `path` + is the **resolved absolute** dir (after `~`/relative expansion); `entries` are + immediate subdirectory names. +- `DaemonInbound::ListDir { reply_to, path: String }` — registry → owner daemon. +- `DaemonOutbound::ListDirResult { reply_to, result: RpcResult }` — + owner → registry. +- `DaemonOutbound::ForwardListDir { reply_to, daemon_id, path }` — home daemon → + registry (relay to owner). +- `DaemonInbound::ForwardListDirResult { reply_to, result: RpcResult }` + — registry → home daemon. + +Carried by the existing `RpcResult` + `#[serde(other)] Unknown` forward-compat +machinery, so older peers degrade gracefully. + +### Daemon (`maestro-daemon`) + +- New pure module `dirlist`: `list_dir(home_dir, requested) -> Result`. + Expansion: empty → home; leading `~` → home; relative → joined onto home. + Reads the dir, keeps directories (resolving symlinks), sorts, caps (`MAX_ENTRIES`). + Error codes: `not_found`, `permission_denied`, `not_a_directory`, `io_error`. +- `registry_client` handles `ListDir` (calls `dirlist::list_dir`, replies + `ListDirResult`) and `ForwardListDirResult` (routes to broker). +- `broker` gains `forward_list_dir(daemon_id, path)` + `complete_forward_list_dir`, + mirroring `forward_kill` (fail-fast while disconnected, RAII pending cleanup). +- `local_api`: `GET /api/daemons/:id/dirs?path=

` — self → `dirlist::list_dir`; + peer → `broker.forward_list_dir`. Maps error codes to HTTP status. + +### Registry (`maestro-registry`) + +- `Registry::list_dir(daemon_id, path)` mirroring `create_session` (new + `pending_list_dir` map + `PendingGuard`, `complete_list_dir`); no catalog + side-effects. +- `forward_list_dir(home, reply_to, target, path)` mirroring `forward_kill`. +- `handler` dispatches `ListDirResult` → `complete_list_dir` and `ForwardListDir` + → `forward_list_dir`. +- `client_api`: `GET /api/daemons/:id/dirs?path=

` → `registry.list_dir`, same + timeout/error mapping as `create_session`. + +### Web (`web/src`) + +- `types.ts`: `DirListing`. +- `api.ts`: `listDir(daemonId, path) -> Promise` → + `GET /api/daemons/:id/dirs?path=…`. +- New `dirAutocomplete.ts`: attaches to an input + a daemon-id getter. Debounced + fetch of the current dir, `(daemon,dir)` cache, fixed-position dropdown with + arrow-nav; **Tab** completes the longest common prefix / accepts the highlighted + entry; **Enter** accepts (suppressing submit) or submits when closed; **`/`** + drills into the next level; **Esc** closes. Failures degrade to no suggestions. +- `spawn.ts`: keep host-change home-dir prefill; wire the widget onto cwd + + worktree base repo. + +## Testing + +- Protocol: serde round-trip for the four new variants + `DirListing`, and an + unknown-variant decode (already covered generically). +- Daemon `dirlist`: temp-dir listing (dirs-only, hidden included, sorted), `~` + and relative + empty expansion, and `not_found` / `not_a_directory` codes. +- Daemon `broker`: `forward_list_dir` round-trip + fail-fast-while-disconnected, + mirroring the existing forward_create/kill tests. +- Registry: `list_dir` RPC pairing + `complete_list_dir`; `forward_list_dir` + relay, reusing the create/kill in-process channel harness. +- Web: no test runner present; rely on `tsc` typecheck + manual verification. + +## Scope + +In: the full web→(home daemon|registry)→owner daemon path so completion works +for any host. Out: listing files (dirs only); fuzzy matching (prefix only); +persisting recently-used paths.