# 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`
default | both (back-compat)
spec target | `.claude/chain/<skill>-dashboard.json`
tokens | ~580 (explore) / ~830 (assess)
vs markdown | ~3000 / ~3500
tests | 16/16 (4 negative cases)

### #1529 — Storybook → catalog

script | `storybook-to-catalog.mjs`
input | Storybook MCP `list-all-documentation`
output | `catalog.ts` + `components.tsx`
drops | callbacks, object props, z.any
tests | 22/22 (3 negative + 5 safety)
backstop | genui-architect agent decision flow

## Catalog the importer can produce — Storybook ArgType → Zod

Storybook | Zod | Notes
--- | --- | ---
`{ 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' }` | DROPPED | too open for AI safety
`() => void` (callbacks) | DROPPED | AI cannot generate executables
ReactNode | children: 'allowed' | container marker, not a prop