M125 Lane C playground

Interactive walkthrough of #1527 (json-render dashboards) and #1529 (Storybook → genui catalog) shipping in PR #1539.
PR #1539 issues #1527 + #1529 milestone M125 CC 2.1.121 json-render 0.17 tests 38/38

Edit a json-render spec — see it rendered as markdown

This is the same logic that ships in src/skills/{explore,assess}/scripts/render-spec.mjs. Validation runs on every keystroke; invalid specs show inline errors instead of partial output. Default skill mode --render=both emits this spec to .claude/chain/<skill>-dashboard.json alongside the human-readable markdown.

spec.json 0 tok

rendered markdown

Storybook manifest → Zod catalog (single source of truth)

Same logic as src/skills/json-render-catalog/scripts/storybook-to-catalog.mjs. AI safety is enforced at import: callbacks dropped, raw object controls dropped, z.any()/z.unknown() blocked. Adding a story expands the AI-allowed surface; removing one shrinks it.

storybook manifest — components

generated catalog.ts

Why both Lane C issues ship in one PR

#1527 establishes the catalog conventions (Card/StatGrid/DataTable/StatusBadge/BarMeter); #1529 builds on them by importing component catalogs from Storybook. Splitting the PR would force an awkward middle state where the catalog-emission docs reference an importer that doesn't exist yet.

#1527 — Dashboard emission

flag--render=markdown|json-render|both
defaultboth (back-compat)
spec target.claude/chain/<skill>-dashboard.json
tokens~580 (explore) / ~830 (assess)
vs markdown~3000 / ~3500
tests16/16 (4 negative cases)

#1529 — Storybook → catalog

scriptstorybook-to-catalog.mjs
inputStorybook MCP list-all-documentation
outputcatalog.ts + components.tsx
dropscallbacks, object props, z.any
tests22/22 (3 negative + 5 safety)
backstopgenui-architect agent decision flow

Catalog the importer can produce — Storybook ArgType → Zod

StorybookZodNotes
{ control: 'text' }z.string().max(500)prompt-injection cap
{ control: 'select', options }z.enum([...])best case — fully constrained
{ control: 'color' }z.string().regex(/^#[0-9a-fA-F]{6}$/)hex only
{ control: 'object' }DROPPEDtoo open for AI safety
() => void (callbacks)DROPPEDAI cannot generate executables
ReactNodechildren: 'allowed'container marker, not a prop