Interactive tutorial
Edit a Loupe config in the browser, render it live, lock tiles, and watch the deterministic export brief recompute.
This is Loupe with the lid off. Edit the JSON config on the left and hit Render (or ⌘/Ctrl + Enter). A real <Loupe /> mounts below; lock tiles and the deterministic export brief on the right recomputes in real time — the same selectExportBrief derivation a build pass would call.
Everything runs in your browser. The config is parsed with parseConfig, checked with validateConfig, and any error shows under the editor — so you also get a feel for how the contract rejects bad input.
config valid — rendered below
Try these edits
Change a label and re-render
Change "label": "Bold sans" to something of your own, press ⌘/Ctrl + Enter, and watch the tile and the brief update together.
Add a third option to a group
Copy an option object inside an options array, give it a unique id, and re-render. A group allows 2–6 options.
{
"id": "editorial",
"label": "Editorial serif",
"specimen": {
"kind": "type",
"family": "Georgia, 'Times New Roman', serif",
"weight": 500,
"sample": "A warmer way to ship."
}
}Add a whole new group
Add a layoutMock decision and watch it appear as a new section with a wireframe tile:
{
"id": "hero",
"title": "Hero layout",
"prompt": "How does the first screen open?",
"options": [
{ "id": "portrait", "label": "Statement portrait", "recommended": true, "specimen": { "kind": "layoutMock", "plan": "portrait" } },
{ "id": "sparse", "label": "Sparse, text-led", "specimen": { "kind": "layoutMock", "plan": "sparse" } }
]
}Break it on purpose
Give two options the same id, or point a preview band at a group that does not exist, and re-render. The error panel shows the exact validateConfig message — the same gate the generator and the agent loop use.
Watch the brief, then lock by hand
After rendering, click tiles in the live picker. The brief on the right tracks your actual picks (not just the recommended defaults) via onLockChange — proving preview and brief never drift from the same selections.
Why is there an explicit Render button instead of live-parsing every keystroke? <Loupe /> owns a store keyed on config identity, so re-parsing on each character would reset your picks. The playground commits a new config only when you ask, and recomputes the brief live from your selections in between.
What just happened
The playground is ~80 lines and uses nothing the docs do not document:
parseConfig+validateConfigfromloupe-schemato compile and gate the edited JSON;<Loupe />fromloupe-reactto render it, withonLockChangereporting selections;recommendedSelections+selectExportBrieffromloupe-coreto derive the brief from those selections.
That is the entire contract. From here, the agent method is the same loop run headless: author the config, generate a portable artifact, screenshot-verify it, export the brief.