diff --git a/workbench-ui/src/app/TopBar.tsx b/workbench-ui/src/app/TopBar.tsx index 2881e2ff..6614bf4f 100644 --- a/workbench-ui/src/app/TopBar.tsx +++ b/workbench-ui/src/app/TopBar.tsx @@ -1,5 +1,6 @@ import { CommandPalette } from "../design/components/primitives/CommandPalette"; import { useRuntimeStatus } from "../api/queries"; +import { WrongZeroFrame } from "./WrongZeroFrame"; export function TopBar({ paletteOpen, @@ -64,6 +65,8 @@ export function TopBar({ + +
{connectionPill}
diff --git a/workbench-ui/src/app/WrongZeroFrame.test.tsx b/workbench-ui/src/app/WrongZeroFrame.test.tsx new file mode 100644 index 00000000..c0dd636c --- /dev/null +++ b/workbench-ui/src/app/WrongZeroFrame.test.tsx @@ -0,0 +1,80 @@ +import { QueryClientProvider } from "@tanstack/react-query"; +import { render, screen } from "@testing-library/react"; +import { MemoryRouter } from "react-router-dom"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { createTestQueryClient } from "../test/createTestQueryClient"; +import type { ServingMetrics } from "../types/api"; +import { WrongZeroFrame } from "./WrongZeroFrame"; + +function renderFrame() { + return render( + + + + + , + ); +} + +function stub(metrics: ServingMetrics[] | "error") { + vi.stubGlobal( + "fetch", + vi.fn(() => + metrics === "error" + ? Promise.resolve({ + json: () => + Promise.resolve({ + ok: false, + generated_at: "now", + error: { code: "read_error", message: "boom" }, + }), + }) + : Promise.resolve({ + json: () => Promise.resolve({ ok: true, generated_at: "now", data: { items: metrics } }), + }), + ), + ); +} + +function lane(correct: number, refused: number, wrong: number, name: string): ServingMetrics { + return { + lane: name, + correct, + refused, + wrong, + sample_count: correct + refused + wrong, + source_path: `evals/${name}/report.json`, + source_digest: "sha256:x", + }; +} + +describe("WrongZeroFrame", () => { + afterEach(() => { + vi.restoreAllMocks(); + localStorage.clear(); + }); + + it("aggregates the live serving triplet across lanes with wrong load-bearing", async () => { + stub([lane(4, 46, 0, "train_sample"), lane(5, 495, 0, "holdout_dev")]); + renderFrame(); + // 9 correct · 541 refused · 0 wrong + expect(await screen.findByText(/9 correct · 541 refused ·/)).toBeInTheDocument(); + expect(screen.getByText("0 wrong")).toBeInTheDocument(); + // it links to the discipline behind it + expect(screen.getByRole("link")).toHaveAttribute("href", "/calibration"); + }); + + it("is a mirror — renders a non-zero wrong honestly, not a hard-coded zero", async () => { + stub([lane(90, 5, 5, "drifted")]); + renderFrame(); + expect(await screen.findByText("5 wrong")).toBeInTheDocument(); + expect(screen.queryByText("0 wrong")).not.toBeInTheDocument(); + }); + + it("renders an honest 'metrics unavailable', never a fake zero, on error", async () => { + stub("error"); + renderFrame(); + expect(await screen.findByText(/metrics unavailable/)).toBeInTheDocument(); + expect(screen.queryByText("0 wrong")).not.toBeInTheDocument(); + }); +}); diff --git a/workbench-ui/src/app/WrongZeroFrame.tsx b/workbench-ui/src/app/WrongZeroFrame.tsx new file mode 100644 index 00000000..546cb5e4 --- /dev/null +++ b/workbench-ui/src/app/WrongZeroFrame.tsx @@ -0,0 +1,59 @@ +import { Link } from "react-router-dom"; +import { useServingMetrics } from "../api/queries"; + +// The project's thesis, made a constant presence: the live serving outcome +// with the wrong count load-bearing. It is a MIRROR of /serving/metrics — +// it shows whatever the committed reports say, never a hard-coded zero, and +// says so honestly when the metrics can't be read. +export function WrongZeroFrame() { + const { data, isLoading, isError } = useServingMetrics(); + + const base = + "shrink-0 rounded-full border px-3 py-1 text-xs tabular-nums focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--color-focus-ring)]"; + + if (isLoading) { + return ( + + wrong=0 … + + ); + } + + if (isError || !data || data.length === 0) { + return ( + + wrong=0: metrics unavailable + + ); + } + + const correct = data.reduce((sum, m) => sum + m.correct, 0); + const refused = data.reduce((sum, m) => sum + m.refused, 0); + const wrong = data.reduce((sum, m) => sum + m.wrong, 0); + + return ( + + {correct} correct · {refused} refused · + 0 + ? "font-semibold text-[var(--color-state-contradicted)]" + : "font-semibold text-[var(--color-state-verified)]" + } + > + {wrong} wrong + + + ); +}