docs(maestro): design for spawn-form dir autocomplete + home prefill (NAP-329)
This commit is contained in:
@@ -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<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 included (shell parity); 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 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.
|
||||||
Reference in New Issue
Block a user