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.
145 lines
6.1 KiB
TypeScript
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"));
|
|
}
|
|
}
|