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/web/src/mock.ts
T
Miguel Palhas 5c3f9cab9f feat(maestro): replace spawn modal with draft panes (#62)
The draft pane covers every field the modal had; the modal was a second
code path for the same job. The header Spawn button, sidebar empty state,
welcome button, and ?mock&spawn now open a draft pane via
MultiView.openDraft — reusing an empty focused pane, else splitting it.
"Spawn similar" seeds a draft with the session's cwd + command.

Modal-only CSS (backdrop, sheet/dialog chrome, keyframes) is deleted;
.modal-body form styling folds into .spawn-form, which also picks up the
select styling the worktree branch picker was borrowing from the modal.
2026-08-23 22:36:48 +01:00

145 lines
6.1 KiB
TypeScript

// Dev-only review fixture. Imported lazily behind `import.meta.env.DEV` from
// 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 } 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.
const ACTIVITIES: ActivityState[] = ["Active", "Idle", "AwaitingInput"];
function sess(
id: string,
command: string,
status: "Running" | "Dead",
cwd: string,
name?: string,
): Session {
const seed = parseInt(id.slice(-1), 16) || 0;
return {
id,
cwd,
command,
env: {},
metadata: name ? { name } : {},
status,
created_at: "2026-06-15T12:00:00Z",
// Inferred fields (#18) so the HUD has real data to render in review.
foreground: command.split(" ")[0],
activity: ACTIVITIES[seed % ACTIVITIES.length],
last_activity_at: new Date(Date.now() - seed * 11_000).toISOString(),
// The session tree nests by current_cwd; default it to the spawn dir.
current_cwd: cwd,
};
}
/** Stub the spawn-form backend endpoints (#26/#59) so `?mock&spawn` renders the
* full flow — presets, projects, gitroot — with no daemon running. Dev-only;
* delegates every other request to the real `fetch`. */
function stubSpawnApi(): void {
const real = globalThis.fetch.bind(globalThis);
const json = (body: unknown) =>
new Response(JSON.stringify(body), {
status: 200,
headers: { "content-type": "application/json" },
});
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/info")) {
return Promise.resolve(
json({
hostname: "thinkpad.lan",
version: "0.1.0",
home_dir: "/home/dev",
presets: [
{ id: "claude", label: "Claude", command: "claude" },
{ id: "opencode", label: "opencode", command: "opencode" },
{ id: "zsh", label: "Shell", command: "zsh" },
{ id: "custom", label: "Custom…", command: "" },
],
}),
);
}
if (path.includes("/projects")) {
const now = Date.now();
const iso = (daysAgo: number) => new Date(now - daysAgo * 86_400_000).toISOString();
return Promise.resolve(
json([
{
path: "/home/dev/git/maestro",
name: "maestro",
is_worktree: false,
last_activity: iso(0),
last_command: "claude",
},
{
path: "/home/dev/worktrees/maestro-59",
name: "maestro-59",
is_worktree: true,
base_repo: "/home/dev/git/maestro",
last_activity: iso(1),
last_command: "opencode",
},
{
path: "/home/dev/git/dotfiles",
name: "dotfiles",
is_worktree: false,
last_activity: iso(30),
last_command: "zsh",
},
{
path: "/home/dev/work/acme/api",
name: "api",
is_worktree: false,
},
]),
);
}
if (path.includes("/gitroot")) {
const root = path.includes("maestro") || path.includes("dotfiles")
? { root: "/home/dev/git/maestro" }
: {};
return Promise.resolve(json(root));
}
return real(input, init);
};
}
export function seedMock(store: CatalogStore): void {
const sessions: Session[] = [
// ~/git/maestro/web/* — deep dir chain; "~/git/maestro" collapses to one node.
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", "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", "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", "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", "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 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",
sessions,
});
// `?mock&spawn` auto-opens a draft pane for visual review, with the spawn
// backend endpoints stubbed so presets/projects/gitroot all render.
if (location.search.includes("spawn")) {
stubSpawnApi();
document.dispatchEvent(new CustomEvent("catalog-open-spawn"));
}
}