## File: README.md

# json-render

**The Generative UI framework.**

Generate dynamic, personalized UIs from prompts without sacrificing reliability. Predefined components and actions for safe, predictable output.

```bash
# for React
npm install @json-render/core @json-render/react
# for React with pre-built shadcn/ui components
npm install @json-render/shadcn
# or for React Native
npm install @json-render/core @json-render/react-native
# or for video
npm install @json-render/core @json-render/remotion
# or for PDF documents
npm install @json-render/core @json-render/react-pdf
# or for HTML email
npm install @json-render/core @json-render/react-email @react-email/components @react-email/render
# or for Vue
npm install @json-render/core @json-render/vue
# or for Svelte
npm install @json-render/core @json-render/svelte
# or for SolidJS
npm install @json-render/core @json-render/solid
# or for terminal UIs
npm install @json-render/core @json-render/ink ink react
# or for full Next.js apps (routes, layouts, SSR, metadata)
npm install @json-render/core @json-render/react @json-render/next
# or for 3D scenes (and gaussian splatting via the GaussianSplat component)
npm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three
```

## Why json-render?

json-render is a **Generative UI** framework: AI generates interfaces from natural language prompts, constrained to components you define. You set the guardrails, AI generates within them:

- **Guardrailed** - AI can only use components in your catalog
- **Predictable** - JSON output matches your schema, every time
- **Fast** - Stream and render progressively as the model responds
- **Cross-Platform** - React, Vue, Svelte, Solid (web), React Native (mobile) from the same catalog
- **Batteries Included** - 36 pre-built shadcn/ui components ready to use

## Quick Start

### 1. Define Your Catalog

```typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { z } from "zod";

const catalog = defineCatalog(schema, {
  components: {
    Card: {
      props: z.object({ title: z.string() }),
    },
    Metric: {
      props: z.object({
        label: z.string(),
        value: z.string(),
        format: z.enum(["currency", "percent", "number"]).nullable(),
      }),
    },
    Button: {
      props: z.object({
        label: z.string(),
        action: z.string(),
      }),
    },
  },
  actions: {
    export_report: { description: "Export dashboard to PDF" },
    refresh_data: { description: "Refresh all metrics" },
  },
});
```

### 2. Define Your Components

```tsx
import { defineRegistry, Renderer } from "@json-render/react";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: ({ props, children }) => (
      
        <h3>{props.title}</h3>
        {children}
      
    ),
    Metric: ({ props }) => (
      
        {props.label}
        {format(props.value, props.format)}
      
    ),
    Button: ({ props, emit }) => (
       emit("press")}>{props.label}
    ),
  },
});
```

### 3. Render AI-Generated Specs

```tsx
function Dashboard({ spec }) {
  return <Renderer spec={spec} registry={registry} />;
}
```

**That's it.** AI generates JSON, you render it safely.

---

## Packages

| Package                     | Description                                                            |
| --------------------------- | ---------------------------------------------------------------------- |
| `@json-render/core`         | Schemas, catalogs, AI prompts, dynamic props, SpecStream utilities     |
| `@json-render/react`        | React renderer, contexts, hooks                                        |
| `@json-render/vue`          | Vue 3 renderer, composables, providers                                 |
| `@json-render/svelte`       | Svelte 5 renderer with runes-based reactivity                          |
| `@json-render/solid`        | SolidJS renderer with fine-grained reactive contexts                   |
| `@json-render/shadcn`       | 36 pre-built shadcn/ui components (Radix UI + Tailwind CSS)            |
| `@json-render/shadcn-svelte`| 36 pre-built shadcn-svelte components (Svelte 5 + Tailwind CSS)        |
| `@json-render/react-three-fiber` | React Three Fiber renderer for 3D scenes (20 built-in components, including GaussianSplat)  |
| `@json-render/react-native` | React Native renderer with standard mobile components                  |
| `@json-render/next`         | Next.js renderer — JSON becomes full apps with routes, layouts, SSR    |
| `@json-render/remotion`     | Remotion video renderer, timeline schema                               |
| `@json-render/react-pdf`    | React PDF renderer for generating PDF documents from specs             |
| `@json-render/react-email`  | React Email renderer for HTML/plain-text emails from specs             |
| `@json-render/ink`          | Ink terminal renderer with built-in components for interactive TUIs.   |
| `@json-render/image`        | Image renderer for SVG/PNG output (OG images, social cards) via Satori |
| `@json-render/directives`   | Pre-built custom directives — $format, $math, $concat, $count, $truncate, $pluralize, $join, $t (i18n) |
| `@json-render/codegen`      | Utilities for generating code from json-render UI trees                |
| `@json-render/devtools`     | Framework-agnostic devtools core — panel UI, event store, picker, stream taps |
| `@json-render/devtools-react`   | React adapter for `@json-render/devtools` (drop-in `<JsonRenderDevtools />`)     |
| `@json-render/devtools-vue`     | Vue adapter for `@json-render/devtools`                                           |
| `@json-render/devtools-svelte`  | Svelte adapter for `@json-render/devtools`                                        |
| `@json-render/devtools-solid`   | SolidJS adapter for `@json-render/devtools`                                       |
| `@json-render/redux`        | Redux / Redux Toolkit adapter for `StateStore`                         |
| `@json-render/zustand`      | Zustand adapter for `StateStore`                                       |
| `@json-render/jotai`        | Jotai adapter for `StateStore`                                         |
| `@json-render/xstate`       | XState Store (atom) adapter for `StateStore`                           |
| `@json-render/mcp`          | MCP Apps integration for Claude, ChatGPT, Cursor, VS Code              |
| `@json-render/yaml`         | YAML wire format with streaming parser, edit modes, AI SDK transform   |

## Renderers

### React (UI)

```tsx
import { defineRegistry, Renderer } from "@json-render/react";
import { schema } from "@json-render/react/schema";

// Flat spec format (root key + elements map)
const spec = {
  root: "card-1",
  elements: {
    "card-1": {
      type: "Card",
      props: { title: "Hello" },
      children: ["button-1"],
    },
    "button-1": {
      type: "Button",
      props: { label: "Click me" },
      children: [],
    },
  },
};

// defineRegistry creates a type-safe component registry
const { registry } = defineRegistry(catalog, { components });
<Renderer spec={spec} registry={registry} />;
```

### Vue (UI)

```typescript
import { h } from "vue";
import { defineRegistry, Renderer } from "@json-render/vue";
import { schema } from "@json-render/vue/schema";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: ({ props, children }) =>
      h("div", { class: "card" }, [h("h3", null, props.title), children]),
    Button: ({ props, emit }) =>
      h("button", { onClick: () => emit("press") }, props.label),
  },
});

// In your Vue component template:
// <Renderer :spec="spec" :registry="registry" />
```

### Svelte (UI)

```typescript
import { defineRegistry, Renderer } from "@json-render/svelte";
import { schema } from "@json-render/svelte/schema";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: ({ props, children }) => /* Svelte 5 snippet */,
    Button: ({ props, emit }) => /* Svelte 5 snippet */,
  },
});

// In your Svelte component:
// <Renderer spec={spec} registry={registry} />
```

### Solid (UI)

```tsx
import { defineRegistry, Renderer } from "@json-render/solid";
import { schema } from "@json-render/solid/schema";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: (renderProps) => {renderProps.children},
    Button: (renderProps) => (
       renderProps.emit("press")}>
        {renderProps.element.props.label as string}
      
    ),
  },
});

<Renderer spec={spec} registry={registry} />;
```

### shadcn/ui (Web)

```tsx
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { defineRegistry, Renderer } from "@json-render/react";
import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
import { shadcnComponents } from "@json-render/shadcn";

// Pick components from the 36 standard definitions
const catalog = defineCatalog(schema, {
  components: {
    Card: shadcnComponentDefinitions.Card,
    Stack: shadcnComponentDefinitions.Stack,
    Heading: shadcnComponentDefinitions.Heading,
    Button: shadcnComponentDefinitions.Button,
  },
  actions: {},
});

// Use matching implementations
const { registry } = defineRegistry(catalog, {
  components: {
    Card: shadcnComponents.Card,
    Stack: shadcnComponents.Stack,
    Heading: shadcnComponents.Heading,
    Button: shadcnComponents.Button,
  },
});

<Renderer spec={spec} registry={registry} />;
```

### React Native (Mobile)

```tsx
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react-native/schema";
import {
  standardComponentDefinitions,
  standardActionDefinitions,
} from "@json-render/react-native/catalog";
import { defineRegistry, Renderer } from "@json-render/react-native";

// 25+ standard components included
const catalog = defineCatalog(schema, {
  components: { ...standardComponentDefinitions },
  actions: standardActionDefinitions,
});

const { registry } = defineRegistry(catalog, { components: {} });
<Renderer spec={spec} registry={registry} />;
```

### Remotion (Video)

```tsx
import { Player } from "@remotion/player";
import {
  Renderer,
  schema,
  standardComponentDefinitions,
} from "@json-render/remotion";

// Timeline spec format
const spec = {
  composition: {
    id: "video",
    fps: 30,
    width: 1920,
    height: 1080,
    durationInFrames: 300,
  },
  tracks: [{ id: "main", name: "Main", type: "video", enabled: true }],
  clips: [
    {
      id: "clip-1",
      trackId: "main",
      component: "TitleCard",
      props: { title: "Hello" },
      from: 0,
      durationInFrames: 90,
    },
  ],
  audio: { tracks: [] },
};

;
```

### React PDF (Documents)

```typescript
import { renderToBuffer } from "@json-render/react-pdf";

const spec = {
  root: "doc",
  elements: {
    doc: {
      type: "Document",
      props: { title: "Invoice" },
      children: ["page-1"],
    },
    "page-1": {
      type: "Page",
      props: { size: "A4" },
      children: ["heading-1", "table-1"],
    },
    "heading-1": {
      type: "Heading",
      props: { text: "Invoice #1234", level: "h1" },
      children: [],
    },
    "table-1": {
      type: "Table",
      props: {
        columns: [
          { header: "Item", width: "60%" },
          { header: "Price", width: "40%", align: "right" },
        ],
        rows: [
          ["Widget A", "$10.00"],
          ["Widget B", "$25.00"],
        ],
      },
      children: [],
    },
  },
};

// Render to buffer, stream, or file
const buffer = await renderToBuffer(spec);
```

### React Email (Email)

```typescript
import { renderToHtml } from "@json-render/react-email";
import { schema, standardComponentDefinitions } from "@json-render/react-email";
import { defineCatalog } from "@json-render/core";

const catalog = defineCatalog(schema, {
  components: standardComponentDefinitions,
});

const spec = {
  root: "html-1",
  elements: {
    "html-1": {
      type: "Html",
      props: { lang: "en", dir: "ltr" },
      children: ["head-1", "body-1"],
    },
    "head-1": { type: "Head", props: {}, children: [] },
    "body-1": {
      type: "Body",
      props: { style: { backgroundColor: "#f6f9fc" } },
      children: ["container-1"],
    },
    "container-1": {
      type: "Container",
      props: {
        style: { maxWidth: "600px", margin: "0 auto", padding: "20px" },
      },
      children: ["heading-1", "text-1"],
    },
    "heading-1": { type: "Heading", props: { text: "Welcome" }, children: [] },
    "text-1": {
      type: "Text",
      props: { text: "Thanks for signing up." },
      children: [],
    },
  },
};

const html = await renderToHtml(spec);
```

### Image (SVG/PNG)

```typescript
import { renderToPng } from "@json-render/image/render";

const spec = {
  root: "frame",
  elements: {
    frame: {
      type: "Frame",
      props: { width: 1200, height: 630, backgroundColor: "#1a1a2e" },
      children: ["heading"],
    },
    heading: {
      type: "Heading",
      props: { text: "Hello World", level: "h1", color: "#ffffff" },
      children: [],
    },
  },
};

// Render to PNG (requires @resvg/resvg-js)
const png = await renderToPng(spec, { fonts });

// Or render to SVG string
import { renderToSvg } from "@json-render/image/render";
const svg = await renderToSvg(spec, { fonts });
```

### Three.js (3D)

```tsx
import { defineCatalog } from "@json-render/core";
import { schema, defineRegistry } from "@json-render/react";
import {
  threeComponentDefinitions,
  threeComponents,
  ThreeCanvas,
} from "@json-render/react-three-fiber";

const catalog = defineCatalog(schema, {
  components: {
    Box: threeComponentDefinitions.Box,
    Sphere: threeComponentDefinitions.Sphere,
    AmbientLight: threeComponentDefinitions.AmbientLight,
    DirectionalLight: threeComponentDefinitions.DirectionalLight,
    GaussianSplat: threeComponentDefinitions.GaussianSplat,
    OrbitControls: threeComponentDefinitions.OrbitControls,
  },
  actions: {},
});

const { registry } = defineRegistry(catalog, {
  components: {
    Box: threeComponents.Box,
    Sphere: threeComponents.Sphere,
    AmbientLight: threeComponents.AmbientLight,
    DirectionalLight: threeComponents.DirectionalLight,
    GaussianSplat: threeComponents.GaussianSplat,
    OrbitControls: threeComponents.OrbitControls,
  },
});

;
```

### Next.js (Full Apps)

```typescript
import type { NextAppSpec } from "@json-render/next";
import { createNextApp } from "@json-render/next/server";
import { NextAppProvider } from "@json-render/next";

const spec: NextAppSpec = {
  metadata: { title: { default: "My App", template: "%s | My App" } },
  layouts: {
    main: {
      root: "shell",
      elements: {
        shell: { type: "Container", props: {}, children: ["nav", "slot"] },
        nav: { type: "NavBar", props: {}, children: [] },
        slot: { type: "Slot", props: {}, children: [] },
      },
    },
  },
  routes: {
    "/": {
      layout: "main",
      metadata: { title: "Home" },
      page: {
        root: "hero",
        elements: {
          hero: { type: "Card", props: { title: "Welcome" }, children: [] },
        },
      },
    },
  },
};

// Server: creates Page, generateMetadata, generateStaticParams
const app = createNextApp({ spec });

// Client: wrap your layout with NextAppProvider
// <NextAppProvider registry={registry} handlers={handlers}>
//   {children}
// </NextAppProvider>
```

### shadcn-svelte (Svelte)

```typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/svelte/schema";
import { defineRegistry, Renderer } from "@json-render/svelte";
import { shadcnComponentDefinitions } from "@json-render/shadcn-svelte/catalog";
import { shadcnComponents } from "@json-render/shadcn-svelte";

const catalog = defineCatalog(schema, {
  components: {
    Card: shadcnComponentDefinitions.Card,
    Stack: shadcnComponentDefinitions.Stack,
    Heading: shadcnComponentDefinitions.Heading,
    Button: shadcnComponentDefinitions.Button,
  },
  actions: {},
});

const { registry } = defineRegistry(catalog, {
  components: {
    Card: shadcnComponents.Card,
    Stack: shadcnComponents.Stack,
    Heading: shadcnComponents.Heading,
    Button: shadcnComponents.Button,
  },
});

// In your Svelte component:
// <Renderer spec={spec} registry={registry} />
```

### Devtools

Drop-in inspector panel for any json-render app. Spec tree, state editor, action log, stream log, catalog browser, DOM picker.

```tsx
// React
import { JsonRenderDevtools } from "@json-render/devtools-react";

<JSONUIProvider registry={registry} handlers={handlers}>
  <Renderer spec={spec} registry={registry} />
  <JsonRenderDevtools spec={spec} catalog={catalog} messages={messages} />
</JSONUIProvider>;
```

Floating toggle appears bottom-right. Hotkey: `Ctrl`/`Cmd` + `Shift` + `J`. Tree-shakes to `null` in production.

Available for React, Vue, Svelte, and Solid — swap `@json-render/devtools-react` for the adapter that matches your renderer.

### Ink (Terminal)

```tsx
import { defineCatalog } from "@json-render/core";
import {
  schema,
  standardComponentDefinitions,
  standardActionDefinitions,
  defineRegistry,
  Renderer,
  JSONUIProvider,
} from "@json-render/ink";

const catalog = defineCatalog(schema, {
  components: { ...standardComponentDefinitions },
  actions: standardActionDefinitions,
});

const { registry } = defineRegistry(catalog, { components: {} });

const spec = {
  root: "card-1",
  elements: {
    "card-1": {
      type: "Card",
      props: { title: "Status" },
      children: ["status-1"],
    },
    "status-1": {
      type: "StatusLine",
      props: { label: "Build", status: "success" },
      children: [],
    },
  },
};

<JSONUIProvider initialState={{}}>
  <Renderer spec={spec} registry={registry} />
</JSONUIProvider>;
```

## Features

### Streaming (SpecStream)

Stream AI responses progressively:

```typescript
import { createSpecStreamCompiler } from "@json-render/core";

const compiler = createSpecStreamCompiler<MySpec>();

// Process chunks as they arrive
const { result, newPatches } = compiler.push(chunk);
setSpec(result); // Update UI with partial result

// Get final result
const finalSpec = compiler.getResult();
```

### AI Prompt Generation

Generate system prompts from your catalog:

```typescript
const systemPrompt = catalog.prompt();
// Includes component descriptions, props schemas, available actions
```

### Conditional Visibility

```json
{
  "type": "Alert",
  "props": { "message": "Error occurred" },
  "visible": [
    { "$state": "/form/hasError" },
    { "$state": "/form/errorDismissed", "not": true }
  ]
}
```

### Dynamic Props

Any prop value can be data-driven using expressions:

```json
{
  "type": "Icon",
  "props": {
    "name": {
      "$cond": { "$state": "/activeTab", "eq": "home" },
      "$then": "home",
      "$else": "home-outline"
    },
    "color": {
      "$cond": { "$state": "/activeTab", "eq": "home" },
      "$then": "#007AFF",
      "$else": "#8E8E93"
    }
  }
}
```

Expression forms:

- **`{ "$state": "/state/key" }`** - reads a value from the state model
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - evaluates a condition and picks a branch
- **`{ "$template": "Hello, ${/user/name}!" }`** - interpolates state values into strings
- **`{ "$computed": "fn", "args": { ... } }`** - calls a registered function with resolved args

### Actions

Components can trigger actions, including the built-in `setState` action:

```json
{
  "type": "Pressable",
  "props": {
    "action": "setState",
    "actionParams": { "statePath": "/activeTab", "value": "home" }
  },
  "children": ["home-icon"]
}
```

The `setState` action updates the state model directly, which re-evaluates visibility conditions and dynamic prop expressions.

### State Watchers

React to state changes by triggering actions:

```json
{
  "type": "Select",
  "props": {
    "value": { "$bindState": "/form/country" },
    "options": ["US", "Canada", "UK"]
  },
  "watch": {
    "/form/country": {
      "action": "loadCities",
      "params": { "country": { "$state": "/form/country" } }
    }
  }
}
```

`watch` is a top-level field on elements (sibling of `type`/`props`/`children`). Watchers fire when the watched value changes, not on initial render.

---

## Demo

```bash
git clone https://github.com/vercel-labs/json-render
cd json-render
pnpm install
pnpm dev
```

- http://json-render.localhost:1355 - Docs & Playground
- http://dashboard-demo.json-render.localhost:1355 - Example Dashboard
- http://react-email-demo.json-render.localhost:1355 - React Email Example
- http://remotion-demo.json-render.localhost:1355 - Remotion Video Example
- Chat Example: run `pnpm dev` in `examples/chat`
- Svelte Example: run `pnpm dev` in `examples/svelte` or `examples/svelte-chat`
- Vue Example: run `pnpm dev` in `examples/vue`
- Vite Renderers (React + Vue + Svelte + Solid): run `pnpm dev` in `examples/vite-renderers`
- React Native example: run `npx expo start` in `examples/react-native`
- Gaussian Splatting (R3F): run `pnpm dev` in `examples/react-three-fiber-gsplat`
- Gaussian Splatting (experimental standalone gsplat.js demo): run `pnpm dev` in `examples/gsplat`

## How It Works

```mermaid
flowchart LR
    A[User Prompt] --> B[AI + Catalog]
    B --> C[JSON Spec]
    C --> D[Renderer]

    B -.- E([guardrailed])
    C -.- F([predictable])
    D -.- G([streamed])
```

1. **Define the guardrails** - what components, actions, and data bindings AI can use
2. **Prompt** - describe what you want in natural language
3. **AI generates JSON** - output is always predictable, constrained to your catalog
4. **Render fast** - stream and render progressively as the model responds

## License

Apache-2.0

---

## File: apps/web/app/(main)/docs/installation/page.mdx

import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("docs/installation")

# Installation

Install the core package plus your renderer of choice.

## For React UI



Peer dependencies: `react ^19.0.0` and `zod ^4.0.0`.



## For Vue



Peer dependencies: `vue ^3.5.0` and `zod ^4.0.0`.



## For Svelte



Peer dependencies: `svelte ^5.0.0` and `zod ^4.0.0`.



## For React UI with shadcn/ui

Pre-built components for fast prototyping and production use:



Requires Tailwind CSS in your project. See the [@json-render/shadcn API reference](/docs/api/shadcn) for usage.

## For React Native



## For Remotion Video



## For React Email



## For External State Management (Optional)

If you want to wire json-render to an existing state management library instead of the built-in store, install the adapter for your library:









See the [Data Binding](/docs/data-binding#external-store-controlled-mode) guide for usage.

## For AI Integration

To use json-render with AI models, you'll also need the Vercel AI SDK:

---

## File: apps/web/app/(main)/docs/generation-modes/page.mdx

import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("docs/generation-modes")

# Generation Modes

json-render supports two modes for AI-generated UI: **Standalone mode** for standalone UI and **Inline mode** for inline UI within a conversation.

The mode controls how the AI formats its output and how your app processes the stream. The underlying JSONL patch format is the same in both modes.

<GenerationModesDiagram />

## Standalone Mode

In standalone mode, the AI outputs **only JSONL patches** — no prose, no markdown. The entire response is a UI spec.

This is the default mode and is ideal for:

- Playground and builder tools
- Form generators
- Dashboard builders
- Any UI where the generated interface is the whole response

### Setup

```typescript
import { streamText } from "ai";

// Standalone mode is the default (no mode option needed)
const systemPrompt = catalog.prompt({
  customRules: [
    "Use Card as root for forms and small UIs.",
    "Use Grid for multi-column layouts.",
  ],
});

const result = streamText({
  model: "anthropic/claude-haiku-4.5",
  system: systemPrompt,
  prompt: userPrompt,
});
```

### Client

On the client, use `useUIStream` from `@json-render/react` or the lower-level `createSpecStreamCompiler` from `@json-render/core` to compile the JSONL stream into a spec:

```tsx
import { useUIStream } from "@json-render/react";

function Playground() {
  const { spec, isStreaming, send } = useUIStream({
    api: "/api/generate",
  });

  return (
    <Renderer
      spec={spec}
      registry={registry}
      loading={isStreaming}
    />
  );
}
```

### Example output

The AI outputs only JSONL — one patch per line, no surrounding text:

```
{"op":"add","path":"/root","value":"card-1"}
{"op":"add","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Sign In"},"children":["email","password","submit"]}}
{"op":"add","path":"/elements/email","value":{"type":"Input","props":{"label":"Email","name":"email","type":"email"}}}
{"op":"add","path":"/elements/password","value":{"type":"Input","props":{"label":"Password","name":"password","type":"password"}}}
{"op":"add","path":"/elements/submit","value":{"type":"Button","props":{"label":"Sign In"}}}
```

## Inline Mode

In inline mode, the AI responds **conversationally first**, then outputs JSONL patches on their own lines. Text-only replies are allowed when no UI is needed (e.g. greetings, clarifying questions).

This is ideal for:

- AI chatbots with rich UI responses
- Copilot experiences
- Educational assistants
- Any conversational interface where generated UI is embedded in chat messages

### Setup

```typescript
import { streamText } from "ai";
import { pipeJsonRender } from "@json-render/core";
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";

// Enable inline mode
const systemPrompt = catalog.prompt({ mode: "inline" });

const result = streamText({
  model: yourModel,
  system: systemPrompt,
  messages,
});

// In your API route, pipe the stream through pipeJsonRender
// to separate text from JSONL patches
const stream = createUIMessageStream({
  execute: async ({ writer }) => {
    writer.merge(pipeJsonRender(result.toUIMessageStream()));
  },
});

return createUIMessageStreamResponse({ stream });
```

`pipeJsonRender` inspects each line of the AI's response. Lines that parse as JSONL patches are emitted as `data-spec` parts (which the renderer picks up). Everything else is passed through as text.

### Client

On the client, use `useJsonRenderMessage` from `@json-render/react` to extract the spec from a chat message's parts:

```tsx
import { useChat } from "@ai-sdk/react";
import { useJsonRenderMessage } from "@json-render/react";

function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    
      {messages.map((msg) => (
        <ChatMessage key={msg.id} message={msg} />
      ))}
      {/* input form */}
    
  );
}

function ChatMessage({ message }) {
  const { spec } = useJsonRenderMessage(message.parts);

  return (
    
      {/* Render text parts */}
      {message.parts
        .filter((p) => p.type === "text")
        .map((p, i) => {p.text})}

      {/* Render the generated UI inline */}
      {spec && (
        <Renderer
          spec={spec}
          registry={registry}
        />
      )}
    
  );
}
```

### Example output

The AI writes a brief explanation, then JSONL patches on their own lines:

```
Here's a dashboard showing the latest crypto prices:

{"op":"add","path":"/root","value":"dashboard"}
{"op":"add","path":"/state/prices","value":[{"name":"Bitcoin","price":98450},{"name":"Ethereum","price":3120}]}
{"op":"add","path":"/elements/dashboard","value":{"type":"Grid","props":{"columns":"2"},"children":["btc","eth"]}}
{"op":"add","path":"/elements/btc","value":{"type":"Metric","props":{"label":"Bitcoin","value":{"$state":"/prices/0/price"}}}}
{"op":"add","path":"/elements/eth","value":{"type":"Metric","props":{"label":"Ethereum","value":{"$state":"/prices/1/price"}}}}
```

If the user asks a simple question ("what does BTC stand for?"), the AI replies with text only — no JSONL.

## Quick Comparison


  

| Standalone | Inline |  |
| --- | --- | --- |
| Output format | JSONL only | Text + JSONL |
| Text-only replies | No | Yes |
| System prompt | `{"catalog.prompt()"}` | `{'catalog.prompt({ mode: "inline" })'}` |
| Stream utility | `{"useUIStream"}` | `{"pipeJsonRender"}`{" + "}`{"useJsonRenderMessage"}` |
| Typical use case | Playground, builders | Chatbots, copilots |




Both modes use the same JSONL patch format (RFC 6902) and the same catalog/registry system. The only difference is whether the AI is allowed to include prose alongside the patches.

## Next

- Learn about the [JSONL streaming format](/docs/streaming)
- See the [AI SDK integration](/docs/ai-sdk) for setup with the Vercel AI SDK

---

## File: apps/web/app/(main)/docs/directives/page.mdx

import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("docs/directives")

# Directives

Extend the spec language with custom `$`-prefixed dynamic values. Directives let you add formatting, math, string manipulation, i18n, and any other transformation without modifying core.

## Overview

A directive is a user-defined dynamic value expression, like `$state` or `$computed`, but defined in userland. Each directive has a `$`-prefixed name, a Zod schema for validation, and a resolver function.

```json
{
  "type": "Text",
  "props": {
    "text": {
      "$format": "currency",
      "value": { "$state": "/cart/total" },
      "currency": "USD"
    }
  },
  "children": []
}
```

## Defining a Directive

Use `defineDirective` from `@json-render/core`:

```typescript
import { defineDirective, resolvePropValue } from '@json-render/core';
import { z } from 'zod';

const doubleDirective = defineDirective({
  name: '$double',
  schema: z.object({
    $double: z.unknown(),
  }),
  resolve(value, ctx) {
    const resolved = resolvePropValue(value.$double, ctx);
    return (resolved as number) * 2;
  },
});
```

The `description` field is optional. When generating prompts, the directive's schema fields are auto-described from the Zod schema; the `description` adds short behavioral context the schema can't express.

## Wiring Directives

Pass directives to both the renderer (for runtime resolution) and the catalog prompt (for AI generation).

### Runtime

```tsx
import { JSONUIProvider, Renderer } from '@json-render/react';
import { standardDirectives } from '@json-render/directives';

<JSONUIProvider registry={registry} directives={standardDirectives}>
  <Renderer spec={spec} registry={registry} />
</JSONUIProvider>
```

Or with `createRenderer`:

```tsx
const MyRenderer = createRenderer(catalog, components);

<MyRenderer spec={spec} directives={directives} />
```

All four renderers (React, Vue, Svelte, Solid) accept the `directives` prop on their provider and `createRenderer` output.

### Prompt Generation

```typescript
const prompt = catalog.prompt({ directives });
```

Each directive's schema is auto-described in the "CUSTOM DYNAMIC VALUES" section of the system prompt. The optional `description` field adds behavioral context inline.

## Pre-built Directives

The `@json-render/directives` package ships ready-to-use directives:

```typescript
import { standardDirectives, createI18nDirective } from '@json-render/directives';
```

`standardDirectives` includes `$format`, `$math`, `$concat`, `$count`, `$truncate`, `$pluralize`, and `$join`. Add factory directives by spreading:

```typescript
const directives = [...standardDirectives, createI18nDirective(config)];
```

See the [API reference](/docs/api/directives) for details on each directive.

## Composition

Directives compose naturally. Each resolver calls `resolvePropValue` on its inputs, so directives can wrap other directives or built-in expressions like `$state`:

```json
{
  "$format": "currency",
  "value": {
    "$math": "multiply",
    "a": { "$state": "/price" },
    "b": { "$state": "/qty" }
  },
  "currency": "USD"
}
```

This resolves inside-out: `$state` reads from state, `$math` multiplies the values, and `$format` formats the result as currency.

## Built-in Precedence

Built-in expressions (`$state`, `$computed`, `$cond`, `$template`, etc.) always take precedence over custom directives. `defineDirective` throws if you try to register a name that conflicts with a built-in key.

## Next

- [API Reference](/docs/api/directives) — full directive reference
- [Computed Values](/docs/computed-values) — `$computed` and `$template` expressions
- [Data Binding](/docs/data-binding) — `$state`, `$item`, and binding expressions

---

## File: apps/web/app/(main)/docs/devtools/page.mdx

import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("docs/devtools")

# Devtools

A drop-in inspector panel for any json-render app. See the spec tree, edit state inline, watch dispatched actions, follow stream patches live, browse your catalog, and pick DOM elements to map them back to spec keys.

Production-safe: the component tree-shakes to a null render when `NODE_ENV === "production"`.

## Install

Pick the adapter that matches your renderer.

### React

```bash
npm install @json-render/devtools @json-render/devtools-react
```

### Vue

```bash
npm install @json-render/devtools @json-render/devtools-vue
```

### Svelte

```bash
npm install @json-render/devtools @json-render/devtools-svelte
```

### Solid

```bash
npm install @json-render/devtools @json-render/devtools-solid
```

## Quick Start

Drop `<JsonRenderDevtools />` anywhere inside your existing `<JSONUIProvider>` (or the equivalent provider tree).

```tsx
// React
import { JsonRenderDevtools } from "@json-render/devtools-react";

<JSONUIProvider registry={registry} handlers={handlers}>
  <Renderer spec={spec} registry={registry} />
  <JsonRenderDevtools spec={spec} catalog={catalog} />
</JSONUIProvider>
```

That's it. A floating toggle appears in the bottom-right corner. Click it, or press <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>Shift</kbd> + <kbd>J</kbd>, to open the drawer.

### Chat apps (AI SDK)

When you're using `@ai-sdk/react`'s `useChat`, pass the `messages` prop so the Stream tab captures spec patches as they arrive:

```tsx
<JsonRenderDevtools
  spec={spec}
  catalog={catalog}
  messages={messages}
/>
```

## Panels



| Tab | What it shows |
| --- | --- |
| **Spec** | Element tree rooted at `spec.root`. Expand to walk children. Selecting an element fills a detail pane with its full props, visibility condition, event bindings, watchers, and any issues reported by `validateSpec`. |
| **State** | Every leaf path in the state model listed via `flattenToPointers`. Click a value to edit inline — writes go through `store.set`, so conditional elements and computed props re-evaluate immediately. |
| **Actions** | Timeline of dispatched actions: name, params, result or error, duration. Newest first. Expand a row for the full JSON payload. |
| **Stream** | Patches, text chunks, token usage, and lifecycle markers from the AI generation stream. Grouped by generation. |
| **Catalog** | Components and actions declared in your catalog with prop chips and type hints. |
| **Pick** | Click any element in the page to surface its entry in the Spec tab. Works because the renderer transparently tags each element with `data-jr-key` while devtools is mounted. |



## Props



| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `spec` | `Spec   null` | `null` | The spec currently being rendered. |
| `catalog` | `Catalog   null` | `null` | Catalog definition — required for the Catalog panel. |
| `messages` | `UIMessage[]` | `undefined` | AI SDK `useChat` messages. Scanned for spec data parts and streamed into the Stream panel. |
| `initialOpen` | `boolean` | `false` | Start the drawer open. |
| `position` | `"bottom-right"   "bottom-left"   "right"` | `"bottom-right"` | Floating toggle button position. |
| `hotkey` | `string   false` | `"mod+shift+j"` | Keyboard shortcut. Use `mod` for Cmd on macOS / Ctrl elsewhere. Pass `false` to disable. |
| `bufferSize` | `number` | `500` | Max events retained in the ring buffer. |
| `onEvent` | `(evt: DevtoolsEvent) =&gt; void` | `undefined` | Optional tap — fires for every event as it is recorded. Useful for forwarding to analytics. |



## Production Safety

The component renders `null` when `process.env.NODE_ENV === "production"`. Bundlers fold the constant check so the panel's code tree-shakes out of production builds.

If you want extra certainty, gate the import behind an env check:

```tsx
import dynamic from "next/dynamic";

const JsonRenderDevtools = dynamic(
  () =>
    import("@json-render/devtools-react").then((m) => ({
      default: m.JsonRenderDevtools,
    })),
  { ssr: false, loading: () => null },
);
```

## Advanced

### Imperative controls

Use `useJsonRenderDevtools()` (React adapter only) to open / close the panel or record custom events from anywhere in the app:

```tsx
import { useJsonRenderDevtools } from "@json-render/devtools-react";

function DebugButton() {
  const devtools = useJsonRenderDevtools();
  return (
     devtools?.toggle()}>Toggle devtools
  );
}
```

### Server-side stream tap

Capture stream events before they reach the client. Useful for server logs:

```ts
import { tapJsonRenderStream } from "@json-render/devtools";

const tapped = tapJsonRenderStream(
  result.toUIMessageStream(),
  serverEventStore,
);
writer.merge(pipeJsonRender(tapped));
```

The `@json-render/devtools` core package exports `tapJsonRenderStream` and `tapYamlStream` for this pattern.

---

## File: apps/web/app/(main)/docs/data-binding/page.mdx

import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("docs/data-binding")

# Data Binding

Connect UI elements to dynamic data using expressions in your JSON specs.

## State Model

Every spec can include a `state` object that holds the data your UI reads from:

```json
{
  "root": "greeting",
  "elements": {
    "greeting": {
      "type": "Text",
      "props": { "content": { "$state": "/user/name" } },
      "children": []
    }
  },
  "state": {
    "user": { "name": "Alice" }
  }
}
```

State can also be provided programmatically at runtime. In `@json-render/react`, this is done via `StateProvider` and hooks like `useStateStore`. See the [React API reference](/docs/api/react) for details.

## JSON Pointer Paths

All paths in json-render follow JSON Pointer (RFC 6901). A path is a string of `/`-separated tokens starting from the root:

```
Given this state:
{
  "user": { "name": "Alice", "email": "alice@example.com" },
  "todos": [
    { "title": "Buy milk", "done": false },
    { "title": "Walk dog", "done": true }
  ]
}

"/user/name"      -> "Alice"
"/user/email"     -> "alice@example.com"
"/todos/0/title"  -> "Buy milk"
"/todos/1/done"   -> true
```

## Expressions

Expressions are special objects you place in props to read dynamic values instead of hardcoding them. There are six expression types.

### `$state` — Read from state

Use `{ "$state": "/path" }` in any prop to read a value from the state model:

```json
{
  "type": "Card",
  "props": {
    "title": { "$state": "/user/name" },
    "subtitle": { "$state": "/user/email" }
  },
  "children": []
}
```

If state contains `{ "user": { "name": "Alice", "email": "alice@example.com" } }`, the Card renders with title "Alice" and subtitle "alice@example.com".

### `$item` — Read from the current repeat item

Use `{ "$item": "field" }` inside a [repeat](#repeat) to read a field from the current array item:

```json
{
  "type": "Text",
  "props": { "content": { "$item": "title" } },
  "children": []
}
```

Use `{ "$item": "" }` to get the entire item object.

### `$index` — Current repeat index

Use `{ "$index": true }` inside a [repeat](#repeat) to get the current array index (zero-based number):

```json
{
  "type": "Text",
  "props": { "content": { "$index": true } },
  "children": []
}
```

## Repeat

The `repeat` field on an element renders its children once per item in a state array. It is a top-level field on the element, sibling of `type`, `props`, and `children` — not inside `props`.

```json
{
  "root": "todo-list",
  "elements": {
    "todo-list": {
      "type": "Column",
      "props": { "gap": 8 },
      "repeat": { "statePath": "/todos", "key": "id" },
      "children": ["todo-item"]
    },
    "todo-item": {
      "type": "Card",
      "props": {
        "title": { "$item": "title" },
        "subtitle": { "$item": "description" }
      },
      "children": []
    }
  },
  "state": {
    "todos": [
      { "id": "1", "title": "Buy milk", "description": "2% or whole" },
      { "id": "2", "title": "Walk dog", "description": "Around the park" }
    ]
  }
}
```

- `repeat.statePath`: root JSON Pointer to the state array, or `{ "$item": "field" }` for an array on the enclosing repeat item
- `repeat.key` — field name on each item to use as a stable key for rendering

Inside `todo-item`, `{ "$item": "title" }` reads the `title` field from whichever array item is currently being rendered. `{ "$index": true }` would return `0` for the first item, `1` for the second, and so on.

## Two-Way Binding with `$bindState`

Form components use `{ "$bindState": "/path" }` on their natural value prop for two-way binding. The component reads from and writes to the state path.

### Value prop (text inputs)

```json
{
  "type": "TextInput",
  "props": {
    "value": { "$bindState": "/form/email" },
    "placeholder": "Enter your email"
  },
  "children": []
}
```

### Checked prop (switches, checkboxes)

```json
{
  "type": "Switch",
  "props": {
    "label": "Enable notifications",
    "checked": { "$bindState": "/settings/notifications" }
  },
  "children": []
}
```

### Pressed prop (toggle buttons)

```json
{
  "type": "ToggleButton",
  "props": {
    "label": "Bold",
    "pressed": { "$bindState": "/editor/bold" }
  },
  "children": []
}
```

## Two-Way Binding with `$bindItem`

Inside a repeat scope, use `{ "$bindItem": "field" }` to bind to a field on the current item:

```json
{
  "type": "Switch",
  "props": {
    "label": "Done",
    "checked": { "$bindItem": "completed" }
  },
  "children": []
}
```

Use `{ "$bindItem": "" }` to bind to the entire item.

`statePath` is not used for component binding. It remains for `repeat.statePath` (array iteration path) and action params like `setState.statePath` (target path for mutations).

## Conditional Props

Use `$cond` / `$then` / `$else` to pick a prop value based on a condition:

```json
{
  "type": "Badge",
  "props": {
    "label": {
      "$cond": { "$state": "/user/isAdmin" },
      "$then": "Admin",
      "$else": "Member"
    }
  },
  "children": []
}
```

The condition uses the same [visibility](/docs/visibility) expression format.

## Template Strings

Use `{ "$template": "..." }` to interpolate state values into a string using `${/path}` syntax:

```json
{
  "type": "Text",
  "props": {
    "text": { "$template": "Welcome back, ${/user/name}!" }
  },
  "children": []
}
```

See [Computed Values](/docs/computed-values) for details on `$template` and `$computed` expressions.

## Quick Reference


  

| Expression | Syntax | Context |
| --- | --- | --- |
| `{"$state"}` | `{'{ "$state": "/path" }'}` | Anywhere |
| `{"$item"}` | `{'{ "$item": "field" }'}` | Inside repeat only |
| `{"$index"}` | `{'{ "$index": true }'}` | Inside repeat only |
| `{"$cond"}` | `{'{ "$cond": ..., "$then": ..., "$else": ... }'}` | Anywhere |
| `{"$bindState"}` | `{'{ "$bindState": "/path" }'}` | Form components (value, checked, pressed) |
| `{"$bindItem"}` | `{'{ "$bindItem": "field" }'}` | Form components inside repeat |
| `{"$template"}` | `{'{ "$template": "Hello, ${/name}!" }'}` | Anywhere (string props) |
| `{"$computed"}` | `{'{ "$computed": "fn", "args": { ... } }'}` | Anywhere (requires registered function) |




## External Store (Controlled Mode)

For advanced use cases, you can pass a `StateStore` to `StateProvider` to use your own state management (Redux, Zustand, XState, etc.) instead of the built-in internal store:

```tsx
import { createStateStore, type StateStore } from "@json-render/react";

const store = createStateStore({ user: { name: "Alice" } });

<StateProvider store={store}>
  {children}
</StateProvider>

// Mutate from anywhere — React re-renders automatically:
store.set("/user/name", "Bob");
```

When `store` is provided, `initialState` and `onStateChange` are ignored. The store is the single source of truth. See the [React API reference](/docs/api/react#external-store-controlled-mode) for the full `StateStore` interface.

## Next

- [Visibility](/docs/visibility) — conditionally show or hide elements
- [Action handlers](/docs/registry#action-handlers) — respond to user interactions
- [React API reference](/docs/api/react) — React-specific hooks for programmatic state access

---

## File: apps/web/app/(main)/docs/custom-schema/page.mdx

import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("docs/custom-schema")

# Custom Schema & Renderer

Build your own schema and renderer with `@json-render/core`.

## Overview

`@json-render/core` is schema-agnostic. While `@json-render/react` provides a ready-to-use schema and renderer, you can create your own to match any JSON structure - whether it's a domain-specific format, an existing protocol, or something entirely custom.

## 1. Define Your Schema

Start by defining the JSON structure your system will use. Here's an example of a simple dashboard schema:

```json
{
  "layout": "grid",
  "columns": 2,
  "widgets": [
    {
      "type": "metric",
      "title": "Revenue",
      "value": "$12,345",
      "trend": "up"
    },
    {
      "type": "chart",
      "title": "Sales",
      "chartType": "line",
      "dataKey": "salesData"
    },
    {
      "type": "table",
      "title": "Recent Orders",
      "columns": ["id", "customer", "amount"],
      "dataKey": "orders"
    }
  ]
}
```

## 2. Create the Catalog

Define a catalog that describes your components and validates props using `defineCatalog` — see [Catalog](/docs/catalog).

```typescript
import { defineCatalog } from '@json-render/core';
import { z } from 'zod';

export const dashboardCatalog = defineCatalog(mySchema, {
  components: {
    metric: {
      props: z.object({
        title: z.string(),
        value: z.string(),
        trend: z.enum(['up', 'down', 'flat']).optional(),
        change: z.string().optional(),
      }),
    },
    chart: {
      props: z.object({
        title: z.string(),
        chartType: z.enum(['line', 'bar', 'pie', 'area']),
        dataKey: z.string(),
        height: z.number().optional(),
      }),
    },
    table: {
      props: z.object({
        title: z.string(),
        columns: z.array(z.string()),
        dataKey: z.string(),
        pageSize: z.number().optional(),
      }),
    },
    text: {
      props: z.object({
        content: z.string(),
        variant: z.enum(['heading', 'body', 'caption']).optional(),
      }),
    },
  },
});
```

## 3. Define the Root Schema

Create a schema for the overall document structure:

```typescript
import { z } from 'zod';

const WidgetSchema = z.object({
  type: z.string(),
  title: z.string().optional(),
  // Additional props validated by catalog
}).passthrough();

export const DashboardSchema = z.object({
  layout: z.enum(['grid', 'stack', 'tabs']),
  columns: z.number().optional(),
  widgets: z.array(WidgetSchema),
});

export type Dashboard = z.infer<typeof DashboardSchema>;
export type Widget = z.infer<typeof WidgetSchema>;
```

## 4. Build the Renderer

Create a renderer that maps your schema to React components:

```tsx
import React from 'react';
import { dashboardCatalog } from './catalog';
import type { Dashboard, Widget } from './schema';

// Widget component registry
const widgetComponents: Record<string, React.FC<any>> = {
  metric: ({ title, value, trend, change }) => (
    
      {title}
      {value}
      {trend && (
        
          {trend === 'up' ? '+' : '-'}{change}
        
      )}
    
  ),

  chart: ({ title, chartType, data }) => (
    
      {title}
      
        {/* Your chart library here */}
        {chartType} chart
      
    
  ),

  table: ({ title, columns, data }) => (
    
      {title}
      

| {col} |
| --- |
| {row[col]} |


    
  ),

  text: ({ content, variant = 'body' }) => {
    const className = {
      heading: 'text-xl font-bold',
      body: 'text-base',
      caption: 'text-sm text-muted-foreground',
    }[variant];
    return {content};
  },
};

// Main renderer
export function DashboardRenderer({
  spec,
  data = {},
}: {
  spec: Dashboard;
  data?: Record<string, any>;
}) {
  const layoutClass = {
    grid: `grid gap-4 ${spec.columns ? `grid-cols-${spec.columns}` : 'grid-cols-2'}`,
    stack: 'flex flex-col gap-4',
    tabs: 'space-y-4',
  }[spec.layout];

  return (
    
      {spec.widgets.map((widget, index) => {
        const Component = widgetComponents[widget.type];
        if (!Component) {
          console.warn(`Unknown widget type: ${widget.type}`);
          return null;
        }

        // Resolve data references
        const widgetData = widget.dataKey ? data[widget.dataKey] : undefined;

        return (
          <Component
            key={index}
            {...widget}
            data={widgetData}
          />
        );
      })}
    
  );
}
```

## 5. Generate LLM Prompts

Use the catalog to generate system prompts for AI:

```typescript
const systemPrompt = dashboardCatalog.prompt({
  customRules: [
    'Use metric widgets for single KPI values',
    'Use chart widgets for time-series data',
    'Use table widgets for lists of records',
    'Limit dashboards to 6 widgets maximum',
  ],
});

// Use with any LLM
const response = await generateText({
  model: 'gpt-4',
  system: systemPrompt,
  prompt: 'Create a sales dashboard with revenue, orders, and a chart',
});
```

## 6. Validate Specs

Validate incoming specs against your schema. Use `catalog.validate()` to check AI output against the catalog's Zod schema:

```typescript
function validateDashboard(spec: unknown) {
  // Validate root structure
  const rootResult = DashboardSchema.safeParse(spec);
  if (!rootResult.success) {
    return { valid: false, errors: rootResult.error.errors };
  }

  // Validate each widget's props against the catalog
  const result = dashboardCatalog.validate(spec);
  if (!result.success) {
    return { valid: false, errors: result.error.errors };
  }

  return { valid: true, errors: [] };
}
```

## Usage Example

```tsx
'use client';

import { useState } from 'react';
import { DashboardRenderer } from './renderer';
import type { Dashboard } from './schema';

const initialSpec: Dashboard = {
  layout: 'grid',
  columns: 2,
  widgets: [
    { type: 'metric', title: 'Revenue', value: '$12,345', trend: 'up' },
    { type: 'metric', title: 'Orders', value: '156', trend: 'up' },
    { type: 'chart', title: 'Sales Trend', chartType: 'line', dataKey: 'sales' },
    { type: 'table', title: 'Recent Orders', columns: ['id', 'customer', 'amount'], dataKey: 'orders' },
  ],
};

const data = {
  sales: [/* chart data */],
  orders: [
    { id: '001', customer: 'Acme Inc', amount: '$500' },
    { id: '002', customer: 'Globex', amount: '$750' },
  ],
};

export function MyDashboard() {
  const [spec, setSpec] = useState(initialSpec);

  return <DashboardRenderer spec={spec} data={data} />;
}
```

## Next

See how to integrate with [A2UI](/docs/a2ui) or [Adaptive Cards](/docs/adaptive-cards) protocols.

---

## File: apps/web/app/(main)/docs/computed-values/page.mdx

import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("docs/computed-values")

# Computed Values

Derive dynamic prop values using registered functions or string templates.

## `$template` — String Interpolation

Use `{ "$template": "..." }` to embed state values into a string. References use `${/path}` syntax where the path is a JSON Pointer:

```json
{
  "type": "Text",
  "props": {
    "text": { "$template": "Hello, ${/user/name}! You have ${/inbox/count} messages." }
  },
  "children": []
}
```

If state is `{ "user": { "name": "Alice" }, "inbox": { "count": 3 } }`, the text renders as "Hello, Alice! You have 3 messages."

Missing paths resolve to an empty string.

## `$computed` — Registered Functions

Use `{ "$computed": "<name>", "args": { ... } }` to call a named function registered in your catalog. Each arg can be a literal value or any prop expression (`$state`, `$item`, `$cond`, etc.):

```json
{
  "type": "Text",
  "props": {
    "text": {
      "$computed": "fullName",
      "args": {
        "first": { "$state": "/form/firstName" },
        "last": { "$state": "/form/lastName" }
      }
    }
  },
  "children": []
}
```

### Registering Functions

Functions are registered in the catalog and provided at runtime.

**Catalog definition (for AI prompt generation):**

```typescript
import { defineCatalog } from '@json-render/core';
import { schema } from '@json-render/react/schema';

const catalog = defineCatalog(schema, {
  components: { /* ... */ },
  functions: {
    fullName: {
    },
    formatCurrency: {
    },
  },
});
```

**Runtime implementation:**

```tsx
import { JSONUIProvider } from '@json-render/react';

const functions = {
  fullName: (args) => `${args.first ?? ''} ${args.last ?? ''}`.trim(),
  formatCurrency: (args) => {
    const value = Number(args.value ?? 0);
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: (args.currency as string) ?? 'USD',
    }).format(value);
  },
};

<JSONUIProvider registry={registry} functions={functions}>
  <Renderer spec={spec} registry={registry} />
</JSONUIProvider>
```

### Using with `createRenderer`

```tsx
const MyRenderer = createRenderer(catalog, components);

<MyRenderer
  spec={spec}
  functions={functions}
/>
```

## Combining Expressions

`$computed` args can use any expression type. This example computes a total from repeat item fields:

```json
{
  "$computed": "lineTotal",
  "args": {
    "price": { "$item": "price" },
    "quantity": { "$item": "quantity" }
  }
}
```

## Next

- [Watchers](/docs/watchers) — react to state changes with cascading actions
- [Data Binding](/docs/data-binding) — all expression types
- [Validation](/docs/validation) — validate form inputs

---

## File: apps/web/app/(main)/docs/code-export/page.mdx

import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("docs/code-export")

# Code Export

Export generated UI as standalone code for your framework.

## Overview

While json-render is designed for dynamic rendering, you can export generated UI as static code. The code generation is intentionally project-specific so you have full control over:

- Component templates (standalone, no json-render dependencies)
- Package.json and project structure
- Framework-specific patterns (Next.js, Remix, etc.)
- How data is passed to components

## Architecture

Code export is split into two parts:

### 1. @json-render/codegen (utilities)

Framework-agnostic utilities for building code generators:

```typescript
import {
  traverseSpec,          // Walk the UI spec
  collectUsedComponents, // Get all component types used
  collectStatePaths,      // Get all data binding paths
  collectActions,        // Get all action names
  serializeProps,        // Convert props to JSX string
} from '@json-render/codegen';
```

### 2. Your Project (generator)

Custom code generator specific to your project and framework:

```typescript
// lib/codegen/generator.ts
import { collectUsedComponents, serializeProps } from '@json-render/codegen';

export function generateNextJSProject(spec: Spec): GeneratedFile[] {
  const components = collectUsedComponents(spec);
  
  return [
    { path: 'package.json', content: '...' },
    { path: 'app/page.tsx', content: '...' },
    // ... component files
  ];
}
```

## Example: Next.js Export

See the dashboard example for a complete implementation that exports:

- `package.json` - Dependencies and scripts
- `tsconfig.json` - TypeScript config
- `next.config.js` - Next.js config
- `app/layout.tsx` - Root layout
- `app/globals.css` - Global styles
- `app/page.tsx` - Generated page with data
- `components/ui/*.tsx` - Standalone components

## Standalone Components

The exported components are standalone with no json-render dependencies. They receive data as props instead of using hooks:

```tsx
// Generated component (standalone)
interface MetricProps {
  label: string;
  statePath: string;
  data?: Record<string, unknown>;
}

export function Metric({ label, statePath, data }: MetricProps) {
  const value = data ? getByPath(data, statePath) : undefined;
  return (
    
      {label}
      {formatValue(value)}
    
  );
}
```

## Using the Utilities

### traverseSpec

```typescript
import { traverseSpec } from '@json-render/codegen';

traverseSpec(spec, (element, key, depth, parent) => {
  console.log(' '.repeat(depth * 2) + `${key}: ${element.type}`);
});
```

### collectUsedComponents

```typescript
import { collectUsedComponents } from '@json-render/codegen';

const components = collectUsedComponents(spec);
// Set { 'Card', 'Metric', 'Chart', 'Table' }

// Generate only the needed component files
for (const component of components) {
  files.push({
    path: `components/ui/${component.toLowerCase()}.tsx`,
    content: componentTemplates[component],
  });
}
```

### serializeProps

```typescript
import { serializeProps } from '@json-render/codegen';

const propsStr = serializeProps({
  title: 'Dashboard',
  columns: 3,
  disabled: true,
});
// 'title="Dashboard" columns={3} disabled'
```

## Try It

Run the dashboard example and click "Export Project" to see code generation in action:

```bash
cd examples/dashboard
pnpm dev
# Open http://dashboard-demo.json-render.localhost:1355
# Generate a widget, then click "Export Project"
```

---

## File: apps/web/app/(main)/docs/changelog/page.mdx

import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("docs/changelog")

# Changelog

Notable changes and updates to json-render.

## v0.20.0

August 15, 2026

### New: Named Slots for React

React components can now declare named slots such as `header` and `footer`, while `children` remains the default slot. Named slots are preserved through validation, streaming, nested conversion, code export, playground views, and Devtools navigation.

This work builds on the original named slots contribution by @wotnak.

```tsx
const catalog = defineCatalog(schema, {
  components: {
    Layout: {
      props: z.object({}),
      slots: ["default", "header", "footer"],
    },
  },
});

const { registry } = defineRegistry(catalog, {
  components: {
    Layout: ({ children, slots }) => (
      <section>
        <header>{slots?.header}</header>
        <main>{children}</main>
        <footer>{slots?.footer}</footer>
      </section>
    ),
  },
});
```

### New: Nested Repeats

`repeat.statePath` now accepts item-relative paths such as `{ "$item": "employees" }`. Nested data can be rendered across React, React Native, React Email, React PDF, Image, Ink, Solid, Svelte, and Vue.

This work builds on the original nested repeats contribution by @tmchow.

```json
{
  "type": "Table",
  "repeat": { "statePath": { "$item": "employees" }, "key": "id" },
  "children": ["employee-row"],
  "props": {}
}
```

### New: Harness Chat Example

Added a complete Next.js example using the AI SDK 7 harness adapter, agent delegation, sandbox transport, and json-render components.

### Fixed: Chained Action Params

Named `onSuccess` and `onError` actions now receive their configured `params` across core and all renderer bridges. Custom renderer bridges must now accept an `ActionBinding` in the core `executeAction` callback instead of a bare action name.

### Improved: Spec Validation and Compatibility

Element visibility remains optional across Zod 4 versions. Autofix now prunes dangling child references, validation reports malformed visibility conditions, and repeated items can be filtered safely.

### Improved: Release Toolchain

The workspace now requires Node.js 24 and pnpm 11, enforces package engine checks, and applies a minimum package release age.

---

## v0.19.0

May 6, 2026

### New: Custom Directives API

`@json-render/core` now supports custom directives via `defineDirective`, letting you declare new JSON shapes (like `$format`, `$math`) that resolve to computed values at render time. Directives compose naturally -- nest `$format` over `$math` over `$state` and they resolve inside-out. All four renderers (React, Vue, Svelte, Solid) have built-in directive resolution.

### New: `@json-render/directives`

New package shipping seven ready-made directives: `$format` (date, currency, number, percent via `Intl`), `$math` (arithmetic and rounding), `$concat`, `$count`, `$truncate`, `$pluralize`, and `$join`. Also exports `createI18nDirective` for `$t` translation keys with interpolation, and `standardDirectives` for one-line registration.

```bash
npm install @json-render/directives
```

```tsx
import { standardDirectives } from "@json-render/directives";

const catalog = createCatalog({
  directives: standardDirectives,
  // ...
});
```

See the [Directives guide](/docs/directives) and the [API reference](/docs/api/directives) for details.

---

## v0.18.0

April 17, 2026

### New: Devtools

Five new packages for inspecting json-render apps in the browser:

- `@json-render/devtools` -- framework-agnostic core
- `@json-render/devtools-react`
- `@json-render/devtools-vue`
- `@json-render/devtools-svelte`
- `@json-render/devtools-solid`

Drop `<JsonRenderDevtools />` into your app to get a shadow-DOM-isolated panel with six tabs (Spec, State, Actions, Stream, Catalog, Pick), a DOM picker that maps clicked elements back to spec keys via `data-jr-key`, and a capped event store. Toggle with the floating button or `Cmd`/`Ctrl` + `Shift` + `J`. Tree-shakes to `null` in production.

```bash
npm install @json-render/devtools-react
```

```tsx
import { JsonRenderDevtools } from "@json-render/devtools-react";

<JsonRenderDevtools />
```

See the [Devtools guide](/docs/devtools) and the [API reference](/docs/api/devtools) for details.

### New: Devtools Example

New `examples/devtools` Next.js demo showing the full devtools panel wired up to an AI chat endpoint and a component catalog.

### New: Action Observer and Devtools Flag in Core

`@json-render/core` now exposes an action observer and a devtools enablement flag that framework adapters use to mirror actions and stream events into the panel.

### Fixed: Zod 4 Schema Formatting

`formatZodType` now correctly handles `z.record()`, `z.default()`, and `z.literal()` types from Zod 4, which previously produced incorrect or empty output in generated prompts and schemas.

---

## v0.17.0

April 10, 2026

### New: Gaussian Splatting

Added `GaussianSplat` component to `@json-render/react-three-fiber`, bringing the component count to 20. Composable with all existing R3F components (lights, controls, post-processing) via drei's Splat loader.

### New: R3F Gaussian Splatting Example

Demo app with five scenes: splat showroom, splat with primitives, multi-splat, post-processing effects, and animated floating splat.

### New: Standalone gsplat Example

Experimental demo app showcasing Gaussian Splatting with gsplat.js (no Three.js dependency), featuring scene selector, live JSON spec viewer, and progress indicator.

### Improved: AI Output Quality

Improved prompt output and schema generation for more reliable AI-generated specs.

---

## v0.16.0

March 27, 2026

### New: `@json-render/next`

Next.js renderer that turns JSON specs into full Next.js applications with routes, layouts, SSR, metadata, data loaders, and static generation. Client and server entry points at `@json-render/next` and `@json-render/next/server`. Includes built-in `Link`, `Slot`, error boundary, loading, and not-found components.

```bash
npm install @json-render/next
```

### New: `@json-render/shadcn-svelte`

Pre-built shadcn-svelte components for json-render Svelte apps. 36 components built on Svelte 5 + Tailwind CSS with state binding, validation, and action support. Server-safe catalog at `@json-render/shadcn-svelte/catalog`.

```bash
npm install @json-render/shadcn-svelte
```

### Improved: Release Process

Switched from Changesets to a manual single-PR release workflow with changelog markers and automatic npm publish on version bump.

---

## v0.15.0

March 23, 2026

### New: `@json-render/ink`

Terminal renderer for json-render. JSON becomes terminal UIs powered by [Ink](https://github.com/vadimdemedes/ink). Stream AI-generated specs directly to the terminal with `useUIStream`.

```bash
npm install @json-render/ink
```

### Improved: YAML Format Support in `buildUserPrompt`

`buildUserPrompt` now accepts `format` and `serializer` options, enabling YAML as a wire format alongside JSON.

---

## v0.14.0

March 13, 2026

### New: `@json-render/yaml`

YAML wire format for json-render. Includes streaming YAML parser, `yamlPrompt()` for system prompts, and AI SDK transform (`pipeYamlRender`) as a drop-in alternative to JSONL streaming. Supports four fence types: `yaml-spec`, `yaml-edit`, `yaml-patch`, and `diff`.

```bash
npm install @json-render/yaml
```

### New: Universal Edit Modes

Three strategies for multi-turn spec refinement in `@json-render/core`:

- **Patch** -- RFC 6902 JSON Patch
- **Merge** -- RFC 7396 Merge Patch
- **Diff** -- Unified diff

New `editModes` option on `buildUserPrompt()` and `PromptOptions`. New helpers: `deepMergeSpec()`, `diffToPatches()`, `buildEditUserPrompt()`, `buildEditInstructions()`, `isNonEmptySpec()`.

### Improved: Playground

Format toggle (JSONL / YAML), edit mode picker (patch / merge / diff), and token usage display with prompt caching stats.

### Improved: Prompt Caching

Generate API uses Anthropic ephemeral cache control for system prompts.

---

## v0.13.0

March 12, 2026

### New: `@json-render/solid`

SolidJS renderer for json-render. JSON becomes Solid components with reactive rendering, schema export, and full catalog support.

```bash
npm install @json-render/core @json-render/solid
```

### New: `@json-render/react-three-fiber`

React Three Fiber renderer for json-render. JSON becomes 3D scenes with 19 built-in components for meshes, lights, models, environments, text, cameras, and controls.

```bash
npm install @json-render/react-three-fiber
```

### Improved: Strict JSON Schema Mode

`catalog.jsonSchema({ strict: true })` produces a JSON Schema subset compatible with LLM structured output APIs (OpenAI, Google Gemini, Anthropic). Ensures `additionalProperties: false` on every object and all properties listed in `required`.

---

## v0.12.1

March 11, 2026

### Changed: Generation Mode Renames

Renamed generation modes from `"generate"` / `"chat"` to `"standalone"` / `"inline"`. The old names still work but emit a deprecation warning.

### Fixed: MCP React Duplicate Module Error

Resolved React duplicate module error (`useRef` returning null) in `@json-render/mcp` by adding `resolve.dedupe` Vite configuration. Added `./build-app-html` export entry point.

---

## v0.12.0

March 6, 2026

### New: `@json-render/svelte`

Svelte 5 renderer with runes-based reactivity. Full support for data binding, visibility, actions, validation, watchers, streaming, and repeat scopes. Includes `defineRegistry`, `Renderer`, `schema`, composables, and context providers.

```bash
npm install @json-render/core @json-render/svelte
```

### New: `@json-render/react-email`

React Email renderer for generating HTML and plain-text emails from JSON specs. 17 standard components (Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown). Server-side `renderToHtml` / `renderToPlainText` APIs.

```bash
npm install @json-render/react-email
```

### New: `@json-render/mcp`

MCP Apps integration that serves json-render UIs as interactive apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients. `createMcpApp` server factory, `useJsonRenderApp` React hook for iframes, and `buildAppHtml` utility.

```bash
npm install @json-render/mcp
```

---

## v0.11.0

February 27, 2026

### New: `@json-render/image`

Server-side image renderer powered by Satori. Turns the same `{ root, elements }` spec format into SVG or PNG output for OG images, social cards, and banners.

```bash
npm install @json-render/image
```

```typescript
import { renderToSvg, renderToPng } from "@json-render/image/render";
import { standardComponentDefinitions } from "@json-render/image/catalog";

const svg = await renderToSvg(spec, { width: 1200, height: 630 });
const png = await renderToPng(spec, { width: 1200, height: 630 });
```

9 standard components: Frame, Box, Row, Column, Heading, Text, Image, Divider, Spacer. Server-safe import path at `@json-render/image/server`.

---

## v0.10.0

February 25, 2026

### New: `@json-render/vue`

Vue 3 renderer for json-render with full feature parity with `@json-render/react`. Data binding, visibility conditions, actions, validation, repeat scopes, streaming, and external store support.

```bash
npm install @json-render/core @json-render/vue
```

```typescript
import { h } from "vue";
import { defineRegistry, Renderer } from "@json-render/vue";
import { schema } from "@json-render/vue/schema";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: ({ props, children }) =>
      h("div", { class: "card" }, [h("h3", null, props.title), children]),
    Button: ({ props, emit }) =>
      h("button", { onClick: () => emit("press") }, props.label),
  },
});
```

Providers: `StateProvider`, `ActionProvider`, `VisibilityProvider`, `ValidationProvider`. Composables: `useStateStore`, `useStateValue`, `useActions`, `useAction`, `useIsVisible`, `useFieldValidation`, `useBoundProp`, `useUIStream`, `useChatUI`.

See the [Vue API reference](/docs/api/vue) for details.

### New: `@json-render/xstate`

[XState Store](https://stately.ai/docs/xstate-store) (atom) adapter for json-render's `StateStore` interface. Wire an `@xstate/store` atom as the state backend for any renderer.

```bash
npm install @json-render/xstate @xstate/store
```

```typescript
import { createAtom } from "@xstate/store";
import { xstateStoreStateStore } from "@json-render/xstate";

const atom = createAtom({ count: 0 });
const store = xstateStoreStateStore({ atom });
```

Requires `@xstate/store` v3+.

### New: `$computed` and `$template` Expressions

Two new prop expression types for dynamic values:

- **`$template`** -- interpolate state values into strings: `{ "$template": "Hello, ${/user/name}!" }`
- **`$computed`** -- call registered functions: `{ "$computed": "fullName", "args": { "first": { "$state": "/form/firstName" } } }`

Register functions via the `functions` prop on `JSONUIProvider` or `createRenderer`. See [Computed Values](/docs/computed-values) for details.

### New: State Watchers

Elements can declare a `watch` field to trigger actions when state values change. Useful for cascading dependencies like country/city selects.

```json
{
  "type": "Select",
  "props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
  "watch": {
    "/form/country": { "action": "loadCities", "params": { "country": { "$state": "/form/country" } } }
  }
}
```

`watch` is a top-level field on elements (sibling of type/props/children), not inside props. Watchers only fire on value changes, not on initial render. See [Watchers](/docs/watchers) for details.

### New: Cross-Field Validation

New built-in validation functions for cross-field comparisons:

- `equalTo` -- alias for `matches` with clearer semantics
- `lessThan` -- value must be less than another field
- `greaterThan` -- value must be greater than another field
- `requiredIf` -- required only when a condition field is truthy

Validation check args now resolve through `resolvePropValue`, so `$state` expressions work consistently.

### New: `validateForm` Action

Built-in action (React) that validates all registered form fields at once and writes `{ valid, errors }` to state:

```json
{
  "on": {
    "press": [
      { "action": "validateForm", "params": { "statePath": "/formResult" } },
      { "action": "submitForm" }
    ]
  }
}
```

### Improved: shadcn/ui Validation

All form components now support `checks` and `validateOn` props:
- Checkbox, Radio, Switch added validation support
- `validateOn` controls timing: `"change"` (default for Select, Checkbox, Radio, Switch), `"blur"` (default for Input, Textarea), or `"submit"`

### New Examples

- **Vue example** -- standalone Vue 3 app with custom components
- **Vite Renderers** -- side-by-side React and Vue renderers with shared catalog

---

## v0.9.1

February 24, 2026

### Fixed: Install failure due to private dependency

`@json-render/react`, `@json-render/react-pdf`, and `@json-render/react-native` v0.9.0 failed to install because `@internal/react-state` (a private workspace package) was published as a dependency. The internal package is now bundled into each renderer at build time, so it no longer needs to be resolved from npm.

---

## v0.9.0

February 24, 2026

### New: External State Store

The `StateStore` interface lets you plug in your own state management (Redux, Zustand, Jotai, XState, etc.) instead of the built-in internal store. Pass a `store` prop to `StateProvider`, `JSONUIProvider`, or `createRenderer` for controlled mode.

- Added `StateStore` interface and `createStateStore()` factory to `@json-render/core`
- `StateProvider`, `JSONUIProvider`, and `createRenderer` now accept an optional `store` prop
- When `store` is provided, it becomes the single source of truth (`initialState`/`onStateChange` are ignored)
- When `store` is omitted, everything works exactly as before (fully backward compatible)
- Applied across all platform packages: react, react-native, react-pdf
- Store utilities (`createStoreAdapter`, `immutableSetByPath`, `flattenToPointers`) available via `@json-render/core/store-utils` for building custom adapters

New adapter packages: `@json-render/redux`, `@json-render/zustand`, `@json-render/jotai`.

See the [Data Binding](/docs/data-binding#external-store-controlled-mode) guide for usage.

### Changed: `onStateChange` signature updated (breaking)

The `onStateChange` callback now receives a single array of changed entries instead of being called once per path. This makes batch updates via `update()` easier to handle:

```ts
// Before
onStateChange?: (path: string, value: unknown) => void

// After
onStateChange?: (changes: Array<{ path: string; value: unknown }>) => void
```

The callback is only called when a `set()` or `update()` call actually changes the state. A `set()` call produces a single-element array; an `update()` call produces one array with all changed paths.

### Fixed: Server-safe schema import

`@json-render/react` barrel-imports React contexts that call `createContext`, which crashes in Next.js App Router API routes (RSC runtime strips `createContext`). All docs, examples, and skills now import `schema` from `@json-render/react/schema` instead of `@json-render/react`.

For combined imports, split into separate `schema` (subpath) and client API (main entry) lines:

```ts
import { schema } from "@json-render/react/schema";
import { defineRegistry, Renderer } from "@json-render/react";
```

### Fixed: Chaining actions

Fixed an issue where chaining multiple actions on the same event (e.g. `setState` followed by a custom action) did not execute all actions. Affected `@json-render/react`, `@json-render/react-native`, and `@json-render/react-pdf`.

### Fixed: Zod array inner type resolution

Fixed safely resolving the inner type for Zod arrays in schema introspection, preventing errors when catalog component props use `z.array()`.

---

## v0.8.0

February 20, 2026

### New: `@json-render/react-pdf`

PDF renderer for json-render, powered by [`@react-pdf/renderer`](https://react-pdf.org/). Define catalogs and registries the same way as `@json-render/react`, but output PDF documents instead of web UI.

```bash
npm install @json-render/core @json-render/react-pdf
```

```typescript
import { renderToBuffer } from "@json-render/react-pdf";
import type { Spec } from "@json-render/core";

const spec: Spec = {
  root: "doc",
  elements: {
    doc: { type: "Document", props: { title: "Invoice" }, children: ["page"] },
    page: {
      type: "Page",
      props: { size: "A4" },
      children: ["heading", "table"],
    },
    heading: {
      type: "Heading",
      props: { text: "Invoice #1234", level: "h1" },
      children: [],
    },
    table: {
      type: "Table",
      props: {
        columns: [
          { header: "Item", width: "60%" },
          { header: "Price", width: "40%", align: "right" },
        ],
        rows: [
          ["Widget A", "$10.00"],
          ["Widget B", "$25.00"],
        ],
      },
      children: [],
    },
  },
};

const buffer = await renderToBuffer(spec);
```

Server-side rendering APIs:

- `renderToBuffer(spec)` -- render to an in-memory PDF buffer
- `renderToStream(spec)` -- render to a readable stream (pipe to HTTP response)
- `renderToFile(spec, path)` -- render directly to a file

15 standard components covering document structure (Document, Page), layout (View, Row, Column), content (Heading, Text, Image, Link), data (Table, List), decorative (Divider, Spacer), and page-level (PageNumber).

Supports custom catalogs with `defineRegistry`, server-safe imports via `@json-render/react-pdf/server`, and full context support (state, visibility, actions, validation, repeat scopes).

---

## v0.7.0

February 17, 2026

### New: `@json-render/shadcn`

Pre-built [shadcn/ui](https://ui.shadcn.com/) component library for json-render. 36 components built on Radix UI + Tailwind CSS, ready to use with `defineCatalog` and `defineRegistry`.

```bash
npm install @json-render/shadcn
```

```typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
import { defineRegistry } from "@json-render/react";
import { shadcnComponents } from "@json-render/shadcn";

const catalog = defineCatalog(schema, {
  components: {
    Card: shadcnComponentDefinitions.Card,
    Button: shadcnComponentDefinitions.Button,
    Input: shadcnComponentDefinitions.Input,
  },
  actions: {},
});

const { registry } = defineRegistry(catalog, {
  components: {
    Card: shadcnComponents.Card,
    Button: shadcnComponents.Button,
    Input: shadcnComponents.Input,
  },
});
```

Components include: layout (Card, Stack, Grid, Separator), navigation (Tabs, Accordion, Collapsible, Pagination), overlay (Dialog, Drawer, Tooltip, Popover, DropdownMenu), content (Heading, Text, Image, Avatar, Badge, Alert, Carousel, Table), feedback (Progress, Skeleton, Spinner), and input (Button, Link, Input, Textarea, Select, Checkbox, Radio, Switch, Slider, Toggle, ToggleGroup, ButtonGroup).

See the [API reference](/docs/api/shadcn) for full details.

### New: Event Handles (`on()`)

Components now receive an `on(event)` function in addition to `emit(event)`. The `on()` function returns an `EventHandle` with metadata:

- `emit()` -- fire the event
- `shouldPreventDefault` -- whether any action binding requested `preventDefault`
- `bound` -- whether any handler is bound to this event

```tsx
Link: ({ props, on }) => {
  const click = on("click");
  return (
    <a href={props.href} onClick={(e) => {
      if (click.shouldPreventDefault) e.preventDefault();
      click.emit();
    }}>{props.label}</a>
  );
},
```

### New: `BaseComponentProps`

Catalog-agnostic base type for component render functions. Use when building reusable component libraries (like `@json-render/shadcn`) that are not tied to a specific catalog.

```typescript
import type { BaseComponentProps } from "@json-render/react";

const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
  {props.title}{children}
);
```

### New: Built-in Actions in Schema

Schemas can now declare `builtInActions` -- actions that are always available at runtime and automatically injected into prompts. The React schema declares `setState`, `pushState`, and `removeState` as built-in, so they appear in prompts without needing to be listed in catalog `actions`.

### New: `preventDefault` on `ActionBinding`

Action bindings now support a `preventDefault` boolean field, allowing the LLM to request that default browser behavior (e.g. navigation on links) be prevented.

### Improved: Stream Transform Text Block Splitting

`createJsonRenderTransform()` now properly splits text blocks around spec data by emitting `text-end`/`text-start` pairs. This ensures the AI SDK creates separate text parts, preserving correct interleaving of prose and UI in `message.parts`.

### Improved: `defineRegistry` Actions Requirement

`defineRegistry` now conditionally requires the `actions` field only when the catalog declares actions. Catalogs with no actions (e.g. `actions: {}`) no longer need to pass an empty actions object.

---

## v0.6.0

February 13, 2026

### New: Chat Mode (Inline GenUI)

> **Note:** These modes were renamed in v0.12.1 — "Generate" is now "Standalone" and "Chat" is now "Inline". The old names are accepted as deprecated aliases.

json-render now supports two generation modes: **Generate** (JSONL-only, the default) and **Chat** (text + JSONL inline). Chat mode lets the AI respond conversationally with embedded UI specs, ideal for chatbots and copilot experiences.

```typescript
// Generate mode (default) — AI outputs only JSONL
const prompt = catalog.prompt();

// Chat mode — AI outputs text + JSONL inline
const chatPrompt = catalog.prompt({ mode: "chat" });
```

On the server, `pipeJsonRender()` separates text from JSONL patches in a mixed stream:

```typescript
import { pipeJsonRender } from "@json-render/core";
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";

const stream = createUIMessageStream({
  execute: async ({ writer }) => {
    writer.merge(pipeJsonRender(result.toUIMessageStream()));
  },
});
return createUIMessageStreamResponse({ stream });
```

On the client, `useJsonRenderMessage` extracts the spec and text from message parts:

```tsx
import { useJsonRenderMessage } from "@json-render/react";

function ChatMessage({ message }) {
  const { spec, text, hasSpec } = useJsonRenderMessage(message.parts);
  return (
    
      {text && <Markdown>{text}</Markdown>}
      {hasSpec && <Renderer spec={spec} registry={registry} />}
    
  );
}
```

### New: AI SDK Integration

First-class Vercel AI SDK support with typed data parts and stream utilities.

- `SpecDataPart` type for `data-spec` stream parts (patch, flat, nested payloads)
- `SPEC_DATA_PART` / `SPEC_DATA_PART_TYPE` constants for type-safe part filtering
- `createJsonRenderTransform()` low-level TransformStream for custom pipelines
- `createMixedStreamParser()` for parsing mixed text + JSONL streams

### New: Two-Way Binding

Props can now use `$bindState` and `$bindItem` expressions for two-way data binding. The renderer resolves bindings and passes a `bindings` map to components, enabling write-back to state without custom `valuePath` props.

```json
{
  "type": "Input",
  "props": { "label": "Email", "value": { "$bindState": "/form/email" } }
}
```

```tsx
import { useBoundProp } from "@json-render/react";

Input: ({ props, bindings }) => {
  const [value, setValue] = useBoundProp<string>(props.value, bindings?.value);
  return  setValue(e.target.value)} />;
}
```

### New: Expression-Based Props and Visibility

All dynamic expressions now use structured `$state`, `$item`, and `$index` objects instead of string token rewriting. This is simpler, more explicit, and works for both props and visibility conditions.

**Props:**

```json
{ "title": { "$state": "/user/name" } }
{ "label": { "$item": "title" } }
{ "position": { "$index": true } }
```

**Visibility:**

```json
{ "$state": "/isAdmin" }
{ "$state": "/role", "eq": "admin" }
[{ "$state": "/isAdmin" }, { "$state": "/feature" }]
{ "$or": [{ "$state": "/roleA" }, { "$state": "/roleB" }] }
{ "$item": "isActive" }
{ "$index": true, "gt": 0 }
```

Comparison operators: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `not`.

### New: React Chat Hooks

- `useChatUI()` — full chat hook with message history, streaming, and spec extraction
- `useJsonRenderMessage()` — extract spec + text from a message's parts array
- `buildSpecFromParts()` / `getTextFromParts()` — utilities for working with AI SDK message parts
- `useBoundProp()` — two-way binding hook for `$bindState` / `$bindItem`

### New: Chat Example

Full-featured chat example (`examples/chat`) with AI agent, tool calls (crypto, GitHub, Hacker News, weather, search), theme toggle, and streaming inline UI generation.

### Improved: Renderer Performance

- `ElementRenderer` is now `React.memo`'d for better performance with repeat lists
- `emit` is always defined (never `undefined`)
- Repeat scope passes the actual item object, eliminating string token rewriting

### Improved: Utilities

- `applySpecPatch()` — typed wrapper for applying a single patch to a Spec
- `nestedToFlat()` — convert nested tree specs to flat format
- `resolveBindings()` / `resolveActionParam()` — resolve binding paths and action params

### Breaking Changes

- `{ $path }` and `{ path }` replaced by `{ $state }`, `{ $item }`, `{ $index }` in props
- Visibility: `{ path }` -> `{ $state }`, `{ and/or/not }` -> `{ $and/$or }` with `not` as operator flag
- `DynamicValue`: `{ path: string }` -> `{ $state: string }`
- `repeat.path` -> `repeat.statePath`
- Action params: `path` -> `statePath` in setState action
- `actionHandlers` -> `handlers` on `JSONUIProvider` / `ActionProvider`
- `AuthState` and `{ auth }` visibility conditions removed (model auth as regular state)
- Legacy catalog API removed: `createCatalog`, `generateCatalogPrompt`, `generateSystemPrompt`
- React exports removed: `createRendererFromCatalog`, `rewriteRepeatTokens`
- Codegen: `traverseTree` -> `traverseSpec`

See the [Migration Guide](/docs/migration) for detailed upgrade instructions.

---

## v0.5.0

February 9, 2026

### New: @json-render/react-native

Full React Native renderer with 25+ standard components, data binding, visibility, actions, and dynamic props. Build AI-generated native mobile UIs with the same catalog-driven approach as web.

```tsx
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react-native/schema";
import {
  standardComponentDefinitions,
  standardActionDefinitions,
} from "@json-render/react-native/catalog";
import { defineRegistry, Renderer } from "@json-render/react-native";

const catalog = defineCatalog(schema, {
  components: { ...standardComponentDefinitions },
  actions: standardActionDefinitions,
});

const { registry } = defineRegistry(catalog, { components: {} });

<Renderer spec={spec} registry={registry} />
```

Includes standard components for layout (Container, Row, Column, ScrollContainer, SafeArea, Pressable, Spacer, Divider), content (Heading, Paragraph, Label, Image, Avatar, Badge, Chip), input (Button, TextInput, Switch, Checkbox, Slider, SearchBar), feedback (Spinner, ProgressBar), and composite (Card, ListItem, Modal).

### New: Event System

Components now use `emit` to fire named events instead of directly dispatching actions. The element's `on` field maps events to action bindings, decoupling component logic from action handling.

```tsx
// Component emits a named event
Button: ({ props, emit }) => (
   emit("press")}>{props.label}
),

// Element spec maps events to actions
{
  "type": "Button",
  "props": { "label": "Submit" },
  "on": { "press": { "action": "submit", "params": { "formId": "main" } } }
}
```

### New: Repeat/List Rendering

Elements can now iterate over state arrays using the `repeat` field. Child elements use `{ "$item": "field" }` to read from the current item and `{ "$index": true }` for the current array index.

```json
{
  "type": "Column",
  "repeat": { "statePath": "/posts", "key": "id" },
  "children": ["post-card"]
}
```

```json
{
  "type": "Card",
  "props": { "title": { "$item": "title" } }
}
```

### New: User Prompt Builder

Build structured user prompts with optional spec refinement and state context:

```typescript
import { buildUserPrompt } from "@json-render/core";

// Fresh generation
buildUserPrompt({ prompt: "create a todo app" });

// Refinement (patch-only mode)
buildUserPrompt({ prompt: "add a toggle", currentSpec: spec });

// With runtime state
buildUserPrompt({ prompt: "show data", state: { todos: [] } });
```

### New: Spec Validation

Validate spec structure and auto-fix common issues:

```typescript
import { validateSpec, autoFixSpec } from "@json-render/core";

const { valid, issues } = validateSpec(spec);
const fixed = autoFixSpec(spec);
```

### Improved: State Management

`DataProvider` has been renamed to `StateProvider` with a clearer API. State is now a first-class part of specs. Elements can bind to state via `$state` expressions, and the built-in `setState` action updates state directly.

### Improved: AI Prompts

Schema prompts now include streaming best practices, repeat/list examples, and state patching guidance. Schemas can also define `defaultRules` that are always included in generated prompts.

### Improved: Documentation

- All documentation pages migrated to MDX
- AI-powered documentation chat
- Dynamic Open Graph images for all docs pages
- Improved playground

### Breaking Changes

- `DataProvider` renamed to `StateProvider`
- `useData` renamed to `useStateStore`, `useDataValue` to `useStateValue`, `useDataBinding` to `useStateBinding`
- `onAction` renamed to `emit` in component context
- `DataModel` type renamed to `StateModel`
- `Action` type renamed to `ActionBinding` (old name still available but deprecated)

---

## v0.4.0

February 5, 2026

### New: Custom Schema System

Create custom output formats with `defineSchema`. Each renderer now defines its own schema, enabling completely different spec formats for different use cases.

```typescript
import { defineSchema } from "@json-render/core";

const mySchema = defineSchema((s) => ({
  spec: s.object({
    pages: s.array(s.object({
      title: s.string(),
      blocks: s.array(s.ref("catalog.blocks")),
    })),
  }),
  catalog: s.object({
    blocks: s.map({ props: s.zod(), description: s.string() }),
  }),
}), {
  promptTemplate: myPromptTemplate,
});
```

### New: Component Slots

Components can now define which slots they accept. Use `["default"]` for regular children, or named slots like `["header", "footer"]` for more complex layouts.

```typescript
const catalog = defineCatalog(schema, {
  components: {
    Card: {
      props: z.object({ title: z.string() }),
      slots: ["default"],  // accepts children
    },
    Layout: {
      props: z.object({}),
      slots: ["header", "content", "footer"],  // named slots
    },
  },
});
```

### New: AI Prompt Generation

Catalogs now generate AI system prompts automatically with `catalog.prompt()`. The prompt includes all component definitions, props schemas, and action descriptions - ensuring the AI only generates valid specs.

```typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";

const catalog = defineCatalog(schema, {
  components: { /* ... */ },
  actions: { /* ... */ },
});

// Generate system prompt for AI
const systemPrompt = catalog.prompt();

// Use with any AI SDK
const result = await streamText({
  model: "claude-haiku-4.5",
  system: systemPrompt,
  prompt: userMessage,
});
```

### New: @json-render/remotion

Generate AI-powered videos with Remotion. Define video catalogs, stream timeline specs, and render with the Remotion Player.

```tsx
import { Player } from "@remotion/player";
import { Renderer, schema, standardComponentDefinitions } from "@json-render/remotion";

const catalog = defineCatalog(schema, {
  components: standardComponentDefinitions,
  transitions: standardTransitionDefinitions,
});


```

Includes 10 standard video components (TitleCard, TypingText, SplitScreen, etc.), 7 transition types, and the ClipWrapper utility for custom components.

### New: SpecStream

SpecStream is json-render's streaming format for progressively building specs from JSONL patches. The new compiler API makes it easy to process streaming AI responses.

```typescript
import { createSpecStreamCompiler } from "@json-render/core";

const compiler = createSpecStreamCompiler<MySpec>();

// Process streaming chunks
const { result, newPatches } = compiler.push(chunk);
setSpec(result); // Update UI with partial result
```

### Improved: Dashboard Example

The dashboard example is now a full-featured accounting dashboard with:

- Persistent SQLite database with Drizzle ORM
- RESTful API for customers, invoices, expenses, accounts
- Draggable widget reordering
- AI-powered widget generation with streaming
- Real data binding to database records

### Improved: Documentation

- Interactive playground for testing specs
- New guides: Custom Schema, Streaming, Code Export
- Full API reference for all packages
- Integration guides: A2UI, AG-UI, Adaptive Cards, OpenAPI

### Breaking Changes

- `UITree` type renamed to `Spec`
- Schema is now imported from renderer packages (`@json-render/react`) not core
- `defineCatalog` now requires a schema as first argument

---

## v0.3.0

January 20, 2026

Internal release with codegen foundations.

- Added `@json-render/codegen` package (spec traversal and JSX serialization)
- Configurable AI model via environment variables
- Documentation improvements and bug fixes

*Note: Only @json-render/core was published to npm for this release.*

---

## v0.2.0

January 14, 2026

Initial public release.

- Core catalog and spec types
- React renderer with contexts for data, actions, visibility
- AI prompt generation from catalogs
- Basic streaming support
- Dashboard example application