Merge pull request #726 from AssetOverflow/feat/wb-m-wrong-zero-frame
feat(workbench): wrong=0 as a felt global presence (Wave M B3)
This commit is contained in:
commit
2935122805
3 changed files with 142 additions and 0 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
import { CommandPalette } from "../design/components/primitives/CommandPalette";
|
import { CommandPalette } from "../design/components/primitives/CommandPalette";
|
||||||
import { useRuntimeStatus } from "../api/queries";
|
import { useRuntimeStatus } from "../api/queries";
|
||||||
|
import { WrongZeroFrame } from "./WrongZeroFrame";
|
||||||
|
|
||||||
export function TopBar({
|
export function TopBar({
|
||||||
paletteOpen,
|
paletteOpen,
|
||||||
|
|
@ -64,6 +65,8 @@ export function TopBar({
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<WrongZeroFrame />
|
||||||
|
|
||||||
<div className="shrink-0">{connectionPill}</div>
|
<div className="shrink-0">{connectionPill}</div>
|
||||||
|
|
||||||
<CommandPalette open={paletteOpen} onOpenChange={onPaletteOpenChange} />
|
<CommandPalette open={paletteOpen} onOpenChange={onPaletteOpenChange} />
|
||||||
|
|
|
||||||
80
workbench-ui/src/app/WrongZeroFrame.test.tsx
Normal file
80
workbench-ui/src/app/WrongZeroFrame.test.tsx
Normal file
|
|
@ -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(
|
||||||
|
<QueryClientProvider client={createTestQueryClient()}>
|
||||||
|
<MemoryRouter>
|
||||||
|
<WrongZeroFrame />
|
||||||
|
</MemoryRouter>
|
||||||
|
</QueryClientProvider>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
59
workbench-ui/src/app/WrongZeroFrame.tsx
Normal file
59
workbench-ui/src/app/WrongZeroFrame.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<span
|
||||||
|
className={`${base} border-[var(--color-border-subtle)] text-[var(--color-text-secondary)]`}
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
wrong=0 …
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError || !data || data.length === 0) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`${base} border-[var(--color-border-subtle)] text-[var(--color-text-muted)]`}
|
||||||
|
aria-live="polite"
|
||||||
|
title="serving metrics could not be read"
|
||||||
|
>
|
||||||
|
wrong=0: metrics unavailable
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Link
|
||||||
|
to="/calibration"
|
||||||
|
className={`${base} border-[var(--color-border-subtle)] bg-[var(--color-surface-sunken)] no-underline text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]`}
|
||||||
|
aria-label={`Serving outcome: ${correct} correct, ${refused} refused, ${wrong} wrong. View calibration.`}
|
||||||
|
>
|
||||||
|
<span>{correct} correct · {refused} refused · </span>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
wrong > 0
|
||||||
|
? "font-semibold text-[var(--color-state-contradicted)]"
|
||||||
|
: "font-semibold text-[var(--color-state-verified)]"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{wrong} wrong
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue