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:
karti-ai
2026-08-28 16:09:59 -07:00
parent b601511e7f
commit eb88138d15
31 changed files with 1341 additions and 680 deletions
+44
View File
@@ -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',
});
+53
View File
@@ -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
+81
View File
@@ -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;
/** 01, 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);