refactor(maestro): flatten web UI to a single host

Session tree nests by working directory only; the daemon chips in the
header become a session tally, and the spawn form loses its host picker.
API calls move from /api/daemons/:id/* to /api/dirs, /api/recent-dirs,
/api/gitroot, and /api/info.
This commit is contained in:
Miguel Palhas
2026-08-22 10:14:10 +01:00
parent 6790f41652
commit 1bb464be4b
14 changed files with 155 additions and 467 deletions
+19 -35
View File
@@ -2,7 +2,7 @@
// main.ts (`?mock`), so it never ships in a production bundle. It pushes a
// synthetic Snapshot through the store's normal event path.
import type { CatalogStore } from "./events.js";
import type { ActivityState, Session, DaemonInfo } from "./types.js";
import type { ActivityState, Session } from "./types.js";
// Rotate inferred activity across the fixture so the debug HUD (#20) shows all
// three states. Keyed off the id's last hex digit for stable variety.
@@ -10,7 +10,6 @@ const ACTIVITIES: ActivityState[] = ["Active", "Idle", "AwaitingInput"];
function sess(
id: string,
daemon: string,
command: string,
status: "Running" | "Dead",
cwd: string,
@@ -19,7 +18,6 @@ function sess(
const seed = parseInt(id.slice(-1), 16) || 0;
return {
id,
daemon_id: daemon,
cwd,
command,
env: {},
@@ -48,13 +46,12 @@ function stubSpawnApi(): void {
globalThis.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
const path = url.startsWith("http") ? new URL(url).pathname + new URL(url).search : url;
if (path.startsWith("/api/web/info")) {
if (path.startsWith("/api/info")) {
return Promise.resolve(
json({
daemon_id: "thinkpad",
hostname: "thinkpad.lan",
version: "0.1.0",
daemon_url: "wss://thinkpad.lan/daemon",
home_dir: "/home/dev",
presets: [
{ id: "claude", label: "Claude", command: "claude" },
{ id: "opencode", label: "opencode", command: "opencode" },
@@ -87,38 +84,31 @@ function stubSpawnApi(): void {
}
export function seedMock(store: CatalogStore): void {
const daemons: DaemonInfo[] = [
{ daemon_id: "thinkpad", hostname: "thinkpad.lan", version: "0.1.0", daemon_url: "wss://thinkpad.lan/daemon", home_dir: "/home/dev" },
{ daemon_id: "tower", hostname: "tower.lan", version: "0.1.0", daemon_url: "wss://tower.lan/daemon", home_dir: "/home/dev" },
{ daemon_id: "pi", hostname: "pi.lan", version: "0.1.0", daemon_url: "wss://pi.lan/daemon", home_dir: "/home/pi" },
];
const sessions: Session[] = [
// ~/git/maestro/web/* — deep dir chain; "~/git/maestro" collapses to one node.
sess("a1b2c3d4-0000-0000-0000-000000000001", "thinkpad", "claude", "Running", "/home/dev/git/maestro/web", "redesign"),
sess("a1b2c3d4-0000-0000-0000-000000000002", "thinkpad", "vite dev", "Running", "/home/dev/git/maestro/web", "dev-server"),
sess("a1b2c3d4-0000-0000-0000-000000000003", "tower", "bash", "Dead", "/home/dev/git/maestro/web", "scratch"),
sess("a1b2c3d4-0000-0000-0000-000000000004", "thinkpad", "claude", "Running", "/home/dev/git/maestro/web/tests", "e2e-fixes"),
sess("a1b2c3d4-0000-0000-0000-000000000001", "claude", "Running", "/home/dev/git/maestro/web", "redesign"),
sess("a1b2c3d4-0000-0000-0000-000000000002", "vite dev", "Running", "/home/dev/git/maestro/web", "dev-server"),
sess("a1b2c3d4-0000-0000-0000-000000000003", "bash", "Dead", "/home/dev/git/maestro/web", "scratch"),
sess("a1b2c3d4-0000-0000-0000-000000000004", "claude", "Running", "/home/dev/git/maestro/web/tests", "e2e-fixes"),
// ~/git/maestro/crates/* — sibling subtree under the same repo.
sess("a1b2c3d4-0000-0000-0000-000000000005", "thinkpad", "claude", "Running", "/home/dev/git/maestro/crates/registry", "registry-broker"),
sess("a1b2c3d4-0000-0000-0000-000000000006", "tower", "cargo watch -x test", "Running", "/home/dev/git/maestro/crates/registry", "test-loop"),
sess("a1b2c3d4-0000-0000-0000-000000000007", "tower", "cargo run -- daemon", "Running", "/home/dev/git/maestro/crates/daemon", "daemon"),
sess("a1b2c3d4-0000-0000-0000-000000000005", "claude", "Running", "/home/dev/git/maestro/crates/daemon", "daemon-work"),
sess("a1b2c3d4-0000-0000-0000-000000000006", "cargo watch -x test", "Running", "/home/dev/git/maestro/crates/daemon", "test-loop"),
sess("a1b2c3d4-0000-0000-0000-000000000007", "cargo run -- daemon", "Running", "/home/dev/git/maestro/crates/daemon", "daemon"),
// ~/work/acme/* — a separate top-level dir on the same host.
sess("a1b2c3d4-0000-0000-0000-000000000008", "tower", "claude", "Running", "/home/dev/work/acme/api", "billing-api"),
sess("a1b2c3d4-0000-0000-0000-000000000009", "tower", "pnpm dev", "Dead", "/home/dev/work/acme/web", "dashboard"),
sess("a1b2c3d4-0000-0000-0000-000000000008", "claude", "Running", "/home/dev/work/acme/api", "billing-api"),
sess("a1b2c3d4-0000-0000-0000-000000000009", "pnpm dev", "Dead", "/home/dev/work/acme/web", "dashboard"),
// ~/git/dotfiles — single session, fully collapsed chain.
sess("a1b2c3d4-0000-0000-0000-000000000010", "tower", "opencode", "Running", "/home/dev/git/dotfiles", "dotfiles"),
// pi's home (different home_dir) — sits directly under ~.
sess("a1b2c3d4-0000-0000-0000-000000000011", "pi", "btop", "Running", "/home/pi", "monitor"),
sess("a1b2c3d4-0000-0000-0000-000000000010", "opencode", "Running", "/home/dev/git/dotfiles", "dotfiles"),
sess("a1b2c3d4-0000-0000-0000-000000000011", "btop", "Running", "/home/dev/git/infra", "monitor"),
// outside any home dir → shown verbatim (no ~ collapse).
sess("a1b2c3d4-0000-0000-0000-000000000012", "pi", "ssh gateway", "Dead", "/etc"),
// home root on thinkpad — a leaf directly under ~ (name from command).
sess("a1b2c3d4-0000-0000-0000-000000000013", "thinkpad", "htop", "Running", "/home/dev"),
sess("a1b2c3d4-0000-0000-0000-000000000012", "ssh gateway", "Dead", "/etc"),
// home root — a leaf directly under ~ (name from command).
sess("a1b2c3d4-0000-0000-0000-000000000013", "htop", "Running", "/home/dev"),
];
// The store applies RegistryEvents internally; feed a Snapshot the same way
// The store applies CatalogEvents internally; feed a Snapshot the same way
// the WS would. `apply` is private, so reach it through an index cast.
(store as unknown as { apply: (e: unknown) => void }).apply({
type: "Snapshot",
daemons,
sessions,
});
@@ -126,12 +116,6 @@ export function seedMock(store: CatalogStore): void {
// backend endpoints stubbed so presets/recents/gitroot all render.
if (location.search.includes("spawn")) {
stubSpawnApi();
void import("./spawn.js").then((m) =>
m.openSpawnModal(
new Map(daemons.map((d) => [d.daemon_id, d])),
undefined,
new Map(sessions.map((s) => [s.id, s])),
),
);
void import("./spawn.js").then((m) => m.openSpawnModal());
}
}