Frontend: site chrome, demo shell, pages, and the contract gates
Five parallel lanes plus an integration pass. The header, gallery, router and sitemap are all generated from the demo registry, so adding src/demos/<slug>/ puts a demo everywhere with zero edits to shared files — which is the whole reason demo nine cannot break demo one. check-demos enforces the twelve contract rules: 142 checks over one live demo. Two worth naming. The shell may not mention a specific slug, because an 'if (slug === wordle)' in src/components/demo/ is a contract bug wearing a patch. And a spec-status demo must ship a real specification — task, actions, grader, counterweight, eval command — since a coming-soon card reads worse than an honest empty gallery. Bundle budget holds: entry 108.79 kB gzipped against a 160 kB ceiling, the demo chunk 21.15 kB against 90 kB. recharts is 108 kB gzipped and lives behind a lazy import so it never touches the entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* ── THE TEMPLATE ───────────────────────────────────────────────────────────
|
||||
*
|
||||
* `node scripts/new-demo.mjs <slug>` copies this directory to
|
||||
* `src/demos/<slug>` and substitutes the `__token__` names. Everything here is
|
||||
* a WORKED EXAMPLE, not filler: it is written the way a real demo is written
|
||||
* so that a scaffold passes `pnpm check` on the first run and you edit prose
|
||||
* rather than discover the contract one failing rule at a time.
|
||||
*
|
||||
* The directory itself is invisible to the site. The registry's glob excludes
|
||||
* `_`-prefixed directories, and `scripts/_lib.mjs` does the same, so nothing
|
||||
* in here renders, ships or is graded until it has been copied under a real
|
||||
* slug.
|
||||
*
|
||||
* `meta.ts` is loaded EAGERLY for every demo on every page, so it stays plain
|
||||
* serialisable data: no React, no lucide component, no imports beyond the kit.
|
||||
*/
|
||||
|
||||
import { defineMeta } from '@/lib/demo-kit';
|
||||
|
||||
export default defineMeta({
|
||||
/** Must equal the directory name. The registry, the route and the OG card key on it. */
|
||||
slug: '__slug__',
|
||||
title: '__Title__',
|
||||
/** One line, exec-facing. What the agent DOES, not how it works. */
|
||||
tagline: 'Pick the one item in a queue that actually needs a person, and leave the rest alone.',
|
||||
vertical: 'reference',
|
||||
/**
|
||||
* `spec` publishes the specification — task, actions, grader, counterweight
|
||||
* and eval command — with no interactive surface. Promote to `live` only
|
||||
* once recorded runs for this slug exist in `public/traces/manifest.json`;
|
||||
* `check-demos` rule 7 enforces that and will fail the build otherwise.
|
||||
*/
|
||||
status: 'spec',
|
||||
/** Sort order within the vertical. Ties break on slug. */
|
||||
order: 100,
|
||||
/** A lucide-react icon NAME, resolved by the shell. Not a component. */
|
||||
icon: 'ListChecks',
|
||||
persona: 'The manager who owns the queue',
|
||||
/** Six words on what the reward pays for, and what it takes away. */
|
||||
rewardLine: 'Escalate what matters, minus false alarms',
|
||||
/** `node scripts/og.mjs` writes this file. Rule 4 fails until it exists. */
|
||||
ogImage: '/og/__slug__.png',
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
"""The reward this demo quotes, as a runnable placeholder.
|
||||
|
||||
A real demo points `reward.source.path` at its environment package — e.g.
|
||||
`envs/__package__/__package__/reward.py` — and quotes the function the grader
|
||||
actually runs. This file exists so a freshly scaffolded demo has a receipt that
|
||||
RESOLVES on day one: an empty receipt panel reads to a visitor as the code not
|
||||
existing, which is the exact impression this site is built to avoid.
|
||||
|
||||
Move the region markers into the environment and repoint `source.path` as soon
|
||||
as the environment lands. `scripts/check-receipts.mjs` will tell you the moment
|
||||
the two disagree.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Episode:
|
||||
"""One rollout, as the environment records it."""
|
||||
|
||||
escalated: frozenset[str]
|
||||
needed: frozenset[str]
|
||||
malformed_actions: int
|
||||
|
||||
|
||||
# region: pig-demo/score
|
||||
def score(episode: Episode) -> dict[str, float]:
|
||||
"""Three terms, weighted 0.60 / 0.25 / 0.15 in the demo's RewardSpec.
|
||||
|
||||
`caught` is the objective. `restraint` is the counterweight: it is what
|
||||
stops the objective being maximised the crude way, by escalating the whole
|
||||
queue. `well_formed` is a gate — every competent policy scores 1.0 on it,
|
||||
so it is declared a gate rather than dressed up as a second counterweight.
|
||||
"""
|
||||
needed = episode.needed
|
||||
escalated = episode.escalated
|
||||
|
||||
caught = len(escalated & needed) / len(needed) if needed else 1.0
|
||||
|
||||
noise = escalated - needed
|
||||
quiet = len(escalated) - len(noise)
|
||||
restraint = 1.0 - (len(noise) / len(escalated)) if escalated else 1.0
|
||||
|
||||
well_formed = 0.0 if episode.malformed_actions else 1.0
|
||||
|
||||
return {
|
||||
"caught": caught,
|
||||
"restraint": restraint,
|
||||
"well_formed": well_formed,
|
||||
# Unweighted diagnostic. Rendered, never summed into the reward.
|
||||
"escalations_that_landed": float(quiet),
|
||||
}
|
||||
# endregion: pig-demo/score
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* The board, and the state it renders.
|
||||
*
|
||||
* One component does all three jobs — the visitor playing, the recorded
|
||||
* replay, and the gallery thumbnail (`compact`) — because three near-identical
|
||||
* boards is how they drift apart. The shell never inspects `__Pascal__State`;
|
||||
* it only ever hands one back.
|
||||
*
|
||||
* Nothing here may import `@/components/demo/*` or reach inside
|
||||
* `@/lib/demo-kit`. `scripts/check-demos.mjs` rule 9 enforces it.
|
||||
*/
|
||||
|
||||
import { memo } from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
/** One row of the queue the agent is triaging. */
|
||||
export interface QueueItem {
|
||||
id: string;
|
||||
label: string;
|
||||
/** 0–1, as the environment scored it. Rendered, never used to decide. */
|
||||
risk: number;
|
||||
/** True when this row genuinely needed a person. The grader's ground truth. */
|
||||
needsPerson: boolean;
|
||||
}
|
||||
|
||||
/** The board after a step. Steps are snapshots, not deltas. */
|
||||
export interface __Pascal__State {
|
||||
seed: number;
|
||||
items: QueueItem[];
|
||||
/** Item ids the agent has escalated so far, in the order it escalated them. */
|
||||
escalated: string[];
|
||||
/** Set once the episode ends; `pending` while it is still running. */
|
||||
outcome: 'pending' | 'solved' | 'failed';
|
||||
}
|
||||
|
||||
/** A fresh board for a seed. Deterministic in the seed — the shell relies on it. */
|
||||
export function empty__Pascal__(seed: number): __Pascal__State {
|
||||
return { seed, items: [], escalated: [], outcome: 'pending' };
|
||||
}
|
||||
|
||||
function Row({ item, escalated, compact }: { item: QueueItem; escalated: boolean; compact?: boolean }) {
|
||||
return (
|
||||
<li
|
||||
className={cn(
|
||||
'flex items-center justify-between gap-3 rounded-md border px-3 py-2',
|
||||
compact ? 'text-[11px]' : 'text-sm',
|
||||
escalated ? 'border-accent bg-surface-2 text-fg' : 'border-border text-muted',
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{item.label}</span>
|
||||
{/* A colour-only distinction fails a projector, deuteranopia and a
|
||||
black-and-white printout, so the state is also a word. */}
|
||||
<span className="shrink-0 font-mono tabular-nums">
|
||||
{escalated ? 'escalated' : 'left alone'}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function Board({ state, compact }: { state: __Pascal__State; compact?: boolean }) {
|
||||
const escalated = new Set(state.escalated);
|
||||
|
||||
if (state.items.length === 0) {
|
||||
return (
|
||||
<p className={cn('text-muted', compact ? 'text-[11px]' : 'text-sm')}>
|
||||
Nothing in the queue yet.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className={cn('flex flex-col', compact ? 'gap-1' : 'gap-2')}>
|
||||
{state.items.map((item) => (
|
||||
<Row key={item.id} item={item} escalated={escalated.has(item.id)} compact={compact} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(Board);
|
||||
Reference in New Issue
Block a user