Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-dashboard/src/fixture-main.ts
Snapshot at 34e3175
1import { freshnessFixtureTransport, freshnessScenarios } from "../test/freshness-fixtures.ts";
2import { judgeScenarios } from "../test/judge-fixtures.ts";
3import { agentStatusScenarios } from "../test/agent-status-fixtures.ts";
4import { archiveScenarios } from "../test/archive-fixtures.ts";
5import { emptyScenarios } from "../test/empty-fixtures.ts";
6
7import { usageScenarios } from "../test/usage-fixtures.ts";
8import { collisionScenarios } from "../test/collision-fixtures.ts";
9import { raceScenarios } from "../test/race-fixtures.ts";
10import { racesScenarios } from "../test/races-fixtures.ts";
11import { forkScenarios } from "../test/fork-graph-fixtures.ts";
12import { needsScenarios } from "../test/needs-you-fixtures.ts";
13import { reviewScenarios } from "../test/review-fixtures.ts";
14import { applyAppTheme, followSystemTheme } from "./theme.ts";
15import { mountDashboard } from "./dashboard.ts";
16import { createHttpTransport } from "./transport.ts";
17import type { Brand } from "./contracts.ts";
18import "./styles.css";
19import themeFixture from "../test/theme-fixture.json";
20
21declare const __DASHBOARD_BRAND__: Brand;
22document.title = `${__DASHBOARD_BRAND__.name} 路 Fixture projects`;
23let paused = new URL(location.href).searchParams.get("scenario") === "paused";
24const statusListeners = new Set<(paused: boolean) => void>();
25const scenario = new URL(location.href).searchParams.get("scenario") ?? "demo";
26const fixtureTransport = freshnessFixtureTransport(createHttpTransport({ origin: location.origin }), scenario);
27// Labeled fixture for capability hosts that support queue/receipts but not detail reads.
28if (new URL(location.href).searchParams.get("host") === "queue-only") {
29 delete fixtureTransport.getReview;
30 delete fixtureTransport.getDiff;
31}
32fixtureTransport.live = {
33 subscribe(listener) {
34 statusListeners.add(listener);
35 listener(paused);
36 return () => statusListeners.delete(listener);
37 },
38 async reconnect() {
39 paused = false;
40 for (const listener of statusListeners) listener(false);
41 },
42};
43if (new URL(location.href).searchParams.get("scenario") === "fork-live") {
44 const listeners = new Set<() => void>();
45 fixtureTransport.subscribe = (_project, invalidate) => {
46 listeners.add(invalidate);
47 return () => {
48 listeners.delete(invalidate);
49 };
50 };
51 const push = document.createElement("button");
52 push.textContent = "Fixture new push";
53 push.style.cssText = "position:fixed;bottom:32px;left:0;z-index:30";
54 push.addEventListener("click", async () => {
55 await fetch("/__fixtures/fork-live-push", { method: "POST" });
56 for (const listener of listeners) listener();
57 });
58 document.body.append(push);
59}
60const dashboard = mountDashboard(document.getElementById("dashboard")!, {
61 brand: __DASHBOARD_BRAND__,
62 fixture: true,
63 owner: new URL(location.href).searchParams.get("role") !== "agent",
64 agent: "fixture-agent",
65 async openPrompt(prompt) {
66 document.getElementById("fixture-prompt")!.textContent =
67 `Fixture prompt received (not sent): ${prompt}`;
68 },
69 initialProject: new URL(location.href).searchParams.get("scenario") === "empty-projects"
70 ? undefined : new URL(location.href).searchParams.get("scenario") ?? "demo",
71 projects: new URL(location.href).searchParams.get("scenario") === "empty-projects" ? [] : [
72 { id: "demo", label: "Demo workspace" },
73 { id: "quiet", label: "Quiet project" },
74 ...[
75 ...freshnessScenarios,
76 ...judgeScenarios,
77 ...agentStatusScenarios,
78 ...archiveScenarios,
79 ...emptyScenarios.filter((id) => id !== "empty-projects"),
80 ...raceScenarios,
81 ...racesScenarios,
82 ...reviewScenarios,
83 ...usageScenarios,
84 ...collisionScenarios,
85 ...needsScenarios,
86 ...forkScenarios,
87 "large",
88 "first",
89 "partial",
90 "truncated",
91 "superseded",
92 "paused",
93 "deleted",
94 "error",
95 "loading",
96 "receipts",
97 "advisory",
98 "recheck",
99 "rerun-failed",
100 "gating-recheck",
101 "history-required",
102 "history-advisory",
103 ].map((id) => ({
104 id,
105 label: `${id} fixture`,
106 })),
107 ],
108 transport: fixtureTransport,
109});
110
111// Retained-data captures start from a real loaded fixture snapshot before the read fails/waits.
112if (scenario === "freshness-refreshing" || scenario === "freshness-failed")
113 setTimeout(() => void dashboard.refresh(), 1000);
114
115const stopTheme = followSystemTheme();
116window.addEventListener("pagehide", stopTheme, { once: true });
117
118// Explicitly labeled fixture controls exercise the same host theme application function.
119const controls = document.createElement("div");
120controls.setAttribute("aria-label", "Fixture theme harness");
121controls.className = "fixture-theme-controls";
122controls.style.cssText = "position:fixed;bottom:0;left:0;z-index:30;display:flex;gap:12px";
123const prompt = document.createElement("p");
124prompt.id = "fixture-prompt";
125controls.append(prompt);
126for (const mode of ["light", "dark"] as const) {
127 const button = document.createElement("button");
128 button.textContent = `Fixture ${mode}`;
129 button.addEventListener("click", () => {
130 stopTheme();
131 applyAppTheme({ mode, accentColor: null });
132 });
133 controls.append(button);
134}
135const accent = document.createElement("button");
136accent.textContent = "Fixture blue accent";
137accent.addEventListener("click", () => {
138 stopTheme();
139 applyAppTheme({ mode: "light", accentColor: themeFixture.accent });
140});
141controls.append(accent);
142const reset = document.createElement("button");
143reset.textContent = "Reset fixture";
144reset.addEventListener("click", async () => {
145 await fetch("/__fixtures/reset", { method: "POST" });
146 location.reload();
147});
148controls.append(reset);
149// Capture controls can be hidden through the fixture UI so they never cover evidence.
150const hide = document.createElement("button");
151hide.textContent = "Hide fixture controls";
152hide.addEventListener("click", () => controls.remove());
153controls.append(hide);
154document.body.append(controls);
155