Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-dashboard/src/fixture-main.ts

Snapshot at 34e3175

Containing directory

5,997 bytes 路 typescriptView raw
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