The "they'd want to use it" first-run surface: a curated narrative that makes
the engine's discipline legible to a newcomer — bring a claim from any model and
watch it get decided, refused, or replayed. Proposer authority ignored.
Honest by construction (the risk with a narrative surface is theater):
- workbench/tour.py determinism_tour() is a curated ordered narrative BOUND to
the real demo registry. Intro (provider-agnostic thesis) → three demo steps
(deductive entailment decides only on engine+oracle agreement; epistemic
truth-state refuses a wrong proposer; proof-carrying promotion ignores
proposer authority) → payoff (replay-to-same-hash + the citable evidence
bundle).
- Each demo step's what_this_proves / what_this_does_not_prove cards and the
demo title are PULLED FROM THE REAL DEMO SPEC — never re-authored — so the
tour can never claim more than the demo it points at.
- A step referencing a missing demo FAILS CLOSED (KeyError), not a dead link.
Tests assert both (cards == spec; phantom demo raises).
Backend: schemas.py DeterminismTour/TourStep; GET /tour. schema-snapshot regen.
Frontend: /tour route registered in the registry (Determinism section, 14
routes; nav/palette/guide counts updated); TourRoute — thesis hero + ordered
step cards with the honesty cards + links to the real demos / replay.
Validation: 140 workbench Python tests incl. tour drift guards; 472/472 frontend
incl. routes/routeConformance/routes.docs (guide↔registry), Shell+palette counts
(14), schemaDrift, and the tour UI test; pnpm build clean; git diff --check
clean. No serving-path imports.
188 lines
6.2 KiB
TypeScript
188 lines
6.2 KiB
TypeScript
import { render, screen, fireEvent } from "@testing-library/react";
|
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
import { MemoryRouter, Routes, Route } from "react-router-dom";
|
|
import { QueryClientProvider } from "@tanstack/react-query";
|
|
import { createTestQueryClient } from "../test/createTestQueryClient";
|
|
import { Shell } from "./Shell";
|
|
import { ChatRoute } from "../routes/ChatRoute";
|
|
import { ProposalsRoute } from "./proposals/ProposalsRoute";
|
|
import { setWorkbenchPref } from "./workbenchPrefs";
|
|
import type { RuntimeStatus } from "../types/api";
|
|
|
|
// Mock the API queries module
|
|
vi.mock("../api/queries", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../api/queries")>();
|
|
return {
|
|
...actual,
|
|
useRuntimeStatus: vi.fn(),
|
|
};
|
|
});
|
|
|
|
import { useRuntimeStatus } from "../api/queries";
|
|
|
|
const mockStatus: RuntimeStatus = {
|
|
backend: "numpy",
|
|
git_revision: "abcdef1234567890",
|
|
engine_state_present: true,
|
|
checkpoint_revision: "deadbeef12345678",
|
|
revision_warning: false,
|
|
active_session_id: null,
|
|
mutation_mode: "read_only",
|
|
};
|
|
|
|
function makeClient() {
|
|
return createTestQueryClient();
|
|
}
|
|
|
|
function renderShell(initialPath = "/chat") {
|
|
const client = makeClient();
|
|
return render(
|
|
<QueryClientProvider client={client}>
|
|
<MemoryRouter initialEntries={[initialPath]}>
|
|
<Routes>
|
|
<Route path="/" element={<Shell />}>
|
|
<Route path="chat" element={<ChatRoute />} />
|
|
<Route path="proposals" element={<ProposalsRoute />} />
|
|
</Route>
|
|
</Routes>
|
|
</MemoryRouter>
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
describe("Shell", () => {
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
localStorage.clear();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
vi.mocked(useRuntimeStatus).mockReturnValue({
|
|
data: mockStatus,
|
|
isLoading: false,
|
|
isError: false,
|
|
isPending: false,
|
|
isSuccess: true,
|
|
status: "success",
|
|
error: null,
|
|
} as any);
|
|
});
|
|
|
|
it("renders five named regions", () => {
|
|
renderShell();
|
|
expect(screen.getByRole("banner")).toBeInTheDocument(); // topbar is a header
|
|
expect(document.querySelector('[data-region="topbar"]')).toBeInTheDocument();
|
|
expect(document.querySelector('[data-region="leftnav"]')).toBeInTheDocument();
|
|
expect(document.querySelector('[data-region="main"]')).toBeInTheDocument();
|
|
expect(document.querySelector('[data-region="statusfooter"]')).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies the persisted density mode to the shell", () => {
|
|
setWorkbenchPref("densityMode", "compact");
|
|
renderShell();
|
|
|
|
expect(document.querySelector('[data-density="compact"]')).toBeInTheDocument();
|
|
});
|
|
|
|
it("LeftNav has exactly 14 items in section-grouped order", () => {
|
|
renderShell();
|
|
const nav = document.querySelector('[data-region="leftnav"]')!;
|
|
const links = nav.querySelectorAll("a");
|
|
expect(links).toHaveLength(14);
|
|
const labels = Array.from(links).map((l) => l.textContent);
|
|
// Grouped by section (Converse → Cognition → Determinism → Evidence →
|
|
// Discipline → Substrate → Settings), derived from the route registry.
|
|
expect(labels).toEqual([
|
|
"Chat",
|
|
"Trace",
|
|
"Contemplation",
|
|
"Tour",
|
|
"Replay",
|
|
"Demos",
|
|
"Proposals",
|
|
"Runs",
|
|
"Vault",
|
|
"Audit",
|
|
"Evals",
|
|
"Calibration",
|
|
"Packs",
|
|
"Settings",
|
|
]);
|
|
});
|
|
|
|
it("clicking a nav item changes route (main shows new content)", () => {
|
|
vi.stubGlobal("fetch", vi.fn(() => new Promise(() => {})));
|
|
renderShell("/chat");
|
|
expect(screen.getByText("Ask CORE a question.")).toBeInTheDocument();
|
|
|
|
// Click Proposals nav link
|
|
const proposalsLink = screen.getByRole("link", { name: "Proposals" });
|
|
fireEvent.click(proposalsLink);
|
|
|
|
expect(screen.getByText("Loading proposal queue...")).toBeInTheDocument();
|
|
});
|
|
|
|
it("StatusFooter shows mutation_mode Read Only label for read_only", () => {
|
|
renderShell();
|
|
const footer = document.querySelector('[data-region="statusfooter"]')!;
|
|
expect(footer.textContent).toContain("Read Only");
|
|
expect(footer.textContent).not.toContain("Runtime Turn");
|
|
});
|
|
|
|
it("StatusFooter shows git_revision short SHA", () => {
|
|
renderShell();
|
|
const el = document.querySelector('[data-testid="git-revision"]');
|
|
expect(el).toBeInTheDocument();
|
|
expect(el!.textContent).toBe("abcdef12");
|
|
});
|
|
|
|
it("StatusFooter shows checkpoint_revision short SHA", () => {
|
|
renderShell();
|
|
const el = document.querySelector('[data-testid="checkpoint-revision"]');
|
|
expect(el).toBeInTheDocument();
|
|
expect(el!.textContent).toBe("deadbeef");
|
|
});
|
|
|
|
it("mutation_mode runtime_turn renders amber styling and Runtime Turn label", () => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
vi.mocked(useRuntimeStatus).mockReturnValue({
|
|
data: { ...mockStatus, mutation_mode: "runtime_turn" },
|
|
isLoading: false,
|
|
isError: false,
|
|
isPending: false,
|
|
isSuccess: true,
|
|
status: "success",
|
|
error: null,
|
|
} as any);
|
|
|
|
renderShell();
|
|
const mutationEl = document.querySelector('[data-testid="mutation-mode"]')!;
|
|
expect(mutationEl.textContent).toBe("Runtime Turn");
|
|
// Should use warning color class (amber)
|
|
expect(mutationEl.className).toContain("warning");
|
|
});
|
|
|
|
it("revision_warning=true makes checkpoint_revision show warning attr, clicking expands note", () => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
vi.mocked(useRuntimeStatus).mockReturnValue({
|
|
data: { ...mockStatus, revision_warning: true },
|
|
isLoading: false,
|
|
isError: false,
|
|
isPending: false,
|
|
isSuccess: true,
|
|
status: "success",
|
|
error: null,
|
|
} as any);
|
|
|
|
renderShell();
|
|
const cpBtn = document.querySelector('[data-testid="checkpoint-revision"]')!;
|
|
expect(cpBtn.getAttribute("data-warning")).toBe("true");
|
|
|
|
// Click to expand note
|
|
fireEvent.click(cpBtn);
|
|
const note = document.querySelector('[data-testid="revision-note"]');
|
|
expect(note).toBeInTheDocument();
|
|
expect(note!.textContent).toContain("ADR-0157 / ADR-0158");
|
|
});
|
|
});
|