feat: add evaluation evidence board

This commit is contained in:
lda
2026-07-10 18:32:52 +07:00 Verified
parent 5aaaf0410c
commit cc77213173
5 changed files with 379 additions and 0 deletions
@@ -0,0 +1,68 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { EvaluationEvidenceScene } from "./EvaluationEvidenceScene.js";
const scene = {
id: "evaluation",
number: 13,
title: "Evaluation",
claimClass: "evaluated" as const,
evidencePointer: "Thesis Evaluation and Appendix C",
stageTheme: "paper" as const,
view: "evaluation" as const,
beats: [],
};
const beat = (id: string) => ({
id,
title: id,
caption: `Caption for ${id}`,
chatMode: "hidden" as const,
chatTheme: "light" as const,
evidencePresentation: "hidden" as const,
figure: null,
});
afterEach(() => cleanup());
describe("EvaluationEvidenceScene", () => {
it.each(["cohort", "validity", "findings"])("renders the persistent board for %s", (beatId) => {
render(<EvaluationEvidenceScene scene={scene} beat={beat(beatId)} />);
expect(screen.getByRole("group", { name: /evaluation evidence board/i })).toHaveAttribute(
"data-evaluation-beat",
beatId,
);
expect(screen.getByText("36")).toBeInTheDocument();
expect(screen.getByText("27")).toBeInTheDocument();
expect(screen.getByText("8")).toBeInTheDocument();
expect(screen.getByText("1")).toBeInTheDocument();
});
it("renders the manual audit reconciliation and validity statement", () => {
render(<EvaluationEvidenceScene scene={scene} beat={beat("validity")} />);
expect(screen.getByText("7 automatic successes")).toBeInTheDocument();
expect(screen.getByText("invalid as clean evidence")).toBeInTheDocument();
expect(screen.getByText("3 automatic failures")).toBeInTheDocument();
expect(screen.getByText("accepted from saved evidence")).toBeInTheDocument();
expect(
screen.getByText("Bounded longitudinal engineering evidence, not a controlled model comparison."),
).toBeInTheDocument();
});
it("renders six labelled finding icons without ranking language", () => {
render(<EvaluationEvidenceScene scene={scene} beat={beat("findings")} />);
const findings = screen.getByRole("list", { name: /ux gaps exposed by trials/i });
expect(findings.querySelectorAll("svg")).toHaveLength(6);
for (const label of [
"Schema discovery",
"Repair hints",
"Binding commands",
"Output schemas",
"Shell assumptions",
"Source contamination",
]) {
expect(screen.getByText(label)).toBeInTheDocument();
}
expect(findings.textContent).not.toMatch(/%|success rate|leaderboard/i);
});
});
@@ -0,0 +1,85 @@
import type { FC } from "react";
import { Cable, FileOutput, SearchCode, ShieldAlert, Terminal, Wrench, type LucideProps } from "lucide-react";
import { StageCaption } from "../StageCaption.js";
import type { SceneBeatDefinition, SceneDefinition } from "../storyboard.js";
import { evaluationEvidence, isEvaluationBeatId, type EvaluationFindingIcon } from "./evaluation-evidence.js";
type EvaluationEvidenceSceneProps = {
readonly scene: SceneDefinition;
readonly beat: SceneBeatDefinition;
};
const findingIcons: Record<EvaluationFindingIcon, FC<LucideProps>> = {
schema: SearchCode,
repair: Wrench,
binding: Cable,
output: FileOutput,
shell: Terminal,
contamination: ShieldAlert,
};
export const EvaluationEvidenceScene: FC<EvaluationEvidenceSceneProps> = ({ scene, beat }) => {
const beatId = isEvaluationBeatId(beat.id) ? beat.id : "cohort";
return (
<>
<StageCaption eyebrow={`Act III · ${scene.claimClass}`} title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<section
className="evaluation-board"
role="group"
aria-label="evaluation evidence board"
data-evaluation-beat={beatId}
>
<div className="evaluation-board__cohort" aria-label="campaign cohort equation">
<span className="evaluation-board__cohort-total">{evaluationEvidence.totalTrials}</span>
<span className="evaluation-board__cohort-label">audited trials</span>
<span className="evaluation-board__cohort-equation" aria-label="cohort factors">
{evaluationEvidence.cohortFactors.map((factor, index) => (
<span className="evaluation-board__factor" key={factor.label}>
{index > 0 ? " × " : ""}
<strong>{factor.value}</strong> {factor.label}
</span>
))}
</span>
</div>
<ol className="evaluation-board__outcomes" aria-label="audited outcomes">
{evaluationEvidence.outcomes.map((outcome) => (
<li className={`evaluation-board__outcome evaluation-board__outcome--${outcome.kind}`} key={outcome.label}>
<strong>{outcome.value}</strong>
<span>{outcome.label}</span>
</li>
))}
</ol>
<div className="evaluation-board__audit" aria-label="automatic and manual audit reconciliation">
<p className="evaluation-board__section-label">Automatic grading manual audit</p>
{evaluationEvidence.auditCorrections.map((correction) => (
<div className="evaluation-board__audit-row" key={correction.automatic}>
<span>{correction.automatic}</span>
<span aria-hidden="true"></span>
<strong>{correction.audited}</strong>
</div>
))}
</div>
<ol className="evaluation-board__findings" aria-label="UX gaps exposed by trials">
{evaluationEvidence.findings.map((finding) => {
const Icon = findingIcons[finding.icon];
return (
<li className="evaluation-board__finding" key={finding.icon}>
<Icon aria-hidden="true" />
<span>{finding.label}</span>
</li>
);
})}
</ol>
<p className="evaluation-board__boundary">{evaluationEvidence.validityStatement}</p>
</section>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
};
@@ -0,0 +1,38 @@
import { describe, expect, it } from "vitest";
import { evaluationEvidence, isEvaluationBeatId } from "./evaluation-evidence.js";
describe("evaluationEvidence", () => {
it("preserves the exact audited trial projection", () => {
expect(evaluationEvidence.totalTrials).toBe(36);
expect(evaluationEvidence.outcomes).toEqual([
{ value: 27, label: "clean product-path passes", kind: "pass" },
{ value: 8, label: "invalid evaluation samples", kind: "invalid" },
{ value: 1, label: "failure", kind: "fail" },
]);
expect(evaluationEvidence.auditCorrections).toEqual([
{ automatic: "7 automatic successes", audited: "invalid as clean evidence" },
{ automatic: "3 automatic failures", audited: "accepted from saved evidence" },
]);
});
it("preserves the validity boundary and six distinct findings", () => {
expect(evaluationEvidence.validityStatement).toBe(
"Bounded longitudinal engineering evidence, not a controlled model comparison.",
);
expect(evaluationEvidence.findings.map((finding) => finding.label)).toEqual([
"Schema discovery",
"Repair hints",
"Binding commands",
"Output schemas",
"Shell assumptions",
"Source contamination",
]);
expect(new Set(evaluationEvidence.findings.map((finding) => finding.icon)).size).toBe(6);
expect(JSON.stringify(evaluationEvidence)).not.toMatch(/%|success rate|leaderboard|superior/i);
});
it("recognizes only authored evaluation beats", () => {
expect(["cohort", "validity", "findings"].every(isEvaluationBeatId)).toBe(true);
expect(isEvaluationBeatId("unknown")).toBe(false);
});
});
@@ -0,0 +1,44 @@
export type EvaluationBeatId = "cohort" | "validity" | "findings";
export type EvaluationFindingIcon = "schema" | "repair" | "binding" | "output" | "shell" | "contamination";
export type EvaluationEvidenceModel = {
readonly cohortFactors: readonly { readonly value: string; readonly label: string }[];
readonly totalTrials: 36;
readonly outcomes: readonly { readonly value: number; readonly label: string; readonly kind: "pass" | "invalid" | "fail" }[];
readonly auditCorrections: readonly { readonly automatic: string; readonly audited: string }[];
readonly findings: readonly { readonly label: string; readonly icon: EvaluationFindingIcon }[];
readonly validityStatement: string;
};
export const evaluationEvidence: EvaluationEvidenceModel = {
cohortFactors: [
{ value: "2", label: "challenges" },
{ value: "2", label: "hosted models" },
{ value: "3", label: "profiles" },
{ value: "3", label: "waves" },
],
totalTrials: 36,
outcomes: [
{ value: 27, label: "clean product-path passes", kind: "pass" },
{ value: 8, label: "invalid evaluation samples", kind: "invalid" },
{ value: 1, label: "failure", kind: "fail" },
],
// These are campaign-specific audit disagreements, not a general accuracy
// measure for automatic grading.
auditCorrections: [
{ automatic: "7 automatic successes", audited: "invalid as clean evidence" },
{ automatic: "3 automatic failures", audited: "accepted from saved evidence" },
],
findings: [
{ label: "Schema discovery", icon: "schema" },
{ label: "Repair hints", icon: "repair" },
{ label: "Binding commands", icon: "binding" },
{ label: "Output schemas", icon: "output" },
{ label: "Shell assumptions", icon: "shell" },
{ label: "Source contamination", icon: "contamination" },
],
validityStatement: "Bounded longitudinal engineering evidence, not a controlled model comparison.",
};
export const isEvaluationBeatId = (value: string): value is EvaluationBeatId =>
value === "cohort" || value === "validity" || value === "findings";
@@ -80,6 +80,150 @@
flex-shrink: 0;
}
.evaluation-board {
--evaluation-ink: oklch(0.2 0.03 250);
--evaluation-paper: oklch(0.96 0.018 85);
--evaluation-rule: oklch(0.74 0.05 75);
display: grid;
grid-template-columns: minmax(14rem, 0.8fr) minmax(20rem, 1.2fr);
gap: 0;
margin: 0.25rem 0 0.5rem;
overflow: hidden;
border: 1px solid var(--evaluation-rule);
border-radius: 0.35rem;
background: var(--evaluation-paper);
color: var(--evaluation-ink);
box-shadow: 0 0.8rem 2rem rgb(20 25 35 / 12%);
}
.evaluation-board__cohort,
.evaluation-board__outcomes,
.evaluation-board__audit,
.evaluation-board__findings,
.evaluation-board__boundary {
transition: opacity 180ms ease, transform 180ms ease;
}
.evaluation-board[data-evaluation-beat="cohort"] .evaluation-board__cohort,
.evaluation-board[data-evaluation-beat="validity"] .evaluation-board__audit,
.evaluation-board[data-evaluation-beat="findings"] .evaluation-board__findings {
transform: translateY(-0.12rem);
}
.evaluation-board__cohort {
display: grid;
align-content: center;
gap: 0.2rem;
padding: 1rem 1.15rem;
border-right: 1px solid var(--evaluation-rule);
}
.evaluation-board__cohort-total {
font: 3.7rem/0.9 "Barlow Condensed", sans-serif;
letter-spacing: -0.04em;
}
.evaluation-board__cohort-label,
.evaluation-board__section-label {
color: oklch(0.4 0.04 250);
font: 0.65rem/1.2 "IBM Plex Mono", monospace;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.evaluation-board__cohort-equation {
margin-top: 0.7rem;
font: 0.76rem/1.45 "IBM Plex Mono", monospace;
}
.evaluation-board__factor strong {
font-size: 1rem;
}
.evaluation-board__outcomes {
display: flex;
grid-column: 2;
gap: 0.5rem;
margin: 0;
padding: 1rem 1.15rem 0.8rem;
list-style: none;
}
.evaluation-board__outcome {
display: grid;
flex: 1;
gap: 0.3rem;
min-width: 0;
padding-left: 0.7rem;
border-left: 3px solid oklch(0.55 0.03 250);
}
.evaluation-board__outcome--pass { border-color: oklch(0.55 0.14 145); }
.evaluation-board__outcome--invalid { border-color: oklch(0.66 0.14 75); }
.evaluation-board__outcome--fail { border-color: oklch(0.58 0.16 25); }
.evaluation-board__outcome strong {
font: 2.2rem/0.9 "Barlow Condensed", sans-serif;
}
.evaluation-board__outcome span,
.evaluation-board__finding span,
.evaluation-board__audit-row {
font: 0.72rem/1.3 "Source Sans 3", sans-serif;
}
.evaluation-board__audit,
.evaluation-board__findings {
padding: 0.8rem 1.15rem;
border-top: 1px solid var(--evaluation-rule);
}
.evaluation-board__section-label { margin: 0 0 0.45rem; }
.evaluation-board__audit-row {
display: grid;
grid-template-columns: 1fr auto 1.15fr;
gap: 0.65rem;
align-items: center;
padding: 0.28rem 0;
border-top: 1px solid oklch(0.84 0.035 75);
}
.evaluation-board__audit-row strong { font-weight: 650; }
.evaluation-board__findings {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.45rem 0.8rem;
grid-column: 2;
}
.evaluation-board__finding {
display: flex;
gap: 0.4rem;
align-items: center;
min-width: 0;
}
.evaluation-board__finding svg { width: 1rem; height: 1rem; flex: 0 0 auto; }
.evaluation-board__boundary {
grid-column: 1 / -1;
margin: 0;
padding: 0.7rem 1.15rem;
border-top: 1px solid var(--evaluation-rule);
background: oklch(0.91 0.025 85);
font: italic 0.82rem/1.35 "Newsreader", serif;
}
@media (max-width: 1080px) {
.evaluation-board { grid-template-columns: 1fr; }
.evaluation-board__cohort { border-right: 0; }
.evaluation-board__outcomes,
.evaluation-board__findings { grid-column: auto; }
.evaluation-board__audit-row { grid-template-columns: 1fr auto 1fr; }
}
.stage-caption__eyebrow {
color: oklch(0.78 0.035 250);
font-size: 0.8rem;