This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/docs/superpowers/specs/2026-06-15-spawn-dir-autocomplete-design.md
T
Miguel Palhas c856333e4a
ci / rust (push) Successful in 2m40s
docs(maestro): describe the single-host architecture
Rewrites CLAUDE.md as the current-architecture reference, drops the
registry from INSTALL/PRODUCT/DESIGN and the web README, deletes the
registry Dockerfile, and marks the dated design specs superseded where
they describe the registry, JWT auth, or cross-host attach.

The installer loses its configure step — every config field now has a
default, so a daemon starts with no config file at all.
2026-08-22 10:26:49 +01:00

5.3 KiB

Spawn form: home-dir prefill + live directory autocomplete (NAP-329)

Partly superseded (2026-08-22). Maestro is now single-host — the endpoint is now GET /api/dirs with no daemon id in the path. See CLAUDE.md.

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<tab>) 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 excluded; sorted case-insensitively; capped.

Wire protocol (maestro-protocol)

  • DirListing { path: PathBuf, entries: Vec<String>, 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<DirListing> } — owner → registry.
  • DaemonOutbound::ForwardListDir { reply_to, daemon_id, path } — home daemon → registry (relay to owner).
  • DaemonInbound::ForwardListDirResult { reply_to, result: RpcResult<DirListing> } — 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<DirListing, ErrorInfo>. 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=<p> — 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=<p> → registry.list_dir, same timeout/error mapping as create_session.

Web (web/src)

  • types.ts: DirListing.
  • api.ts: listDir(daemonId, path) -> Promise<DirListing> → 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 excluded, 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.