Devtools example
An AI-powered chat where each assistant reply streams a fresh Spec that renders inline. A single <JsonRenderDevtools /> panel observes every rendered spec, every streamed patch, every state change, and every dispatched action across the whole page — demonstrating how one devtools instance works with many renderers.
Pairs with @json-render/devtools and @json-render/devtools-react.
What it shows
- AI-streamed specs (inline mode) — the agent writes a short conversational reply, then emits a
```specfence of RFC 6902 JSON patches.pipeJsonRenderon the server splits that intodata-specparts and plain text parts; the client re-assembles both withuseJsonRenderMessage. - One renderer per assistant message, one devtools panel — every message gets its own
<Renderer />, but they share a single top-level<JSONUIProvider>, so the devtools State / Actions / Stream tabs see the whole page, not just one message. - State namespacing per turn — the API route hands the agent a unique
messageIdand requires every element key (<id>-root,<id>-counter, …) and state path (/<id>/count,/<id>/todos) to be prefixed with it, so specs from different messages never collide on shared state. - Built-ins exercised —
setState,pushState,removeState, plusinc/dec/toggle/addcomputed functions,$bindState,$template,$state,repeat,$item,$index, and conditionalvisible. - The Pick panel across renderers — because every element is tagged with
data-jr-key, picking an element in any assistant bubble jumps to its spec in the panel.
Setup
pnpm install
cp .env.example .env.local
# Edit .env.local and set AI_GATEWAY_API_KEY
Grab an AI Gateway key at https://vercel.com/ai-gateway. On Vercel the key is auto-authenticated, so you only need this for local dev.
Run
pnpm dev
# http://devtools-demo.json-render.localhost:1355
Press ⌘ ⇧ J (or click the floating {} badge) to toggle the panel. It starts open by default.
Files
app/page.tsx— chat UI, top-level<JSONUIProvider>, per-message<Renderer />,<JsonRenderDevtools>mountapp/api/chat/route.ts— streams the agent throughpipeJsonRenderlib/agent.ts—ToolLoopAgentwith a system prompt that enforces inline mode +messageId-based namespacinglib/catalog.ts— compact catalog (Card, Stack, Grid, Metric, Button, TextInput, Checkbox, List, ProgressBar, Callout, …) tuned to show off devtoolslib/registry.tsx— component renderers with plain inline styles, no UI framework
Try these prompts
- "Build an interactive counter with + and - buttons" — Actions tab lights up with
setStatedispatches. - "Make a todo list where I can add items, mark them done, and remove them" — exercises
pushState/removeStateand$bindStateinputs. - "Show me a fitness dashboard with three metrics and progress bars" — metric-heavy spec; handy for the Spec tree + State inspector.
- "Quiz me on three geography questions with a submit button that reveals my score" — uses bindings plus conditional visibility.
Then send a second prompt in the same session and watch the Stream tab keep appending patches while the State tab shows both turns' namespaced keys side by side.