Ver Fonte

prepare 0.12.0 (#185)

Chris Tate há 4 meses atrás
pai
commit
63c339b

+ 2 - 1
.changeset/config.json

@@ -18,7 +18,8 @@
       "@json-render/vue",
       "@json-render/xstate",
       "@json-render/image",
-      "@json-render/mcp"
+      "@json-render/mcp",
+      "@json-render/svelte"
     ]
   ],
   "linked": [],

+ 18 - 0
.changeset/v0-12-release.md

@@ -0,0 +1,18 @@
+---
+"@json-render/core": minor
+"@json-render/svelte": minor
+"@json-render/react-email": minor
+"@json-render/mcp": minor
+---
+
+Add Svelte renderer, React Email renderer, and MCP Apps integration.
+
+### 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.
+- **`@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. Custom catalog and registry support.
+- **`@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.
+
+### Fixed:
+
+- **`@json-render/svelte`** — Corrected JSDoc comment and added missing `zod` peer dependency.

+ 22 - 1
README.md

@@ -19,6 +19,8 @@ npm install @json-render/core @json-render/react-pdf
 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
 ```
 
 ## Why json-render?
@@ -28,7 +30,7 @@ json-render is a **Generative UI** framework: AI generates interfaces from natur
 - **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 (web), React Native (mobile) from the same catalog
+- **Cross-Platform** - React, Vue, Svelte (web), React Native (mobile) from the same catalog
 - **Batteries Included** - 36 pre-built shadcn/ui components ready to use
 
 ## Quick Start
@@ -116,12 +118,14 @@ function Dashboard({ spec }) {
 | `@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/shadcn` | 36 pre-built shadcn/ui components (Radix UI + Tailwind CSS) |
 | `@json-render/react-native` | React Native renderer with standard mobile components |
 | `@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/image` | Image renderer for SVG/PNG output (OG images, social cards) via Satori |
+| `@json-render/codegen` | Utilities for generating code from json-render UI trees |
 | `@json-render/redux` | Redux / Redux Toolkit adapter for `StateStore` |
 | `@json-render/zustand` | Zustand adapter for `StateStore` |
 | `@json-render/jotai` | Jotai adapter for `StateStore` |
@@ -178,6 +182,23 @@ const { registry } = defineRegistry(catalog, {
 // <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} />
+```
+
 ### shadcn/ui (Web)
 
 ```tsx

+ 104 - 0
apps/web/app/(main)/docs/api/jotai/page.mdx

@@ -0,0 +1,104 @@
+import { pageMetadata } from "@/lib/page-metadata"
+export const metadata = pageMetadata("docs/api/jotai")
+
+# @json-render/jotai
+
+Jotai adapter for json-render's `StateStore` interface.
+
+## Installation
+
+```bash
+npm install @json-render/jotai @json-render/core @json-render/react jotai
+```
+
+## jotaiStateStore
+
+Create a `StateStore` backed by a Jotai atom.
+
+```typescript
+import { jotaiStateStore } from "@json-render/jotai";
+```
+
+### Options
+
+<table>
+  <thead>
+    <tr>
+      <th>Option</th>
+      <th>Type</th>
+      <th>Required</th>
+      <th>Description</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>atom</code></td>
+      <td><code>{'WritableAtom<StateModel, [StateModel], void>'}</code></td>
+      <td>Yes</td>
+      <td>A writable atom holding the state model.</td>
+    </tr>
+    <tr>
+      <td><code>store</code></td>
+      <td>Jotai <code>Store</code></td>
+      <td>No</td>
+      <td>The Jotai store instance. Defaults to a new store created internally. Pass your own to share state with <code>{'<Provider>'}</code>.</td>
+    </tr>
+  </tbody>
+</table>
+
+### Example
+
+```typescript
+import { atom } from "jotai";
+import { jotaiStateStore } from "@json-render/jotai";
+import { StateProvider } from "@json-render/react";
+
+const uiAtom = atom<Record<string, unknown>>({ count: 0 });
+const store = jotaiStateStore({ atom: uiAtom });
+```
+
+```tsx
+<StateProvider store={store}>
+  {/* json-render reads/writes go through Jotai */}
+</StateProvider>
+```
+
+### Shared Jotai Store
+
+If your app already uses a Jotai `<Provider>` with a custom store, pass it so both json-render and your components share the same state:
+
+```typescript
+import { atom, createStore } from "jotai";
+import { Provider as JotaiProvider } from "jotai/react";
+import { jotaiStateStore } from "@json-render/jotai";
+import { StateProvider } from "@json-render/react";
+
+const jStore = createStore();
+const uiAtom = atom<Record<string, unknown>>({ count: 0 });
+const store = jotaiStateStore({ atom: uiAtom, store: jStore });
+```
+
+```tsx
+<JotaiProvider store={jStore}>
+  <StateProvider store={store}>
+    {/* Both json-render and useAtom() see the same state */}
+  </StateProvider>
+</JotaiProvider>
+```
+
+## Re-exports
+
+<table>
+  <thead>
+    <tr>
+      <th>Export</th>
+      <th>Source</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>StateStore</code></td>
+      <td><code>@json-render/core</code></td>
+    </tr>
+  </tbody>
+</table>

+ 104 - 0
apps/web/app/(main)/docs/api/redux/page.mdx

@@ -0,0 +1,104 @@
+import { pageMetadata } from "@/lib/page-metadata"
+export const metadata = pageMetadata("docs/api/redux")
+
+# @json-render/redux
+
+Redux / Redux Toolkit adapter for json-render's `StateStore` interface.
+
+## Installation
+
+```bash
+npm install @json-render/redux @json-render/core @json-render/react redux
+# or with Redux Toolkit (recommended):
+npm install @json-render/redux @json-render/core @json-render/react @reduxjs/toolkit
+```
+
+## reduxStateStore
+
+Create a `StateStore` backed by a Redux store.
+
+```typescript
+import { reduxStateStore } from "@json-render/redux";
+```
+
+### Options
+
+<table>
+  <thead>
+    <tr>
+      <th>Option</th>
+      <th>Type</th>
+      <th>Required</th>
+      <th>Description</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>store</code></td>
+      <td><code>Store</code></td>
+      <td>Yes</td>
+      <td>The Redux store instance.</td>
+    </tr>
+    <tr>
+      <td><code>selector</code></td>
+      <td><code>{'(state: S) => StateModel'}</code></td>
+      <td>No</td>
+      <td>Select the json-render slice from the Redux state tree. Defaults to <code>{'(state) => state'}</code>.</td>
+    </tr>
+    <tr>
+      <td><code>dispatch</code></td>
+      <td><code>{'(nextState: StateModel, store: Store) => void'}</code></td>
+      <td>Yes</td>
+      <td>Dispatch an action that replaces the selected slice with the next state.</td>
+    </tr>
+  </tbody>
+</table>
+
+### Example
+
+```typescript
+import { configureStore, createSlice } from "@reduxjs/toolkit";
+import { reduxStateStore } from "@json-render/redux";
+import { StateProvider } from "@json-render/react";
+
+const uiSlice = createSlice({
+  name: "ui",
+  initialState: { count: 0 } as Record<string, unknown>,
+  reducers: {
+    replaceUiState: (_state, action) => action.payload,
+  },
+});
+
+const reduxStore = configureStore({
+  reducer: { ui: uiSlice.reducer },
+});
+
+const store = reduxStateStore({
+  store: reduxStore,
+  selector: (state) => state.ui,
+  dispatch: (next, s) => s.dispatch(uiSlice.actions.replaceUiState(next)),
+});
+```
+
+```tsx
+<StateProvider store={store}>
+  {/* json-render reads/writes go through Redux */}
+</StateProvider>
+```
+
+## Re-exports
+
+<table>
+  <thead>
+    <tr>
+      <th>Export</th>
+      <th>Source</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>StateStore</code></td>
+      <td><code>@json-render/core</code></td>
+    </tr>
+  </tbody>
+</table>

+ 77 - 0
apps/web/app/(main)/docs/api/xstate/page.mdx

@@ -0,0 +1,77 @@
+import { pageMetadata } from "@/lib/page-metadata"
+export const metadata = pageMetadata("docs/api/xstate")
+
+# @json-render/xstate
+
+[XState Store](https://stately.ai/docs/xstate-store) adapter for json-render's `StateStore` interface.
+
+Requires `@xstate/store` v3+.
+
+## Installation
+
+```bash
+npm install @json-render/xstate @json-render/core @json-render/react @xstate/store
+```
+
+## xstateStoreStateStore
+
+Create a `StateStore` backed by an `@xstate/store` atom.
+
+```typescript
+import { xstateStoreStateStore } from "@json-render/xstate";
+```
+
+### Options
+
+<table>
+  <thead>
+    <tr>
+      <th>Option</th>
+      <th>Type</th>
+      <th>Required</th>
+      <th>Description</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>atom</code></td>
+      <td><code>{'Atom<StateModel>'}</code></td>
+      <td>Yes</td>
+      <td>An <code>@xstate/store</code> atom (from <code>createAtom</code>) holding the json-render state model.</td>
+    </tr>
+  </tbody>
+</table>
+
+### Example
+
+```typescript
+import { createAtom } from "@xstate/store";
+import { xstateStoreStateStore } from "@json-render/xstate";
+import { StateProvider } from "@json-render/react";
+
+const uiAtom = createAtom({ count: 0 });
+const store = xstateStoreStateStore({ atom: uiAtom });
+```
+
+```tsx
+<StateProvider store={store}>
+  {/* json-render reads/writes go through @xstate/store */}
+</StateProvider>
+```
+
+## Re-exports
+
+<table>
+  <thead>
+    <tr>
+      <th>Export</th>
+      <th>Source</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>StateStore</code></td>
+      <td><code>@json-render/core</code></td>
+    </tr>
+  </tbody>
+</table>

+ 108 - 0
apps/web/app/(main)/docs/api/zustand/page.mdx

@@ -0,0 +1,108 @@
+import { pageMetadata } from "@/lib/page-metadata"
+export const metadata = pageMetadata("docs/api/zustand")
+
+# @json-render/zustand
+
+Zustand adapter for json-render's `StateStore` interface.
+
+Requires Zustand v5+. Zustand v4 is not supported due to breaking API changes in the vanilla store interface.
+
+## Installation
+
+```bash
+npm install @json-render/zustand @json-render/core @json-render/react zustand
+```
+
+## zustandStateStore
+
+Create a `StateStore` backed by a Zustand vanilla store.
+
+```typescript
+import { zustandStateStore } from "@json-render/zustand";
+```
+
+### Options
+
+<table>
+  <thead>
+    <tr>
+      <th>Option</th>
+      <th>Type</th>
+      <th>Required</th>
+      <th>Description</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>store</code></td>
+      <td><code>{'StoreApi<S>'}</code></td>
+      <td>Yes</td>
+      <td>A Zustand vanilla store (from <code>createStore</code> in <code>zustand/vanilla</code>).</td>
+    </tr>
+    <tr>
+      <td><code>selector</code></td>
+      <td><code>{'(state: S) => StateModel'}</code></td>
+      <td>No</td>
+      <td>Select the json-render slice from the store state. Defaults to the entire state.</td>
+    </tr>
+    <tr>
+      <td><code>updater</code></td>
+      <td><code>{'(nextState: StateModel, store: StoreApi<S>) => void'}</code></td>
+      <td>No</td>
+      <td>Apply a state change back to the store. Defaults to a shallow merge.</td>
+    </tr>
+  </tbody>
+</table>
+
+### Example
+
+```typescript
+import { createStore } from "zustand/vanilla";
+import { zustandStateStore } from "@json-render/zustand";
+import { StateProvider } from "@json-render/react";
+
+const bearStore = createStore(() => ({
+  count: 0,
+  name: "Bear",
+}));
+
+const store = zustandStateStore({ store: bearStore });
+```
+
+```tsx
+<StateProvider store={store}>
+  {/* json-render reads/writes go through Zustand */}
+</StateProvider>
+```
+
+### Nested Slice
+
+```typescript
+const appStore = createStore(() => ({
+  ui: { count: 0 },
+  auth: { token: null },
+}));
+
+const store = zustandStateStore({
+  store: appStore,
+  selector: (s) => s.ui,
+  updater: (next, s) => s.setState({ ui: next }),
+});
+```
+
+## Re-exports
+
+<table>
+  <thead>
+    <tr>
+      <th>Export</th>
+      <th>Source</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>StateStore</code></td>
+      <td><code>@json-render/core</code></td>
+    </tr>
+  </tbody>
+</table>

+ 1 - 1
apps/web/app/api/docs-chat/route.ts

@@ -16,7 +16,7 @@ const SYSTEM_PROMPT = `You are a helpful documentation assistant for json-render
 
 GitHub repository: https://github.com/vercel-labs/json-render
 Documentation: https://json-render.dev/docs
-npm packages: @json-render/core, @json-render/react, @json-render/react-email, @json-render/react-pdf, @json-render/image, @json-render/remotion, @json-render/codegen, @json-render/mcp
+npm packages: @json-render/core, @json-render/react, @json-render/vue, @json-render/svelte, @json-render/shadcn, @json-render/react-native, @json-render/react-email, @json-render/react-pdf, @json-render/image, @json-render/remotion, @json-render/codegen, @json-render/mcp, @json-render/redux, @json-render/zustand, @json-render/jotai, @json-render/xstate
 
 You have access to the full json-render documentation via the bash and readFile tools. The docs are available as markdown files in the /workspace/docs/ directory.
 

+ 4 - 0
apps/web/lib/docs-navigation.ts

@@ -134,6 +134,10 @@ export const docsNavigation: NavSection[] = [
       { title: "@json-render/svelte", href: "/docs/api/svelte" },
       { title: "@json-render/codegen", href: "/docs/api/codegen" },
       { title: "@json-render/mcp", href: "/docs/api/mcp" },
+      { title: "@json-render/redux", href: "/docs/api/redux" },
+      { title: "@json-render/zustand", href: "/docs/api/zustand" },
+      { title: "@json-render/jotai", href: "/docs/api/jotai" },
+      { title: "@json-render/xstate", href: "/docs/api/xstate" },
     ],
   },
 ];

+ 4 - 0
apps/web/lib/page-titles.ts

@@ -51,6 +51,10 @@ export const PAGE_TITLES: Record<string, string> = {
   "docs/api/remotion": "@json-render/remotion API",
   "docs/api/shadcn": "@json-render/shadcn API",
   "docs/api/mcp": "@json-render/mcp API",
+  "docs/api/redux": "@json-render/redux API",
+  "docs/api/zustand": "@json-render/zustand API",
+  "docs/api/jotai": "@json-render/jotai API",
+  "docs/api/xstate": "@json-render/xstate API",
 };
 
 /**

+ 155 - 0
packages/svelte/README.md

@@ -0,0 +1,155 @@
+# @json-render/svelte
+
+Svelte 5 renderer for `@json-render/core`. Turn JSON specs into Svelte components with runes-based reactivity.
+
+## Installation
+
+```bash
+npm install @json-render/core @json-render/svelte
+```
+
+Peer dependencies: `svelte ^5.0.0` and `zod ^4.0.0`.
+
+## Quick Start
+
+### 1. Create a Catalog
+
+```typescript
+import { defineCatalog } from "@json-render/core";
+import { schema } from "@json-render/svelte/schema";
+import { z } from "zod";
+
+export const catalog = defineCatalog(schema, {
+  components: {
+    Card: {
+      props: z.object({
+        title: z.string(),
+        description: z.string().nullable(),
+      }),
+      description: "A card container",
+    },
+    Button: {
+      props: z.object({
+        label: z.string(),
+        action: z.string(),
+      }),
+      description: "A clickable button",
+    },
+  },
+  actions: {
+    submit: { description: "Submit the form" },
+  },
+});
+```
+
+### 2. Define Component Implementations
+
+```typescript
+import { defineRegistry } from "@json-render/svelte";
+import { catalog } from "./catalog";
+
+export const { registry } = defineRegistry(catalog, {
+  components: {
+    Card: ({ props, children }) => /* Svelte 5 snippet */,
+    Button: ({ props, emit }) => /* Svelte 5 snippet */,
+  },
+  actions: {
+    submit: async (params) => { /* handle submit */ },
+  },
+});
+```
+
+### 3. Render Specs
+
+```svelte
+<script>
+  import { Renderer, StateProvider, ActionProvider } from "@json-render/svelte";
+  import { registry } from "./registry";
+
+  const spec = { /* ... */ };
+</script>
+
+<StateProvider initialState={{ form: { name: "" } }}>
+  <ActionProvider handlers={{ submit: handleSubmit }}>
+    <Renderer {spec} {registry} />
+  </ActionProvider>
+</StateProvider>
+```
+
+## Providers
+
+| Provider | Purpose |
+|----------|---------|
+| `StateProvider` | Share state across components (JSON Pointer paths). Accepts optional `store` prop for controlled mode. |
+| `ActionProvider` | Handle actions dispatched via the event system |
+| `VisibilityProvider` | Enable conditional rendering based on state |
+| `ValidationProvider` | Form field validation |
+| `RepeatScopeProvider` | Repeat scope for list rendering |
+| `FunctionsContextProvider` | Register `$computed` functions |
+| `JsonUIProvider` | All-in-one provider combining state, actions, visibility, validation, and functions |
+
+## Context Accessors
+
+Svelte 5 uses `getContext`-based accessors instead of hooks:
+
+| Accessor | Purpose |
+|----------|---------|
+| `getStateContext()` | Access state context (`state`, `get`, `set`) |
+| `getStateValue(path)` | Get single value from state |
+| `getBoundProp(value, binding)` | Two-way binding for `$bindState`/`$bindItem` |
+| `getVisibilityContext()` | Access visibility context |
+| `isVisible(condition)` | Check if a visibility condition is met |
+| `getActionContext()` | Access action context |
+| `getAction(binding)` | Get a single action dispatch function |
+| `getValidationContext()` | Access validation context |
+| `getFieldValidation(path, config)` | Field validation state |
+| `getRepeatScope()` | Access current repeat scope |
+| `getFunctions()` | Access registered computed functions |
+
+## Streaming
+
+```typescript
+import { createUIStream, createChatUI } from "@json-render/svelte";
+
+const stream = createUIStream({ endpoint: "/api/generate" });
+
+// Or for chat-style UI:
+const chat = createChatUI({ endpoint: "/api/chat" });
+```
+
+## Key Exports
+
+| Export | Purpose |
+|--------|---------|
+| `defineRegistry` | Create a type-safe component registry from a catalog |
+| `createRenderer` | Create a renderer function from a registry |
+| `Renderer` | Render a spec using a registry |
+| `CatalogRenderer` | Render with automatic provider wiring |
+| `JsonUIProvider` | All-in-one provider |
+| `schema` | Element tree schema (includes built-in state actions) |
+| `createUIStream` | Stream specs from an API endpoint |
+| `createChatUI` | Chat-style streaming interface |
+| `flatToTree` | Convert flat spec to tree format |
+| `ConfirmDialog` | Confirmation dialog component |
+| `ConfirmDialogManager` | Manage multiple confirmation dialogs |
+
+### Types
+
+| Export | Purpose |
+|--------|---------|
+| `ComponentContext` | Typed component render function context (catalog-aware) |
+| `BaseComponentProps` | Catalog-agnostic base type for reusable component libraries |
+| `EventHandle` | Event handle with `emit()`, `shouldPreventDefault`, `bound` |
+| `ComponentFn` | Component render function type |
+| `SetState` | State setter type |
+| `StateModel` | State model type |
+| `SvelteSchema` | Schema type for the Svelte renderer |
+| `SvelteSpec` | Spec type for the Svelte renderer |
+
+## Documentation
+
+Full API reference: [json-render.dev/docs/api/svelte](https://json-render.dev/docs/api/svelte).
+
+## License
+
+Apache-2.0

+ 112 - 0
skills/json-render-codegen/SKILL.md

@@ -0,0 +1,112 @@
+---
+name: json-render-codegen
+description: Code generation utilities for json-render. Use when generating code from UI specs, building custom code exporters, traversing specs, or serializing props for @json-render/codegen.
+---
+
+# @json-render/codegen
+
+Framework-agnostic utilities for generating code from json-render UI trees. Use these to build custom code exporters for Next.js, Remix, or other frameworks.
+
+## Installation
+
+```bash
+npm install @json-render/codegen
+```
+
+## Tree Traversal
+
+```typescript
+import {
+  traverseSpec,
+  collectUsedComponents,
+  collectStatePaths,
+  collectActions,
+} from "@json-render/codegen";
+
+// Walk the spec depth-first
+traverseSpec(spec, (element, key, depth, parent) => {
+  console.log(`${" ".repeat(depth * 2)}${key}: ${element.type}`);
+});
+
+// Get all component types used
+const components = collectUsedComponents(spec);
+// Set { "Card", "Metric", "Button" }
+
+// Get all state paths referenced
+const statePaths = collectStatePaths(spec);
+// Set { "analytics/revenue", "user/name" }
+
+// Get all action names
+const actions = collectActions(spec);
+// Set { "submit_form", "refresh_data" }
+```
+
+## Serialization
+
+```typescript
+import {
+  serializePropValue,
+  serializeProps,
+  escapeString,
+  type SerializeOptions,
+} from "@json-render/codegen";
+
+// Serialize a single value
+serializePropValue("hello");
+// { value: '"hello"', needsBraces: false }
+
+serializePropValue({ $state: "/user/name" });
+// { value: '{ $state: "/user/name" }', needsBraces: true }
+
+// Serialize props for JSX
+serializeProps({ title: "Dashboard", columns: 3, disabled: true });
+// 'title="Dashboard" columns={3} disabled'
+
+// Escape strings for code
+escapeString('hello "world"');
+// 'hello \"world\"'
+```
+
+### SerializeOptions
+
+```typescript
+interface SerializeOptions {
+  quotes?: "single" | "double";
+  indent?: number;
+}
+```
+
+## Types
+
+```typescript
+import type { GeneratedFile, CodeGenerator } from "@json-render/codegen";
+
+const myGenerator: CodeGenerator = {
+  generate(spec) {
+    return [
+      { path: "package.json", content: "..." },
+      { path: "app/page.tsx", content: "..." },
+    ];
+  },
+};
+```
+
+## Building a Custom Generator
+
+```typescript
+import {
+  collectUsedComponents,
+  collectStatePaths,
+  traverseSpec,
+  serializeProps,
+  type GeneratedFile,
+} from "@json-render/codegen";
+import type { Spec } from "@json-render/core";
+
+export function generateNextJSProject(spec: Spec): GeneratedFile[] {
+  const files: GeneratedFile[] = [];
+  const components = collectUsedComponents(spec);
+  // Generate package.json, component files, main page...
+  return files;
+}
+```

+ 66 - 0
skills/json-render-jotai/SKILL.md

@@ -0,0 +1,66 @@
+---
+name: json-render-jotai
+description: Jotai adapter for json-render's StateStore interface. Use when integrating json-render with Jotai for state management via @json-render/jotai.
+---
+
+# @json-render/jotai
+
+Jotai adapter for json-render's `StateStore` interface. Wire a Jotai atom as the state backend for json-render.
+
+## Installation
+
+```bash
+npm install @json-render/jotai @json-render/core @json-render/react jotai
+```
+
+## Usage
+
+```tsx
+import { atom } from "jotai";
+import { jotaiStateStore } from "@json-render/jotai";
+import { StateProvider } from "@json-render/react";
+
+// 1. Create an atom that holds the json-render state
+const uiAtom = atom<Record<string, unknown>>({ count: 0 });
+
+// 2. Create the json-render StateStore adapter
+const store = jotaiStateStore({ atom: uiAtom });
+
+// 3. Use it
+<StateProvider store={store}>
+  {/* json-render reads/writes go through Jotai */}
+</StateProvider>
+```
+
+### With a Shared Jotai Store
+
+When your app already uses a Jotai `<Provider>` with a custom store, pass it so both json-render and your components share the same state:
+
+```tsx
+import { atom, createStore } from "jotai";
+import { Provider as JotaiProvider } from "jotai/react";
+import { jotaiStateStore } from "@json-render/jotai";
+import { StateProvider } from "@json-render/react";
+
+const jStore = createStore();
+const uiAtom = atom<Record<string, unknown>>({ count: 0 });
+
+const store = jotaiStateStore({ atom: uiAtom, store: jStore });
+
+<JotaiProvider store={jStore}>
+  <StateProvider store={store}>
+    {/* Both json-render and useAtom() see the same state */}
+  </StateProvider>
+</JotaiProvider>
+```
+
+## API
+
+### `jotaiStateStore(options)`
+
+Creates a `StateStore` backed by a Jotai atom.
+
+| Option | Type | Required | Description |
+|--------|------|----------|-------------|
+| `atom` | `WritableAtom<StateModel, [StateModel], void>` | Yes | A writable atom holding the state model |
+| `store` | Jotai `Store` | No | The Jotai store instance. Defaults to a new store. Pass your own to share state with `<Provider>`. |

+ 142 - 0
skills/json-render-react-pdf/SKILL.md

@@ -0,0 +1,142 @@
+---
+name: json-render-react-pdf
+description: React PDF renderer for json-render. Use when generating PDF documents from JSON specs, working with @json-render/react-pdf, or rendering specs to PDF buffers/streams/files.
+---
+
+# @json-render/react-pdf
+
+React PDF renderer that generates PDF documents from JSON specs using `@react-pdf/renderer`.
+
+## Installation
+
+```bash
+npm install @json-render/core @json-render/react-pdf
+```
+
+## Quick Start
+
+```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);
+```
+
+## Render APIs
+
+```typescript
+import { renderToBuffer, renderToStream, renderToFile } from "@json-render/react-pdf";
+
+// In-memory buffer
+const buffer = await renderToBuffer(spec);
+
+// Readable stream (pipe to HTTP response)
+const stream = await renderToStream(spec);
+stream.pipe(res);
+
+// Direct to file
+await renderToFile(spec, "./output.pdf");
+```
+
+All render functions accept an optional second argument: `{ registry?, state?, handlers? }`.
+
+## Standard Components
+
+| Component | Description |
+|-----------|-------------|
+| `Document` | Top-level PDF wrapper (must be root) |
+| `Page` | Page with size (A4, LETTER), orientation, margins |
+| `View` | Generic container (padding, margin, background, border) |
+| `Row`, `Column` | Flex layout with gap, align, justify |
+| `Heading` | h1-h4 heading text |
+| `Text` | Body text (fontSize, color, weight, alignment) |
+| `Image` | Image from URL or base64 |
+| `Link` | Hyperlink with text and href |
+| `Table` | Data table with typed columns and rows |
+| `List` | Ordered or unordered list |
+| `Divider` | Horizontal line separator |
+| `Spacer` | Empty vertical space |
+| `PageNumber` | Current page number and total pages |
+
+## Custom Catalog
+
+```typescript
+import { defineCatalog } from "@json-render/core";
+import { schema, defineRegistry, renderToBuffer } from "@json-render/react-pdf";
+import { standardComponentDefinitions } from "@json-render/react-pdf/catalog";
+import { z } from "zod";
+
+const catalog = defineCatalog(schema, {
+  components: {
+    ...standardComponentDefinitions,
+    Badge: {
+      props: z.object({ label: z.string(), color: z.string().nullable() }),
+      slots: [],
+      description: "A colored badge label",
+    },
+  },
+  actions: {},
+});
+
+const { registry } = defineRegistry(catalog, {
+  components: {
+    Badge: ({ props }) => (
+      <View style={{ backgroundColor: props.color ?? "#e5e7eb", padding: 4 }}>
+        <Text>{props.label}</Text>
+      </View>
+    ),
+  },
+});
+
+const buffer = await renderToBuffer(spec, { registry });
+```
+
+## External Store (Controlled Mode)
+
+Pass a `StateStore` for full control over state:
+
+```typescript
+import { createStateStore } from "@json-render/react-pdf";
+
+const store = createStateStore({ invoice: { total: 100 } });
+store.set("/invoice/total", 200);
+```
+
+## Server-Safe Import
+
+Import schema and catalog without pulling in React:
+
+```typescript
+import { schema, standardComponentDefinitions } from "@json-render/react-pdf/server";
+```

+ 64 - 0
skills/json-render-redux/SKILL.md

@@ -0,0 +1,64 @@
+---
+name: json-render-redux
+description: Redux adapter for json-render's StateStore interface. Use when integrating json-render with Redux or Redux Toolkit for state management via @json-render/redux.
+---
+
+# @json-render/redux
+
+Redux adapter for json-render's `StateStore` interface. Wire a Redux store (or Redux Toolkit slice) as the state backend for json-render.
+
+## Installation
+
+```bash
+npm install @json-render/redux @json-render/core @json-render/react redux
+# or with Redux Toolkit (recommended):
+npm install @json-render/redux @json-render/core @json-render/react @reduxjs/toolkit
+```
+
+## Usage
+
+```tsx
+import { configureStore, createSlice } from "@reduxjs/toolkit";
+import { reduxStateStore } from "@json-render/redux";
+import { StateProvider } from "@json-render/react";
+
+// 1. Define a slice for json-render state
+const uiSlice = createSlice({
+  name: "ui",
+  initialState: { count: 0 } as Record<string, unknown>,
+  reducers: {
+    replaceUiState: (_state, action) => action.payload,
+  },
+});
+
+// 2. Create the Redux store
+const reduxStore = configureStore({
+  reducer: { ui: uiSlice.reducer },
+});
+
+// 3. Create the json-render StateStore adapter
+const store = reduxStateStore({
+  store: reduxStore,
+  selector: (state) => state.ui,
+  dispatch: (next, s) => s.dispatch(uiSlice.actions.replaceUiState(next)),
+});
+
+// 4. Use it
+<StateProvider store={store}>
+  {/* json-render reads/writes go through Redux */}
+</StateProvider>
+```
+
+## API
+
+### `reduxStateStore(options)`
+
+Creates a `StateStore` backed by a Redux store.
+
+| Option | Type | Required | Description |
+|--------|------|----------|-------------|
+| `store` | `Store` | Yes | The Redux store instance |
+| `selector` | `(state) => StateModel` | Yes | Select the json-render slice from the Redux state tree. Use `(s) => s` if the entire state is the model. |
+| `dispatch` | `(nextState, store) => void` | Yes | Dispatch an action that replaces the selected slice with the next state |
+
+The `dispatch` callback receives the full next state model and the Redux store.

+ 177 - 0
skills/json-render-vue/SKILL.md

@@ -0,0 +1,177 @@
+---
+name: json-render-vue
+description: Vue 3 renderer for json-render. Use when building Vue UIs from JSON specs, working with @json-render/vue, defining Vue component registries, or rendering AI-generated specs in Vue.
+---
+
+# @json-render/vue
+
+Vue 3 renderer that converts JSON specs into Vue component trees with data binding, visibility, and actions.
+
+## Installation
+
+```bash
+npm install @json-render/vue @json-render/core zod
+```
+
+Peer dependencies: `vue ^3.5.0` and `zod ^4.0.0`.
+
+## Quick Start
+
+### Create a Catalog
+
+```typescript
+import { defineCatalog } from "@json-render/core";
+import { schema } from "@json-render/vue/schema";
+import { z } from "zod";
+
+export const catalog = defineCatalog(schema, {
+  components: {
+    Card: {
+      props: z.object({ title: z.string(), description: z.string().nullable() }),
+      description: "A card container",
+    },
+    Button: {
+      props: z.object({ label: z.string(), action: z.string() }),
+      description: "A clickable button",
+    },
+  },
+  actions: {},
+});
+```
+
+### Define Registry with h() Render Functions
+
+```typescript
+import { h } from "vue";
+import { defineRegistry } from "@json-render/vue";
+import { catalog } from "./catalog";
+
+export const { registry } = defineRegistry(catalog, {
+  components: {
+    Card: ({ props, children }) =>
+      h("div", { class: "card" }, [
+        h("h3", null, props.title),
+        props.description ? h("p", null, props.description) : null,
+        children,
+      ]),
+    Button: ({ props, emit }) =>
+      h("button", { onClick: () => emit("press") }, props.label),
+  },
+});
+```
+
+### Render Specs
+
+```vue
+<script setup lang="ts">
+import { StateProvider, ActionProvider, Renderer } from "@json-render/vue";
+import { registry } from "./registry";
+
+const spec = { root: "card-1", elements: { /* ... */ } };
+</script>
+
+<template>
+  <StateProvider :initial-state="{ form: { name: '' } }">
+    <ActionProvider :handlers="{ submit: handleSubmit }">
+      <Renderer :spec="spec" :registry="registry" />
+    </ActionProvider>
+  </StateProvider>
+</template>
+```
+
+## Providers
+
+| Provider | Purpose |
+|----------|---------|
+| `StateProvider` | Share state across components (JSON Pointer paths). Accepts `initialState` or `store` for controlled mode. |
+| `ActionProvider` | Handle actions dispatched via the event system |
+| `VisibilityProvider` | Enable conditional rendering based on state |
+| `ValidationProvider` | Form field validation |
+
+## Composables
+
+| Composable | Purpose |
+|------------|---------|
+| `useStateStore()` | Access state context (`state` as `ShallowRef`, `get`, `set`, `update`) |
+| `useStateValue(path)` | Get single value from state |
+| `useIsVisible(condition)` | Check if a visibility condition is met |
+| `useActions()` | Access action context |
+| `useAction(binding)` | Get a single action dispatch function |
+| `useFieldValidation(path, config)` | Field validation state |
+| `useBoundProp(propValue, bindingPath)` | Two-way binding for `$bindState`/`$bindItem` |
+
+Note: `useStateStore().state` returns a `ShallowRef<StateModel>` — use `state.value` to access.
+
+## External Store (StateStore)
+
+Pass a `StateStore` to `StateProvider` to wire json-render to Pinia, VueUse, or any state management:
+
+```typescript
+import { createStateStore, type StateStore } from "@json-render/vue";
+
+const store = createStateStore({ count: 0 });
+```
+
+```vue
+<StateProvider :store="store">
+  <Renderer :spec="spec" :registry="registry" />
+</StateProvider>
+```
+
+## Dynamic Prop Expressions
+
+Props support `$state`, `$bindState`, `$cond`, `$template`, `$computed`. Use `{ "$bindState": "/path" }` on the natural value prop for two-way binding.
+
+## Visibility Conditions
+
+```typescript
+{ "$state": "/user/isAdmin" }
+{ "$state": "/status", "eq": "active" }
+{ "$state": "/maintenance", "not": true }
+[ cond1, cond2 ]  // implicit AND
+```
+
+## Built-in Actions
+
+`setState`, `pushState`, `removeState`, and `validateForm` are built into the Vue schema and handled by `ActionProvider`:
+
+```json
+{
+  "action": "setState",
+  "params": { "statePath": "/activeTab", "value": "settings" }
+}
+```
+
+## Event System
+
+Components use `emit(event)` to fire events, or `on(event)` for metadata (`shouldPreventDefault`, `bound`).
+
+## Streaming
+
+`useUIStream` and `useChatUI` return Vue Refs for streaming specs from an API.
+
+## BaseComponentProps
+
+For catalog-agnostic reusable components:
+
+```typescript
+import type { BaseComponentProps } from "@json-render/vue";
+
+const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) =>
+  h("div", null, [props.title, children]);
+```
+
+## Key Exports
+
+| Export | Purpose |
+|--------|---------|
+| `defineRegistry` | Create a type-safe component registry from a catalog |
+| `Renderer` | Render a spec using a registry |
+| `schema` | Element tree schema (from `@json-render/vue/schema`) |
+| `StateProvider`, `ActionProvider`, `VisibilityProvider`, `ValidationProvider` | Context providers |
+| `useStateStore`, `useStateValue`, `useBoundProp` | State composables |
+| `useActions`, `useAction` | Action composables |
+| `useFieldValidation`, `useIsVisible` | Validation and visibility |
+| `useUIStream`, `useChatUI` | Streaming composables |
+| `createStateStore` | Create in-memory `StateStore` |
+| `BaseComponentProps` | Catalog-agnostic component props type |

+ 45 - 0
skills/json-render-xstate/SKILL.md

@@ -0,0 +1,45 @@
+---
+name: json-render-xstate
+description: XState Store adapter for json-render's StateStore interface. Use when integrating json-render with @xstate/store for state management via @json-render/xstate.
+---
+
+# @json-render/xstate
+
+[XState Store](https://stately.ai/docs/xstate-store) adapter for json-render's `StateStore` interface. Wire an `@xstate/store` atom as the state backend for json-render.
+
+## Installation
+
+```bash
+npm install @json-render/xstate @json-render/core @json-render/react @xstate/store
+```
+
+Requires `@xstate/store` v3+.
+
+## Usage
+
+```tsx
+import { createAtom } from "@xstate/store";
+import { xstateStoreStateStore } from "@json-render/xstate";
+import { StateProvider } from "@json-render/react";
+
+// 1. Create an atom
+const uiAtom = createAtom({ count: 0 });
+
+// 2. Create the json-render StateStore adapter
+const store = xstateStoreStateStore({ atom: uiAtom });
+
+// 3. Use it
+<StateProvider store={store}>
+  {/* json-render reads/writes go through @xstate/store */}
+</StateProvider>
+```
+
+## API
+
+### `xstateStoreStateStore(options)`
+
+Creates a `StateStore` backed by an `@xstate/store` atom.
+
+| Option | Type | Required | Description |
+|--------|------|----------|-------------|
+| `atom` | `Atom<StateModel>` | Yes | An `@xstate/store` atom (from `createAtom`) holding the json-render state model |

+ 65 - 0
skills/json-render-zustand/SKILL.md

@@ -0,0 +1,65 @@
+---
+name: json-render-zustand
+description: Zustand adapter for json-render's StateStore interface. Use when integrating json-render with Zustand for state management via @json-render/zustand.
+---
+
+# @json-render/zustand
+
+Zustand adapter for json-render's `StateStore` interface. Wire a Zustand vanilla store as the state backend for json-render.
+
+## Installation
+
+```bash
+npm install @json-render/zustand @json-render/core @json-render/react zustand
+```
+
+Requires Zustand v5+. Zustand v4 is not supported due to breaking API changes in the vanilla store interface.
+
+## Usage
+
+```tsx
+import { createStore } from "zustand/vanilla";
+import { zustandStateStore } from "@json-render/zustand";
+import { StateProvider } from "@json-render/react";
+
+// 1. Create a Zustand vanilla store
+const bearStore = createStore(() => ({
+  count: 0,
+  name: "Bear",
+}));
+
+// 2. Create the json-render StateStore adapter
+const store = zustandStateStore({ store: bearStore });
+
+// 3. Use it
+<StateProvider store={store}>
+  {/* json-render reads/writes go through Zustand */}
+</StateProvider>
+```
+
+### With a Nested Slice
+
+```tsx
+const appStore = createStore(() => ({
+  ui: { count: 0 },
+  auth: { token: null },
+}));
+
+const store = zustandStateStore({
+  store: appStore,
+  selector: (s) => s.ui,
+  updater: (next, s) => s.setState({ ui: next }),
+});
+```
+
+## API
+
+### `zustandStateStore(options)`
+
+Creates a `StateStore` backed by a Zustand store.
+
+| Option | Type | Required | Description |
+|--------|------|----------|-------------|
+| `store` | `StoreApi<S>` | Yes | Zustand vanilla store (from `createStore` in `zustand/vanilla`) |
+| `selector` | `(state) => StateModel` | No | Select the json-render slice. Defaults to entire state. |
+| `updater` | `(nextState, store) => void` | No | Apply next state to the store. Defaults to shallow merge. Override for nested slices, or use `(next, s) => s.setState(next, true)` for full replacement. |