Capture harness, fixture verification, CI, and the public README

The site does no live inference. Rollouts are captured once against spark-1 and
replayed at their recorded wall-clock — a public demo with no auth cannot hold
an API key, and a recorded run can be scrubbed, permalinked, blind-compared and
verified in ways a live one cannot. What stops it being a video is that the
browser re-derives every number from the recorded moves.

verify_fixtures.py is the Python half of that: it replays every committed
fixture through the engine and reproduces its own rewards. All 16 land at
delta 0.0. A fixture that cannot be regenerated is a claim with no receipt.

First real measurement, thinking off, 8 seeds: solved 0/8. The model repeats
guesses it has already played, invents words (trape, slith, postt, boomy),
and contradicts its own feedback — consistency 0.09 to 0.17. That is the
published failure taxonomy showing up in our own data on the first run, and it
is why `consistency` is a reward component rather than a footnote.

A capture failure is recorded as a turn with a null reply, never dropped. A
capture that silently discarded failed turns would be reporting a better model
than the one that ran.

CI gates both halves and four things that fail silently in production: the word
lists must rebuild byte-identically, the prerendered routes must carry their own
baked og tags (crawlers do not run JS, so without them every shared link
previews as the homepage), no blob: URL may reach the bundle (the site's CSP has
no worker-src, so it falls back to default-src 'self' and a blob worker is
blocked with no error), and the conformance digest must match across languages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
This commit is contained in:
karti-ai
2026-08-28 15:47:31 -07:00
parent 69607fbfe9
commit 408ce4a525
43 changed files with 5279 additions and 136 deletions
+192
View File
@@ -0,0 +1,192 @@
import { useState } from 'react';
import type { ComponentType } from 'react';
import { Eye, Trophy } from 'lucide-react';
import type { DemoStep } from '@/lib/demo-kit/types';
import { cn } from '@/lib/utils';
import { formatOrDash } from './format';
export interface BlindCompareRun<T> {
runId: string;
/** The identity, revealed only after the visitor commits. */
label: string;
model: string;
/** Required by the contract on an `intervened` run; shown at reveal. */
intervention?: string;
steps: DemoStep<T>[];
total: number | null;
}
export type BlindVote = 'A' | 'B' | 'tie';
export interface BlindCompareProps<T> {
/** Both runs must be the same seed or the comparison is meaningless. */
seed: number;
a: BlindCompareRun<T>;
b: BlindCompareRun<T>;
Surface: ComponentType<{ state: T; compact?: boolean }>;
question?: string;
onVote?: (vote: BlindVote) => void;
className?: string;
}
/**
* Two runs on the same seed, unlabelled, until you commit.
*
* The point is not the vote. The point is that the visitor forms an opinion
* from the behaviour BEFORE they learn which one had the better prompt, the
* bigger model or the training run — because once they know, they cannot
* unknow it, and every "obviously the trained one looks better" is worthless
* after the fact.
*
* There is a "reveal without voting" escape on purpose: a visitor who does not
* want to play should not be held hostage by a modal-shaped page.
*/
export function BlindCompare<T>({
seed,
a,
b,
Surface,
question = 'Which agent would you rather have running this?',
onVote,
className,
}: BlindCompareProps<T>) {
const [vote, setVote] = useState<BlindVote | null>(null);
const [revealed, setRevealed] = useState(false);
const commit = (choice: BlindVote) => {
setVote(choice);
setRevealed(true);
onVote?.(choice);
};
const winner: 'A' | 'B' | 'tie' =
a.total === null || b.total === null
? 'tie'
: a.total > b.total
? 'A'
: b.total > a.total
? 'B'
: 'tie';
const sides: { id: 'A' | 'B'; run: BlindCompareRun<T> }[] = [
{ id: 'A', run: a },
{ id: 'B', run: b },
];
return (
<section aria-label="Blind comparison" className={cn('space-y-3', className)}>
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
<h3 className="text-sm font-semibold">{question}</h3>
<p className="nums text-xs text-muted">Same puzzle, same seed ({seed}).</p>
</div>
<div className="grid gap-3 sm:grid-cols-2">
{sides.map(({ id, run }) => {
const last = run.steps[run.steps.length - 1];
const picked = vote === id;
return (
<div
key={id}
className={cn(
'card flex flex-col gap-3 p-3 transition-colors duration-2 ease-enter',
picked && 'border-brand',
revealed && winner === id && 'border-positive',
)}
>
<div className="flex items-center justify-between gap-2">
<h4 className="text-sm font-semibold">Agent {id}</h4>
<span className="nums text-xs text-muted">{run.steps.length} steps</span>
</div>
<div className="rounded-lg bg-surface-2 p-3">
{last ? (
<Surface state={last.state} />
) : (
<p className="text-sm text-muted">This run recorded no steps.</p>
)}
</div>
{!revealed ? (
<button
type="button"
onClick={() => commit(id)}
className="tap w-full rounded-lg bg-primary px-3 text-sm font-medium text-primary-foreground transition-colors duration-2 ease-enter hover:bg-primary/90"
>
Agent {id} is better
</button>
) : (
<dl className="space-y-1 text-sm">
<div className="flex items-baseline justify-between gap-2">
<dt className="text-muted">Identity</dt>
<dd className="text-right font-medium">{run.label}</dd>
</div>
<div className="flex items-baseline justify-between gap-2">
<dt className="text-muted">Model</dt>
<dd className="nums text-right font-mono text-xs">{run.model}</dd>
</div>
<div className="flex items-baseline justify-between gap-2">
<dt className="text-muted">Intervention</dt>
<dd className="text-right text-xs">
{run.intervention ?? (
<span className="text-muted">none plain rollout</span>
)}
</dd>
</div>
<div className="flex items-baseline justify-between gap-2 border-t border-border pt-1">
<dt className="text-muted">Reward</dt>
<dd className="nums flex items-center gap-1 text-right font-mono font-semibold">
{revealed && winner === id ? (
<Trophy className="h-3.5 w-3.5 text-positive" aria-hidden="true" />
) : null}
{formatOrDash(run.total)}
</dd>
</div>
</dl>
)}
</div>
);
})}
</div>
{!revealed ? (
<div className="flex flex-wrap gap-2">
<button
type="button"
onClick={() => commit('tie')}
className="tap rounded-lg border border-border px-3 text-sm font-medium transition-colors duration-2 ease-enter hover:bg-surface-2"
>
Too close to call
</button>
<button
type="button"
onClick={() => setRevealed(true)}
className="tap inline-flex items-center gap-1.5 rounded-lg px-3 text-sm font-medium text-muted transition-colors duration-2 ease-enter hover:text-fg"
>
<Eye className="h-4 w-4" aria-hidden="true" />
Just show me
</button>
</div>
) : (
<p role="status" className="card bg-surface-2 p-3 text-sm leading-relaxed">
{vote === null ? (
<>Revealed without a vote. </>
) : vote === winner ? (
<>
<span className="font-semibold text-positive">You picked the higher-scoring run.</span>{' '}
</>
) : (
<>
<span className="font-semibold text-warning">
You picked the lower-scoring run.
</span>{' '}
</>
)}
The environment scored these two with the same grader, on the same seed. The difference
between them is stated above and if it is a prompt change rather than a training run,
it says so, because a prompt change presented as a training result is the oldest trick
in this business.
</p>
)}
</section>
);
}
+188
View File
@@ -0,0 +1,188 @@
import { useMemo, useState } from 'react';
import { ExternalLink } from 'lucide-react';
import { cn } from '@/lib/utils';
export interface CodeReceiptProps {
/** The file's text, imported with `?raw` so it cannot drift from the source. */
code: string;
/** Repo-relative path, shown as the receipt's header. */
path: string;
/**
* A literal string that appears in the source and marks the interesting part.
* See `resolveMarkedRange` for the two conventions it supports.
*/
marker?: string;
/** Link to the whole file — GitHub, usually. */
href?: string;
/** Lines of context kept around the marked range while collapsed. */
context?: number;
className?: string;
}
export interface MarkedRange {
/** 0-based, inclusive. */
start: number;
/** 0-based, inclusive. */
end: number;
}
/**
* Two marker conventions, because both exist in real repos:
*
* TWICE — the marker brackets a region (`# --8<-- reward` … `# --8<--`).
* The marked range is the lines BETWEEN them; the fences themselves
* are not interesting code.
*
* ONCE — the marker sits on a definition line (`def compute_reward`). The
* marked range is that line plus its indented body, which is what
* you actually meant. Blank lines inside the body are kept; trailing
* blank lines are not, or the highlight runs on past the function.
*
* A marker that matches nothing returns null and the whole file renders. That
* is the right failure: a stale marker must not hide the source.
*/
export function resolveMarkedRange(lines: string[], marker?: string): MarkedRange | null {
if (!marker) return null;
const hits: number[] = [];
lines.forEach((line, index) => {
if (line.includes(marker)) hits.push(index);
});
const first = hits[0];
if (first === undefined) return null;
if (hits.length >= 2) {
const last = hits[hits.length - 1] as number;
return last - first > 1 ? { start: first + 1, end: last - 1 } : { start: first, end: last };
}
const anchor = lines[first] ?? '';
const indent = anchor.length - anchor.trimStart().length;
let end = first;
for (let i = first + 1; i < lines.length; i += 1) {
const line = lines[i] ?? '';
if (line.trim() === '') continue;
const lineIndent = line.length - line.trimStart().length;
if (lineIndent <= indent) break;
end = i;
}
return { start: first, end };
}
/**
* Source, with the part that matters marked.
*
* Syntax highlighting is deliberately absent: it costs a highlighter in the
* bundle and buys nothing an exec can use, while the marked range — the thing
* that says "this, right here, is the grader" — costs nothing and is the whole
* reason the panel exists.
*
* The block scrolls horizontally inside itself. Long Python lines must never
* make the PAGE scroll sideways; on a phone that turns every vertical swipe
* into a fight.
*/
export function CodeReceipt({
code,
path,
marker,
href,
context = 3,
className,
}: CodeReceiptProps) {
const lines = useMemo(() => code.replace(/\n$/, '').split('\n'), [code]);
const range = useMemo(() => resolveMarkedRange(lines, marker), [lines, marker]);
const [expanded, setExpanded] = useState(range === null);
const shown = useMemo(() => {
if (range === null || expanded) return { from: 0, to: lines.length - 1 };
return {
from: Math.max(range.start - context, 0),
to: Math.min(range.end + context, lines.length - 1),
};
}, [range, expanded, context, lines.length]);
const visible = lines.slice(shown.from, shown.to + 1);
const gutterWidth = `${String(lines.length).length + 1}ch`;
return (
<section aria-label={`Source: ${path}`} className={cn('card overflow-hidden', className)}>
<header className="flex flex-wrap items-center gap-x-3 gap-y-1 border-b border-border px-3 py-2">
<h3 className="nums min-w-0 flex-1 truncate font-mono text-xs text-muted" title={path}>
{path}
</h3>
{range && !expanded ? (
<button
type="button"
onClick={() => setExpanded(true)}
className="tap rounded-lg border border-border px-2.5 text-xs font-medium transition-colors duration-2 ease-enter hover:bg-surface-2"
>
Show all {lines.length} lines
</button>
) : null}
{range && expanded ? (
<button
type="button"
onClick={() => setExpanded(false)}
className="tap rounded-lg border border-border px-2.5 text-xs font-medium transition-colors duration-2 ease-enter hover:bg-surface-2"
>
Collapse to the marked part
</button>
) : null}
{href ? (
<a
href={href}
rel="noreferrer"
className="tap inline-flex items-center gap-1 text-xs font-medium text-accent-fg underline-offset-2 hover:underline"
>
Read the whole file
<ExternalLink className="h-3 w-3" aria-hidden="true" />
</a>
) : null}
</header>
<div className="max-h-96 overflow-auto">
<pre className="w-max min-w-full py-2 font-mono text-xs leading-relaxed">
<code>
{shown.from > 0 ? (
<span className="block px-3 text-muted" aria-hidden="true">
</span>
) : null}
{visible.map((line, offset) => {
const number = shown.from + offset;
const marked = range !== null && number >= range.start && number <= range.end;
return (
<span
key={number}
className={cn(
'block border-l-2 pr-4',
marked
? 'border-brand bg-accent-subtle/60 text-fg'
: 'border-transparent text-muted',
)}
>
{/* Sticky so the line numbers survive a horizontal scroll —
without it they slide out of view exactly when a long
line makes you want them. */}
<span
aria-hidden="true"
className="sticky left-0 inline-block select-none bg-surface pr-3 text-right text-muted"
style={{ width: gutterWidth }}
>
{number + 1}
</span>
{line === '' ? ' ' : line}
</span>
);
})}
{shown.to < lines.length - 1 ? (
<span className="block px-3 text-muted" aria-hidden="true">
</span>
) : null}
</code>
</pre>
</div>
</section>
);
}
+100
View File
@@ -0,0 +1,100 @@
import type { ComponentType } from 'react';
import { Link } from 'react-router-dom';
import { ArrowRight } from 'lucide-react';
import type { DemoMeta } from '@/lib/demo-kit/types';
import { cn } from '@/lib/utils';
import { DemoIcon } from './icons';
export interface DemoCardProps<T> {
meta: DemoMeta;
/** Defaults to the canonical demo route. */
href?: string;
/**
* The demo's OWN board, drawn compact, as the thumbnail. A screenshot would
* go stale the first time the board changes and nobody would notice; this
* cannot, because it is the same component the demo page renders.
*/
Surface?: ComponentType<{ state: T; compact?: boolean }>;
/** A representative state for the thumbnail — usually a solved board. */
thumbnailState?: T;
className?: string;
}
export function DemoCard<T>({
meta,
href,
Surface,
thumbnailState,
className,
}: DemoCardProps<T>) {
const to = href ?? `/demo/${meta.slug}`;
const isSpec = meta.status === 'spec';
const showSurface = Surface !== undefined && thumbnailState !== undefined;
return (
<article
className={cn(
'card group relative flex flex-col overflow-hidden transition-colors duration-2 ease-enter hover:border-brand/50',
className,
)}
>
<div className="flex items-start gap-3 p-4 pb-3">
<span className="grid h-10 w-10 shrink-0 place-items-center rounded-lg bg-accent-subtle text-accent-fg">
<DemoIcon name={meta.icon} className="h-5 w-5" />
</span>
<div className="min-w-0 flex-1">
<h3 className="text-base font-semibold leading-tight">
{/* Stretched link: the whole card is the hit target, but there is
still exactly ONE link in the accessibility tree for it. */}
<Link
to={to}
className="after:absolute after:inset-0 after:content-[''] focus-visible:outline-none"
>
{meta.title}
</Link>
</h3>
<p className="mt-0.5 text-sm leading-snug text-muted">{meta.tagline}</p>
</div>
{isSpec ? (
<span className="shrink-0 rounded-md border border-border px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted">
Spec
</span>
) : null}
</div>
{showSurface ? (
<div className="mx-4 overflow-hidden rounded-lg bg-surface-2 p-3">
{/* Decorative here: the title and tagline already name the demo, and
a screen reader has no use for a board with no run behind it. */}
<div aria-hidden="true">
<Surface state={thumbnailState as T} compact />
</div>
</div>
) : null}
<dl className="mt-3 flex flex-wrap gap-x-4 gap-y-1 px-4 text-xs">
<div className="flex gap-1">
<dt className="text-muted">For</dt>
<dd className="font-medium">{meta.persona}</dd>
</div>
<div className="flex gap-1">
<dt className="text-muted">Vertical</dt>
<dd className="font-medium capitalize">{meta.vertical.replace(/-/g, ' ')}</dd>
</div>
</dl>
<p className="mt-2 px-4 pb-4 text-xs leading-relaxed text-muted">
<span className="font-medium text-fg">Reward: </span>
{meta.rewardLine}
</p>
<p className="mt-auto flex items-center gap-1 border-t border-border px-4 py-2.5 text-sm font-medium text-accent-fg">
{isSpec ? 'Read the specification' : 'Open the demo'}
<ArrowRight
className="h-4 w-4 transition-transform duration-2 ease-enter group-hover:translate-x-0.5"
aria-hidden="true"
/>
</p>
</article>
);
}
+66
View File
@@ -0,0 +1,66 @@
import { ArrowRight, ShieldQuestion } from 'lucide-react';
import type { Limit } from '@/lib/demo-kit/types';
import { cn } from '@/lib/utils';
export interface LimitsCalloutProps {
limits: Limit[];
/**
* Turns a demo slug into something linkable. The shell has no registry
* dependency of its own, so the page that knows the routes supplies this.
*/
resolveDemo?: (slug: string) => { title: string; href: string } | undefined;
title?: string;
className?: string;
}
function defaultResolve(slug: string) {
return { title: slug, href: `/demo/${slug}` };
}
/**
* What this demo does not teach.
*
* Styled as confidence, not apology. A vendor who tells you the limits of their
* own demo before you find them is a vendor you believe about everything else
* on the page — and every limit here names the demo that closes it, so the list
* reads as a roadmap rather than a disclaimer.
*/
export function LimitsCallout({
limits,
resolveDemo = defaultResolve,
title = 'What this demo does not teach',
className,
}: LimitsCalloutProps) {
if (limits.length === 0) return null;
return (
<section aria-label={title} className={cn('card p-4', className)}>
<div className="flex items-center gap-2">
<ShieldQuestion className="h-4 w-4 text-muted" aria-hidden="true" />
<h3 className="text-sm font-semibold">{title}</h3>
</div>
<ul className="mt-3 space-y-3">
{limits.map((limit) => {
const target = limit.answeredBy ? resolveDemo(limit.answeredBy) : undefined;
return (
<li key={limit.text} className="border-l-2 border-border pl-3">
<p className="text-pretty text-sm leading-relaxed text-fg">{limit.text}</p>
{target ? (
<a
href={target.href}
className="mt-1 inline-flex items-center gap-1 text-xs font-medium text-accent-fg underline-offset-2 hover:underline"
>
Answered by {target.title}
<ArrowRight className="h-3 w-3" aria-hidden="true" />
</a>
) : (
<p className="mt-1 text-xs text-muted">
No demo answers this one yet. It is a real gap, not a rhetorical one.
</p>
)}
</li>
);
})}
</ul>
</section>
);
}
+116
View File
@@ -0,0 +1,116 @@
import {
CartesianGrid,
Line,
LineChart,
ReferenceLine,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import type { TooltipProps } from 'recharts';
import { formatNumber } from './format';
export interface MetricPoint {
/** Whatever the x axis is counting: checkpoint, step, arm name. */
x: string | number;
y: number;
}
export interface MetricChartProps {
data: MetricPoint[];
/** The rule the headline number is being compared against. */
baseline?: { value: number; label: string };
height: number;
/** Off under `prefers-reduced-motion`; recharts animates on mount by default. */
animate: boolean;
digits?: number;
}
/**
* The chart, in its own module so `React.lazy` can hold recharts out of the
* entry chunk. Nothing else in the shell may import this file directly — an
* ordinary import here defeats the whole arrangement and the entry chunk grows
* by ~100 kB without anyone noticing.
*
* Every colour is read from the token layer at paint time rather than passed in
* as a literal, so the chart follows the theme toggle without a re-render.
*/
export default function MetricChart({
data,
baseline,
height,
animate,
digits = 3,
}: MetricChartProps) {
return (
<div style={{ height }} className="w-full">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={data} margin={{ top: 8, right: 12, bottom: 4, left: 4 }}>
<CartesianGrid stroke="hsl(var(--border))" strokeDasharray="2 4" vertical={false} />
<XAxis
dataKey="x"
tick={{ fill: 'hsl(var(--muted))', fontSize: 11 }}
tickLine={false}
axisLine={{ stroke: 'hsl(var(--border))' }}
minTickGap={12}
/>
<YAxis
tick={{ fill: 'hsl(var(--muted))', fontSize: 11 }}
tickLine={false}
axisLine={false}
width={40}
tickFormatter={(value: number) => formatNumber(value, digits > 2 ? 2 : digits)}
/>
{baseline ? (
<ReferenceLine
y={baseline.value}
stroke="hsl(var(--muted))"
strokeDasharray="5 4"
label={{
value: baseline.label,
position: 'insideTopLeft',
fill: 'hsl(var(--muted))',
fontSize: 11,
}}
/>
) : null}
<Tooltip
cursor={{ stroke: 'hsl(var(--border))' }}
content={(props: TooltipProps<number, string>) => (
<ChartTooltip {...props} digits={digits} />
)}
/>
<Line
type="monotone"
dataKey="y"
stroke="hsl(var(--accent))"
strokeWidth={2}
dot={{ r: 2.5, fill: 'hsl(var(--accent))', strokeWidth: 0 }}
activeDot={{ r: 4 }}
isAnimationActive={animate}
animationDuration={400}
/>
</LineChart>
</ResponsiveContainer>
</div>
);
}
function ChartTooltip({
active,
payload,
label,
digits,
}: TooltipProps<number, string> & { digits: number }) {
if (!active || !payload || payload.length === 0) return null;
const value = payload[0]?.value;
return (
<div className="card px-2.5 py-1.5 text-xs shadow-md">
<p className="nums text-muted">{String(label)}</p>
<p className="nums font-mono font-semibold">
{typeof value === 'number' ? formatNumber(value, digits) : '—'}
</p>
</div>
);
}
+126
View File
@@ -0,0 +1,126 @@
import { Suspense, lazy, useMemo } from 'react';
import { cn } from '@/lib/utils';
import { formatNumber, formatSigned, usePrefersReducedMotion } from './format';
import type { MetricPoint } from './MetricChart';
import { EditedChip } from './StatStrip';
export type { MetricPoint } from './MetricChart';
// Lazy, and deliberately not a static import: recharts is ~100 kB gzipped and
// exactly one surface on the site uses it. `vite.config.ts` also names it as
// its own manual chunk, so this stays out of the entry bundle in both dev and
// production builds.
const MetricChart = lazy(() => import('./MetricChart'));
const CHART_HEIGHT = 168;
export interface MetricMoverProps {
/** The one number a reader would repeat in a meeting. */
label: string;
value: number;
digits?: number;
unit?: string;
/** The dashed rule: what the number was before, or what counts as par. */
baseline?: { value: number; label: string };
series?: MetricPoint[];
/** One sentence on what moved it. Not a caveat — the mechanism. */
caption?: string;
edited?: boolean;
className?: string;
}
/**
* The headline number, with the line that shows it moving.
*
* The reference rule is not decoration: a number on its own is a claim, and a
* number against the line it used to sit on is evidence. If a demo has no
* baseline to draw, it should not be using this component.
*/
export function MetricMover({
label,
value,
digits = 3,
unit,
baseline,
series,
caption,
edited = false,
className,
}: MetricMoverProps) {
const reducedMotion = usePrefersReducedMotion();
const delta = baseline ? value - baseline.value : null;
const points = useMemo(() => series ?? [], [series]);
return (
<section aria-label={label} className={cn('card p-4', className)}>
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
<h3 className="text-sm font-medium text-muted">{label}</h3>
{edited ? <EditedChip /> : null}
</div>
<p className="mt-1 flex items-baseline gap-2">
<span className="nums text-4xl font-semibold leading-none tracking-tight">
{formatNumber(value, digits)}
</span>
{unit ? <span className="text-sm text-muted">{unit}</span> : null}
{delta !== null ? (
<span
className={cn(
'nums text-sm font-medium',
delta > 0 ? 'text-positive' : delta < 0 ? 'text-danger' : 'text-muted',
)}
>
{formatSigned(delta, digits)} vs {baseline?.label}
</span>
) : null}
</p>
{points.length > 1 ? (
<div className="mt-3">
<Suspense
fallback={
// Reserve the exact chart height. A chart that pops in and pushes
// the caption down is the layout shift this whole page is trying
// not to have.
<div
style={{ height: CHART_HEIGHT }}
className="w-full rounded-lg bg-surface-2"
aria-hidden="true"
/>
}
>
<MetricChart
data={points}
{...(baseline ? { baseline } : {})}
height={CHART_HEIGHT}
animate={!reducedMotion}
digits={digits}
/>
</Suspense>
{/* The chart is a picture of the table; the table is the accessible
version of the picture. Both are the same numbers. */}
<details className="mt-1">
<summary className="tap inline-flex cursor-pointer items-center text-xs text-muted hover:text-fg">
Show these points as a table
</summary>
<table className="nums mt-1.5 w-full border-collapse font-mono text-xs">
<tbody className="divide-y divide-border">
{points.map((point) => (
<tr key={String(point.x)}>
<th scope="row" className="py-1 text-left font-normal text-muted">
{String(point.x)}
</th>
<td className="py-1 text-right">{formatNumber(point.y, digits)}</td>
</tr>
))}
</tbody>
</table>
</details>
</div>
) : null}
{caption ? (
<p className="mt-3 text-sm leading-relaxed text-muted">{caption}</p>
) : null}
</section>
);
}
+128
View File
@@ -0,0 +1,128 @@
import { useState } from 'react';
import { Check, Copy, ExternalLink } from 'lucide-react';
import type { Provenance, RunRef } from '@/lib/demo-kit/types';
import { cn } from '@/lib/utils';
import { formatDate } from './format';
export interface ProvenanceCardProps {
provenance: Provenance;
/** The run currently on screen, if the page is showing one. */
run?: RunRef;
className?: string;
}
type CopyState = 'idle' | 'copied' | 'failed';
/**
* Where these numbers came from, and the command that reproduces them.
*
* The command is the load-bearing element. Everything else on this page is us
* telling you what happened; this is the line you paste into your own terminal
* to find out for yourself, which is why it is verbatim and copy-pasteable
* rather than prettified into something that would not actually run.
*/
export function ProvenanceCard({ provenance, run, className }: ProvenanceCardProps) {
const [copyState, setCopyState] = useState<CopyState>('idle');
const copy = async () => {
try {
// `navigator.clipboard` is undefined on a non-secure origin, which is
// exactly what a colleague testing over a LAN IP will hit. Fail visibly.
await navigator.clipboard.writeText(provenance.command);
setCopyState('copied');
window.setTimeout(() => setCopyState('idle'), 2000);
} catch {
setCopyState('failed');
}
};
const rows: { label: string; value: string; mono?: boolean }[] = [
{ label: 'Environment package', value: provenance.envPackage, mono: true },
{ label: 'Taskset', value: provenance.tasksetId, mono: true },
{ label: 'verifiers version', value: provenance.verifiersVersion, mono: true },
];
if (run) {
rows.push(
{ label: 'Model', value: run.model, mono: true },
{ label: 'Captured', value: formatDate(run.capturedAt) },
{ label: 'Seed', value: String(run.seed), mono: true },
);
if (run.intervention) rows.push({ label: 'Intervention', value: run.intervention });
}
return (
<section aria-label="Provenance" className={cn('card overflow-hidden', className)}>
<header className="border-b border-border px-3 py-2">
<h3 className="text-sm font-semibold">Provenance</h3>
</header>
<dl className="divide-y divide-border">
{rows.map((row) => (
<div key={row.label} className="flex items-baseline gap-3 px-3 py-2">
<dt className="flex-1 text-sm text-muted">{row.label}</dt>
<dd
className={cn(
'min-w-0 break-all text-right text-sm',
row.mono && 'nums font-mono text-xs',
)}
>
{row.value}
</dd>
</div>
))}
</dl>
<div className="border-t border-border p-3">
<div className="flex items-center justify-between gap-2">
<h4 className="text-xs font-semibold uppercase tracking-wide text-muted">
Run it yourself
</h4>
<button
type="button"
onClick={copy}
className="tap inline-flex items-center gap-1.5 rounded-lg border border-border px-2.5 text-xs font-medium transition-colors duration-2 ease-enter hover:bg-surface-2"
>
{copyState === 'copied' ? (
<Check className="h-3.5 w-3.5 text-positive" aria-hidden="true" />
) : (
<Copy className="h-3.5 w-3.5" aria-hidden="true" />
)}
{copyState === 'copied' ? 'Copied' : 'Copy'}
</button>
</div>
<pre className="mt-2 overflow-x-auto rounded-lg bg-surface-2 p-3">
<code className="font-mono text-xs leading-relaxed">{provenance.command}</code>
</pre>
<p role="status" className="mt-1 text-xs text-muted">
{copyState === 'copied'
? 'Command copied to your clipboard.'
: copyState === 'failed'
? 'Your browser blocked clipboard access — select the command above and copy it manually.'
: ''}
</p>
</div>
{provenance.credits.length > 0 ? (
<div className="border-t border-border px-3 py-2.5">
<h4 className="text-xs font-semibold uppercase tracking-wide text-muted">
Built on
</h4>
<ul className="mt-1.5 flex flex-wrap gap-x-4 gap-y-1">
{provenance.credits.map((credit) => (
<li key={credit.href}>
<a
href={credit.href}
className="inline-flex items-center gap-1 text-sm text-accent-fg underline-offset-2 hover:underline"
rel="noreferrer"
>
{credit.label}
<ExternalLink className="h-3 w-3" aria-hidden="true" />
</a>
</li>
))}
</ul>
</div>
) : null}
</section>
);
}
+340
View File
@@ -0,0 +1,340 @@
import { useMemo, useState } from 'react';
import * as Slider from '@radix-ui/react-slider';
import { ArrowDown, ArrowUp, Minus, RotateCcw } from 'lucide-react';
import type { RewardSpec, RewardValues } from '@/lib/demo-kit/types';
import { cn } from '@/lib/utils';
import { formatNumber, formatOrDash } from './format';
import { scoreReward, shippedWeights } from './reward-math';
import { EditedChip } from './StatStrip';
/** One recorded arm — a run, or a group of runs already reduced to one score. */
export interface RewardArm {
id: string;
label: string;
/** The environment's per-component scores. Re-weighted, never re-run. */
values: RewardValues;
note?: string;
}
export interface RewardPreset {
id: string;
label: string;
description?: string;
weights: Record<string, number>;
}
export interface RewardEditorProps {
spec: RewardSpec;
arms: RewardArm[];
/** Two is the right number. More and the visitor reads instead of playing. */
presets?: RewardPreset[];
onWeightsChange?: (weights: Record<string, number>, edited: boolean) => void;
className?: string;
}
const STEP = 0.05;
/**
* Presets built from the spec's own labels, for a demo that does not supply
* its own. Both are stated as the choice a buyer would actually argue for in a
* meeting, not as "preset A" and "preset B".
*/
function derivePresets(spec: RewardSpec): RewardPreset[] {
const shipped = shippedWeights(spec);
const counterweights = spec.components.filter((c) => c.role === 'counterweight');
const objectives = spec.components.filter((c) => c.role === 'objective');
const presets: RewardPreset[] = [
{
id: 'shipped',
label: 'What we ship',
description: 'The weights in the environment as committed.',
weights: shipped,
},
];
const firstObjective = objectives[0];
if (counterweights.length > 0 && firstObjective) {
const onlyObjective = { ...shipped };
for (const component of counterweights) onlyObjective[component.key] = 0;
presets.push({
id: 'objective-only',
label: `${firstObjective.label} at any cost`,
description: `Drops ${counterweights
.map((c) => c.label.toLowerCase())
.join(' and ')} to zero.`,
weights: onlyObjective,
});
const doubled = { ...shipped };
for (const component of counterweights) doubled[component.key] = component.weight * 2;
presets.push({
id: 'counterweight-heavy',
label: `Double ${counterweights[0]?.label.toLowerCase() ?? 'the counterweight'}`,
description: 'What a risk-averse buyer would ask for.',
weights: doubled,
});
}
return presets;
}
function weightsEqual(a: Record<string, number>, b: Record<string, number>): boolean {
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
for (const key of keys) {
if (Math.abs((a[key] ?? 0) - (b[key] ?? 0)) > 1e-9) return false;
}
return true;
}
/**
* Change what "good" means and watch the ranking move.
*
* The honesty problem this component has to solve: re-weighting recorded scores
* is NOT training. It shows you the ranking a different reward would have
* produced over these exact attempts; it cannot show you the different attempts
* a model trained on that reward would have made. That distinction is the
* permanent caption at the bottom, and it is not collapsible.
*/
export function RewardEditor({
spec,
arms,
presets,
onWeightsChange,
className,
}: RewardEditorProps) {
const shipped = useMemo(() => shippedWeights(spec), [spec]);
const [weights, setWeights] = useState<Record<string, number>>(shipped);
const effectivePresets = useMemo(() => presets ?? derivePresets(spec), [presets, spec]);
const bounds = useMemo(() => {
const values = spec.components.map((c) => c.weight);
const max = Math.max(2, ...values.map((v) => Math.ceil(Math.abs(v) * 2)));
const min = Math.min(0, ...values.map((v) => Math.floor(v)));
return { min, max };
}, [spec]);
const edited = !weightsEqual(weights, shipped);
const apply = (next: Record<string, number>) => {
setWeights(next);
onWeightsChange?.(next, !weightsEqual(next, shipped));
};
const ranked = useMemo(() => {
const shippedTotals = new Map(
arms.map((arm) => [arm.id, scoreReward(spec, arm.values).total]),
);
const rows = arms.map((arm) => ({
arm,
total: scoreReward(spec, arm.values, weights).total,
shippedTotal: shippedTotals.get(arm.id) ?? null,
}));
// Nulls sort last: an unscored arm is not a zero-scoring arm.
const byTotal = (a: { total: number | null }, b: { total: number | null }) => {
if (a.total === null && b.total === null) return 0;
if (a.total === null) return 1;
if (b.total === null) return -1;
return b.total - a.total;
};
const shippedOrder = [...rows]
.sort((a, b) => byTotal({ total: a.shippedTotal }, { total: b.shippedTotal }))
.map((row) => row.arm.id);
return [...rows].sort(byTotal).map((row, index) => ({
...row,
rank: index + 1,
shippedRank: shippedOrder.indexOf(row.arm.id) + 1,
}));
}, [arms, spec, weights]);
const span = useMemo(() => {
const totals = ranked.map((row) => row.total).filter((t): t is number => t !== null);
if (totals.length === 0) return { lo: 0, hi: 1 };
const lo = Math.min(0, ...totals);
const hi = Math.max(...totals);
return { lo, hi: hi === lo ? lo + 1 : hi };
}, [ranked]);
const leader = ranked[0];
return (
<div className={cn('grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]', className)}>
<section aria-label="Reward weights" className="card p-3">
<div className="flex flex-wrap items-center gap-2">
<h3 className="text-sm font-semibold">Change what good means</h3>
{edited ? <EditedChip /> : null}
<button
type="button"
onClick={() => apply(shipped)}
disabled={!edited}
className="tap ml-auto inline-flex items-center gap-1.5 rounded-lg border border-border px-2.5 text-xs font-medium transition-colors duration-2 ease-enter hover:bg-surface-2 disabled:opacity-40"
>
<RotateCcw className="h-3.5 w-3.5" aria-hidden="true" />
Reset to shipped
</button>
</div>
<div className="mt-2 flex flex-wrap gap-2">
{effectivePresets.map((preset) => {
const active = weightsEqual(weights, preset.weights);
return (
<button
key={preset.id}
type="button"
onClick={() => apply({ ...preset.weights })}
aria-pressed={active}
title={preset.description ?? preset.label}
className={cn(
'tap rounded-lg border px-3 text-left text-xs font-medium transition-colors duration-2 ease-enter',
active
? 'border-brand bg-accent-subtle text-accent-fg'
: 'border-border hover:bg-surface-2',
)}
>
{preset.label}
</button>
);
})}
</div>
<div className="mt-4 space-y-4">
{spec.components.map((component) => {
const value = weights[component.key] ?? component.weight;
const changed = Math.abs(value - component.weight) > 1e-9;
return (
<div key={component.key}>
<div className="flex items-baseline justify-between gap-2">
{/* A <label htmlFor> would point at Radix's root <span>,
which is not a labelable element — the association would
silently do nothing. The thumb takes its name from this
text via aria-labelledby instead. */}
<span
id={`weight-label-${component.key}`}
className="text-sm font-medium text-fg"
>
{component.label}
</span>
<span
className={cn(
'nums font-mono text-sm',
changed ? 'font-semibold text-accent-fg' : 'text-muted',
)}
>
{formatNumber(value, 2)}
</span>
</div>
<p className="mb-1.5 text-xs leading-snug text-muted">{component.description}</p>
<Slider.Root
className="relative flex h-6 w-full touch-none select-none items-center"
min={bounds.min}
max={bounds.max}
step={STEP}
value={[value]}
onValueChange={(next) =>
apply({ ...weights, [component.key]: next[0] ?? component.weight })
}
>
<Slider.Track className="relative h-1.5 w-full grow rounded-full bg-surface-2">
<Slider.Range className="absolute h-full rounded-full bg-brand" />
</Slider.Track>
{/* 44px of hit area around a 16px dot: the visible thumb is
small enough to read the track under it, and still catches
a thumb on a phone. */}
<Slider.Thumb
aria-labelledby={`weight-label-${component.key}`}
className="block h-6 w-6 rounded-full border-4 border-brand bg-surface shadow-sm"
/>
</Slider.Root>
</div>
);
})}
</div>
</section>
<section aria-label="Ranking under this reward" className="card flex flex-col p-3">
<h3 className="text-sm font-semibold">
Ranking under this reward {edited ? <EditedChip className="ml-1 align-middle" /> : null}
</h3>
<ol className="mt-3 space-y-2">
{ranked.map((row) => {
const moved = row.rank - row.shippedRank;
const width =
row.total === null
? 0
: Math.max(2, ((row.total - span.lo) / (span.hi - span.lo)) * 100);
return (
<li
key={row.arm.id}
className={cn(
'rounded-lg border p-2.5 transition-colors duration-3 ease-enter',
row.rank === 1 ? 'border-brand bg-accent-subtle/50' : 'border-border',
)}
>
<div className="flex items-baseline gap-2">
<span className="nums text-sm font-semibold text-muted">{row.rank}</span>
<span className="min-w-0 flex-1 truncate text-sm font-medium">
{row.arm.label}
</span>
<RankMove moved={moved} />
<span className="nums font-mono text-sm font-semibold">
{formatOrDash(row.total)}
</span>
</div>
<div
aria-hidden="true"
className="mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-surface-2"
>
<div
className="h-full rounded-full bg-brand transition-[width] duration-3 ease-enter"
style={{ width: `${width}%` }}
/>
</div>
{row.arm.note ? (
<p className="mt-1 text-xs text-muted">{row.arm.note}</p>
) : null}
</li>
);
})}
</ol>
{/* User-initiated, so it is safe to announce here without fighting the
shell's step-change region: the two never fire from one action. */}
<p role="status" className="sr-only">
{leader
? `Leading under this reward: ${leader.arm.label}, ${formatOrDash(leader.total)}.`
: 'No arms to rank.'}
</p>
<p className="mt-auto pt-3 text-xs leading-relaxed text-muted">
We re-scored the same recorded attempts under your reward. Training on it would change
the behaviour, not just the ranking.
</p>
</section>
</div>
);
}
function RankMove({ moved }: { moved: number }) {
if (moved === 0) {
return (
<span className="inline-flex items-center text-muted" title="Same rank as shipped">
<Minus className="h-3.5 w-3.5" aria-hidden="true" />
<span className="sr-only">unchanged</span>
</span>
);
}
const up = moved < 0;
return (
<span
className={cn('nums inline-flex items-center text-xs', up ? 'text-positive' : 'text-danger')}
title={`${Math.abs(moved)} place${Math.abs(moved) === 1 ? '' : 's'} ${up ? 'up' : 'down'} from the shipped reward`}
>
{up ? (
<ArrowUp className="h-3.5 w-3.5" aria-hidden="true" />
) : (
<ArrowDown className="h-3.5 w-3.5" aria-hidden="true" />
)}
{Math.abs(moved)}
<span className="sr-only">{up ? ' places up' : ' places down'}</span>
</span>
);
}
+384
View File
@@ -0,0 +1,384 @@
/**
* A hand-written demo, used to build and exercise the shell without waiting for
* real fixtures.
*
* It is NOT a fixture and it is not shipped as a demo: nothing in
* `src/demos/` imports it, and the shell mounts it only for the reserved slug
* `__mock` in a dev build. It exists so that every surface in this directory
* has something complete to render — including the awkward cases a real trace
* eventually produces: a step with no reasoning, a null model call, a
* not-scored reward component, and a truncated run.
*/
import type {
DemoEpisode,
DemoModule,
DemoStep,
RewardValues,
RunRef,
} from '@/lib/demo-kit/types';
import { cn } from '@/lib/utils';
export type MarkKind = 'exact' | 'present' | 'absent';
export interface MockGuess {
word: string;
marks: MarkKind[];
}
export interface MockState {
answer: string;
guesses: MockGuess[];
solved: boolean;
}
const ANSWER = 'CRANE';
const MAX_GUESSES = 6;
/** Standard Wordle marking, duplicates and all. */
function mark(guess: string, answer: string): MarkKind[] {
const marks: MarkKind[] = Array.from({ length: guess.length }, () => 'absent');
const pool = new Map<string, number>();
for (let i = 0; i < answer.length; i += 1) {
const letter = answer[i] as string;
if (guess[i] === letter) {
marks[i] = 'exact';
} else {
pool.set(letter, (pool.get(letter) ?? 0) + 1);
}
}
for (let i = 0; i < guess.length; i += 1) {
if (marks[i] === 'exact') continue;
const letter = guess[i] as string;
const left = pool.get(letter) ?? 0;
if (left > 0) {
marks[i] = 'present';
pool.set(letter, left - 1);
}
}
return marks;
}
const TILE: Record<MarkKind, string> = {
exact: 'bg-tile-exact text-tile-exact-fg',
present: 'bg-tile-present text-tile-present-fg',
absent: 'bg-tile-absent text-tile-absent-fg',
};
const GLYPH: Record<MarkKind, string> = { exact: '●', present: '◐', absent: '○' };
function MockSurface({ state, compact = false }: { state: MockState; compact?: boolean }) {
const rows = Array.from({ length: MAX_GUESSES }, (_, index) => state.guesses[index]);
return (
<div
className={cn('grid w-fit gap-1', compact && 'gap-0.5')}
role="img"
aria-label={
state.guesses.length === 0
? 'Empty board'
: `Board after ${state.guesses.length} guesses: ${state.guesses
.map((guess) => guess.word)
.join(', ')}`
}
>
{rows.map((guess, rowIndex) => (
<div key={rowIndex} className={cn('flex gap-1', compact && 'gap-0.5')}>
{Array.from({ length: 5 }, (_unused, colIndex) => {
const letter = guess?.word[colIndex];
const kind = guess?.marks[colIndex];
return (
<span
key={colIndex}
className={cn(
'grid place-items-center rounded-md border border-border font-semibold uppercase',
compact ? 'h-3.5 w-3.5 text-[7px]' : 'h-9 w-9 text-base',
kind ? TILE[kind] : 'bg-surface-2 text-muted',
)}
>
{compact ? (kind ? GLYPH[kind] : '') : (letter ?? '')}
</span>
);
})}
</div>
))}
</div>
);
}
const REWARD_SOURCE = `import verifiers as vf
def reward_solved(state) -> float:
"""1.0 if the answer was found, else 0.0."""
return 1.0 if state["solved"] else 0.0
# --8<-- efficiency
def reward_efficiency(state) -> float:
"""Pays for finding it early, and pays nothing for finding it late.
This is the counterweight. Without it the highest-scoring policy is one
that burns every guess it is allowed, because the objective alone cannot
tell a lucky third guess from a grudging sixth.
"""
if not state["solved"]:
return 0.0
used = len(state["guesses"])
return max(0.0, (MAX_GUESSES - used + 1) / MAX_GUESSES)
# --8<--
def reward_legal(state) -> float:
"""Every guess was a real five-letter word in the allowed list."""
return 1.0 if all(g in ALLOWED for g in state["guesses"]) else 0.0
`;
function turnBoard(episode: DemoEpisode, upto: number): MockState {
const guesses: MockGuess[] = [];
for (let i = 0; i <= upto && i < episode.turns.length; i += 1) {
const info = episode.turns[i]?.info;
const word = typeof info?.['guess'] === 'string' ? info['guess'] : null;
if (!word) continue;
guesses.push({ word, marks: mark(word, ANSWER) });
}
const last = guesses[guesses.length - 1];
return { answer: ANSWER, guesses, solved: last?.word === ANSWER };
}
function adapt(episode: DemoEpisode): DemoStep<MockState>[] {
return episode.turns.map((turn, index) => {
const state = turnBoard(episode, index);
const last = state.guesses[state.guesses.length - 1];
const word = last?.word ?? 'no guess';
const exact = last?.marks.filter((m) => m === 'exact').length ?? 0;
return {
index,
state,
reply: turn.reply,
reasoning: turn.reasoning,
call: turn.call,
announce: state.solved
? `Guess ${index + 1}: ${word}. Solved.`
: `Guess ${index + 1}: ${word}. ${exact} letters in the right place.`,
caption: word.toLowerCase(),
};
});
}
function verify(episode: DemoEpisode): RewardValues | null {
if (episode.truncated) return null;
const final = turnBoard(episode, episode.turns.length - 1);
const used = final.guesses.length;
return {
solved: final.solved ? 1 : 0,
efficiency: final.solved ? Math.max(0, (MAX_GUESSES - used + 1) / MAX_GUESSES) : 0,
legal: 1,
};
}
export const mockDemo: DemoModule<MockState> = {
meta: {
slug: '__mock',
title: 'Five-letter word game (mock)',
tagline: 'An agent guesses a hidden five-letter word from letter feedback.',
vertical: 'reference',
status: 'live',
order: 0,
icon: 'Grid3x3',
persona: 'Anyone deciding whether to fund an environment',
rewardLine: 'Pays for solving; takes away for stalling.',
ogImage: '/og/mock.png',
},
narrative: {
thesis:
'An environment is an eval you can take the gradient of. The task, the legal moves and the grader are all code — so you can change what "good" means and watch the number move.',
anxiety: 'Is this a benchmark I read, or a thing I can actually change?',
beats: [
{ id: 'hero', title: 'The run', claim: 'This is a recorded rollout, not a live request.', surface: 'hero' },
{ id: 'anatomy', title: 'The machine', claim: 'Four boxes: task, legal actions, grader, score.', surface: 'anatomy' },
{ id: 'play', title: 'Watch it think', claim: 'Every move has a reason and a cost, both recorded.', surface: 'split-play' },
{ id: 'reward', title: 'Change what good means', claim: 'Move a weight and the ranking moves with it.', surface: 'reward-editor' },
{ id: 'metric', title: 'The number that moves', claim: 'The score is a measurement, not a claim.', surface: 'metric' },
{ id: 'receipt', title: 'The receipts', claim: 'Every number here has a command that reproduces it.', surface: 'receipt' },
{ id: 'limits', title: 'What this does not teach', claim: 'A word game is not your business process.', surface: 'limits' },
],
limits: [
{
text: 'A five-letter word has one right answer. Most business decisions do not, and a grader that pretends otherwise scores confidence rather than correctness.',
answeredBy: 'claims-triage',
},
{
text: 'Nothing here has a cost of being wrong. A real reward has to price the mistake, not just count the win.',
},
],
},
reward: {
components: [
{
key: 'solved',
label: 'Found the word',
description: 'One point if the hidden word was guessed within six tries.',
weight: 1,
role: 'objective',
},
{
key: 'efficiency',
label: 'Found it early',
description: 'Pays more the fewer guesses it took. Zero if it never got there.',
weight: 0.5,
role: 'counterweight',
},
{
key: 'legal',
label: 'Played legal words',
description: 'Every guess was a real five-letter word from the allowed list.',
weight: 0.25,
role: 'gate',
},
],
metrics: [
{ key: 'guesses_used', label: 'Guesses used', description: 'How many of the six were spent.' },
{ key: 'unique_letters', label: 'Unique letters tried', description: 'Breadth of the search.' },
],
source: { path: 'envs/wordle_five/wordle_five/rewards.py', code: REWARD_SOURCE, marker: '--8<-- efficiency' },
},
provenance: {
envPackage: 'wordle_five',
tasksetId: 'wordle-five-v0-mock',
verifiersVersion: '0.1.0',
command: 'uv run vf-eval wordle-five -n 8 -m gpt-4.1-mini --seed 7',
credits: [
{ label: 'verifiers', href: 'https://github.com/PrimeIntellect-ai/verifiers' },
],
},
anatomy: {
task: 'Guess a hidden five-letter word in six tries, using the coloured feedback from each guess.',
actions: 'One legal five-letter word per turn, drawn from the allowed list. Nothing else is a move.',
grader: 'Deterministic Python: it marks the guess against the answer, checks legality, and scores the episode. No model judges it.',
score: 'A weighted sum: one point for finding the word, half a point scaled by how early, a quarter as a legality gate.',
},
adapt,
Surface: MockSurface,
verify,
};
function call(prompt: number, completion: number, reasoning: number | null, ms: number) {
return {
promptTokens: prompt,
completionTokens: completion,
reasoningTokens: reasoning,
durationMs: ms,
finishReason: 'stop',
};
}
export const mockEpisodes: Record<string, DemoEpisode> = {
'mock-base': {
runId: 'mock-base',
seed: 7,
model: 'gpt-4.1-mini',
capturedAt: '2026-08-12',
rewards: { solved: 1, efficiency: 0.5, legal: 1 },
metrics: { guesses_used: 4, unique_letters: 14 },
outcome: 'solved',
turns: [
{
reply: 'SLATE',
reasoning:
'Opening with a word that covers three of the five most common letters and two common consonants. I want information, not a lucky hit.',
call: call(412, 6, 84, 910),
info: { guess: 'SLATE' },
},
{
reply: 'TRACE',
reasoning: null,
call: call(486, 6, null, 640),
info: { guess: 'TRACE' },
},
{
reply: 'BRACE',
reasoning:
'A and E are placed. R is in the word but not where I put it. That leaves the first two positions to resolve.',
call: call(551, 6, 121, 1180),
info: { guess: 'BRACE' },
},
{
reply: 'CRANE',
reasoning: 'Only one word fits every constraint now.',
call: call(618, 6, 96, 720),
info: { guess: 'CRANE' },
},
],
},
'mock-prompted': {
runId: 'mock-prompted',
seed: 7,
model: 'gpt-4.1-mini',
capturedAt: '2026-08-12',
// Written as the expression the environment computes, not as a decimal
// literal: a hand-rounded 0.8333 would fail our own verifier.
rewards: { solved: 1, efficiency: 5 / 6, legal: 1 },
metrics: { guesses_used: 2, unique_letters: 9 },
outcome: 'solved',
turns: [
{
reply: 'TRACE',
reasoning: 'Told to open with maximum letter coverage and to commit early once a word fits.',
call: call(455, 6, 140, 1020),
info: { guess: 'TRACE' },
},
{
reply: 'CRANE',
reasoning: 'Every constraint is satisfied by exactly one candidate.',
call: call(512, 6, 88, 660),
info: { guess: 'CRANE' },
},
],
},
'mock-truncated': {
runId: 'mock-truncated',
seed: 11,
model: 'gpt-4.1-mini',
capturedAt: '2026-08-12',
// A run that hit the turn cap: the environment never reached a terminal
// state, so two of the three components were never scored.
rewards: { solved: null, efficiency: null, legal: 1 },
metrics: { guesses_used: 2, unique_letters: 8 },
truncated: true,
outcome: 'aborted',
turns: [
{ reply: 'AUDIO', reasoning: null, call: call(410, 6, null, 880), info: { guess: 'AUDIO' } },
{ reply: null, reasoning: null, call: null, info: {} },
],
},
};
export const mockRuns: RunRef[] = [
{
id: 'mock-base',
label: 'Out of the box',
path: '/traces/__mock/base.json',
kind: 'recorded',
model: 'gpt-4.1-mini',
capturedAt: '2026-08-12',
seed: 7,
},
{
id: 'mock-prompted',
label: 'With a strategy prompt',
path: '/traces/__mock/prompted.json',
kind: 'intervened',
intervention: 'System prompt rewritten',
model: 'gpt-4.1-mini',
capturedAt: '2026-08-12',
seed: 7,
},
{
id: 'mock-truncated',
label: 'Hit the turn cap',
path: '/traces/__mock/truncated.json',
kind: 'recorded',
model: 'gpt-4.1-mini',
capturedAt: '2026-08-12',
seed: 11,
},
];
+56 -35
View File
@@ -8,9 +8,9 @@ const STORAGE_KEY = 'pig-demo:contrast';
/**
* `localStorage` is not always readable. In a cross-origin iframe with third-
* party storage blocked, and in Safari private mode, the getter itself THROWS
* rather than returning null — so every access has to be wrapped, not just
* null-checked. Unreadable storage means "off", never a crash.
* party storage blocked, and in Safari's private mode, the accessor itself
* THROWS rather than returning null — so every access has to be wrapped, not
* just null-checked. Unreadable storage means "off", never a crash.
*/
function readStored(): boolean {
try {
@@ -24,51 +24,72 @@ function writeStored(high: boolean): void {
try {
window.localStorage.setItem(STORAGE_KEY, high ? 'high' : 'normal');
} catch {
/* Preference is session-only here. The toggle still works. */
/* Preference is session-only in this context. The toggle still works. */
}
}
/**
* One value shared by every mounted toggle, not one `useState` each.
*
* The header renders this control twice — once in the desktop bar, once inside
* the mobile sheet — and only one of them is ever visible. With local state the
* hidden one keeps a stale `aria-pressed`, so a visitor who toggles on a phone
* and then rotates into the desktop layout is told the setting is off while the
* page is plainly showing it on.
*/
let high = false;
let initialised = false;
const listeners = new Set<() => void>();
function applyToRoot(next: boolean): void {
const root = document.documentElement;
// Removed rather than set to "normal": the CSS keys off
// `:root[data-contrast='high']`, and a leftover attribute makes the DOM lie
// about which palette is actually applied.
if (next) root.setAttribute('data-contrast', 'high');
else root.removeAttribute('data-contrast');
}
function setHigh(next: boolean): void {
high = next;
applyToRoot(next);
writeStored(next);
for (const listener of listeners) listener();
}
function subscribe(listener: () => void): () => void {
if (!initialised) {
initialised = true;
high = readStored();
applyToRoot(high);
}
listeners.add(listener);
return () => {
listeners.delete(listener);
};
}
export function ContrastToggle({ className }: { className?: string }) {
const [high, setHigh] = React.useState(false);
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
const stored = readStored();
setHigh(stored);
setMounted(true);
}, []);
React.useEffect(() => {
if (!mounted) return;
const root = document.documentElement;
// Removing the attribute rather than setting it to "normal": the CSS keys
// off `:root[data-contrast='high']`, and leaving a stale attribute behind
// makes the DOM lie about the palette that is actually applied.
if (high) root.setAttribute('data-contrast', 'high');
else root.removeAttribute('data-contrast');
}, [high, mounted]);
// The server snapshot is `false` so a prerendered page never claims a
// preference it cannot know; the real value lands on the first subscribe.
const isHigh = React.useSyncExternalStore(
subscribe,
() => high,
() => false,
);
return (
<Button
type="button"
variant="ghost"
size="icon-touch"
className={cn('lg:size-9', high && 'bg-accent-subtle text-accent-fg', className)}
aria-pressed={high}
aria-label={
high ? 'High contrast tiles on. Turn off.' : 'High contrast tiles off. Turn on.'
}
className={cn('lg:size-9', isHigh && 'bg-accent-subtle text-accent-fg', className)}
aria-pressed={isHigh}
aria-label={isHigh ? 'High contrast tiles on. Turn off.' : 'High contrast tiles off. Turn on.'}
title="High contrast tiles"
onClick={() => {
const next = !high;
setHigh(next);
writeStored(next);
}}
onClick={() => setHigh(!isHigh)}
>
<Contrast aria-hidden="true" />
<span aria-live="polite" className="sr-only">
{mounted ? (high ? 'High contrast on' : 'High contrast off') : ''}
</span>
</Button>
);
}
+13 -3
View File
@@ -247,7 +247,14 @@ function TasksetSource({ className }: { className?: string }) {
<p className="text-xs font-semibold uppercase tracking-wider text-muted">
The actual taskset
</p>
<pre className="overflow-x-auto text-[11px] leading-relaxed text-fg">
{/*
Wrapped, not scrolled. The source is quoted verbatim — reformatting it
to fit would make it stop being a quote — and the widest line is a third
wider than this column at any font size worth reading. In a panel whose
entire job is "this is real code", a visible wrap beats a third of the
line hidden behind a scrollbar nobody in a boardroom will drag.
*/}
<pre className="overflow-x-auto whitespace-pre-wrap break-words text-[11px] leading-relaxed text-fg">
<code className="font-mono">{TASKSET_SOURCE}</code>
</pre>
<a
@@ -265,8 +272,11 @@ function TasksetSource({ className }: { className?: string }) {
function HowItWorksPanel({ ctaHref }: { ctaHref: string }) {
return (
<div className="w-[min(92vw,760px)] p-4">
<div className="grid grid-cols-2 gap-4">
// 820px, not more: the panel hangs off the LEFT edge of the nav, which
// starts about 100px in, so anything wider than this pushes past the right
// edge of a 1024px laptop and gives the whole page a horizontal scrollbar.
<div className="w-[min(90vw,820px)] p-4">
<div className="grid grid-cols-[minmax(0,0.85fr)_minmax(0,1fr)] gap-4">
<dl className="flex flex-col gap-3">
{CONCEPTS.map((concept) => (
<div key={concept.term} className="flex flex-col gap-0.5">