feat(maestro): live directory autocomplete in spawn form (NAP-329)
ci / rust (push) Failing after 5m3s
ci / rust (push) Failing after 5m3s
Adds api.listDir + a reusable dirAutocomplete widget (live dropdown, Tab/Enter completion, /-drill-in, per-(daemon,dir) cache) wired onto the cwd and worktree base-repo fields, with matching dropdown styling.
This commit is contained in:
+16
-1
@@ -1,4 +1,11 @@
|
||||
import type { ApiError, CreateSessionRequest, DaemonInfo, Session, Uuid } from "./types.js";
|
||||
import type {
|
||||
ApiError,
|
||||
CreateSessionRequest,
|
||||
DaemonInfo,
|
||||
DirListing,
|
||||
Session,
|
||||
Uuid,
|
||||
} from "./types.js";
|
||||
|
||||
async function jsonOrError<T>(res: Response): Promise<T> {
|
||||
if (res.ok) {
|
||||
@@ -43,6 +50,14 @@ export async function killSession(id: Uuid): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/** List one directory's immediate subdirectories on `daemonId`'s host (NAP-329).
|
||||
* `path` may be empty, `~`, relative, or absolute — the daemon resolves it
|
||||
* against its own home dir. */
|
||||
export async function listDir(daemonId: string, path: string): Promise<DirListing> {
|
||||
const url = `/api/daemons/${encodeURIComponent(daemonId)}/dirs?path=${encodeURIComponent(path)}`;
|
||||
return jsonOrError(await fetch(url, { credentials: "same-origin" }));
|
||||
}
|
||||
|
||||
export function eventsWsUrl(): string {
|
||||
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
||||
return `${proto}//${location.host}/api/events`;
|
||||
|
||||
Binary file not shown.
+16
-1
@@ -1,4 +1,5 @@
|
||||
import { createSession } from "./api.js";
|
||||
import { attachDirAutocomplete } from "./dirAutocomplete.js";
|
||||
import type { CreateSessionRequest, DaemonInfo } from "./types.js";
|
||||
|
||||
export function openSpawnModal(daemons: Map<string, DaemonInfo>): void {
|
||||
@@ -97,7 +98,21 @@ export function openSpawnModal(daemons: Map<string, DaemonInfo>): void {
|
||||
select.addEventListener("change", applyHomeDir);
|
||||
applyHomeDir();
|
||||
|
||||
const close = () => host.remove();
|
||||
// Live directory autocomplete (NAP-329) on the path fields, scoped to the
|
||||
// currently-selected daemon — re-reading `select.value` per query means it
|
||||
// tracks host changes for free.
|
||||
const baseRepoInput = host.querySelector(
|
||||
"input[name=worktree_base_repo]",
|
||||
) as HTMLInputElement;
|
||||
const autocompletes = [
|
||||
attachDirAutocomplete(cwdInput, () => select.value),
|
||||
attachDirAutocomplete(baseRepoInput, () => select.value),
|
||||
];
|
||||
|
||||
const close = () => {
|
||||
autocompletes.forEach((a) => a.destroy());
|
||||
host.remove();
|
||||
};
|
||||
host.querySelector(".close")?.addEventListener("click", close);
|
||||
host.querySelector(".cancel")?.addEventListener("click", close);
|
||||
host.addEventListener("click", (e) => {
|
||||
|
||||
@@ -1052,6 +1052,52 @@ textarea {
|
||||
border-radius: var(--r-2);
|
||||
}
|
||||
|
||||
/* ---------- directory autocomplete (NAP-329) ---------- */
|
||||
|
||||
.dir-ac {
|
||||
position: fixed;
|
||||
z-index: var(--z-toast); /* above the modal it completes within */
|
||||
margin: 0;
|
||||
padding: var(--sp-1);
|
||||
list-style: none;
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--r-2);
|
||||
box-shadow: var(--shadow-2);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-strong) transparent;
|
||||
}
|
||||
|
||||
.dir-ac-item {
|
||||
padding: 6px var(--sp-2);
|
||||
border-radius: var(--r-1);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--fs-14);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.dir-ac-item:hover {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.dir-ac-item.is-active {
|
||||
background: var(--accent-tint);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.dir-ac-more {
|
||||
padding: 6px var(--sp-2);
|
||||
color: var(--faint);
|
||||
font-size: var(--fs-12);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ---------- not-found ---------- */
|
||||
|
||||
.not-found {
|
||||
|
||||
@@ -27,6 +27,16 @@ export interface DaemonInfo {
|
||||
home_dir?: string;
|
||||
}
|
||||
|
||||
/** One directory's immediate subdirectories on a daemon's host (NAP-329). */
|
||||
export interface DirListing {
|
||||
/** Resolved absolute directory that was listed (after ~/relative expansion). */
|
||||
path: string;
|
||||
/** Immediate subdirectory names, sorted case-insensitively. */
|
||||
entries: string[];
|
||||
/** True when the listing was capped server-side. */
|
||||
truncated?: boolean;
|
||||
}
|
||||
|
||||
export interface WorktreeSpec {
|
||||
base_repo: string;
|
||||
branch: string;
|
||||
|
||||
Reference in New Issue
Block a user