Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,31 @@

Instructions for AI coding agents working with this codebase.

## Package Management

**Always check the latest version before installing a package.**

Before adding or updating any dependency, verify the current latest version on npm:

```bash
npm view <package-name> version
```

Or check multiple packages at once:

```bash
npm view ai version
npm view @ai-sdk/provider-utils version
npm view zod version
```

This ensures we don't install outdated versions that may have incompatible types or missing features.

## Code Style

- Do not use emojis in code or UI
- Do not use barrel files (index.ts that re-exports from other files)
- Use shadcn CLI to add shadcn/ui components: `pnpm dlx shadcn@latest add <component>`

## Workflow

Expand Down
251 changes: 136 additions & 115 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

**Predictable. Guardrailed. Fast.**

Let end users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.
Let end users generate dashboards, widgets, apps, and videos from prompts — safely constrained to components you define.

```bash
npm install @json-render/core @json-render/react
# or for video
npm install @json-render/core @json-render/remotion
```

## Why json-render?
Expand All @@ -18,82 +20,80 @@ When users prompt for UI, you need guarantees. json-render gives AI a **constrai

## Quick Start

### 1. Define Your Catalog (what AI can use)
### 1. Define Your Catalog

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

const catalog = createCatalog({
const catalog = defineCatalog(schema, {
components: {
Card: {
props: z.object({ title: z.string() }),
hasChildren: true,
description: "A card container",
},
Metric: {
props: z.object({
label: z.string(),
valuePath: z.string(), // Binds to your data
format: z.enum(['currency', 'percent', 'number']),
value: z.string(),
format: z.enum(["currency", "percent", "number"]).nullable(),
}),
description: "Display a metric value",
},
Button: {
props: z.object({
label: z.string(),
action: ActionSchema, // AI declares intent, you handle it
action: z.string(),
}),
description: "Clickable button",
},
},
actions: {
export_report: { description: 'Export dashboard to PDF' },
refresh_data: { description: 'Refresh all metrics' },
export_report: { description: "Export dashboard to PDF" },
refresh_data: { description: "Refresh all metrics" },
},
});
```

### 2. Register Your Components (how they render)
### 2. Register Component Implementations

```tsx
const registry = {
Card: ({ element, children }) => (
import { defineComponents } from "@json-render/react";

const components = defineComponents(catalog, {
Card: ({ props, children }) => (
<div className="card">
<h3>{element.props.title}</h3>
<h3>{props.title}</h3>
{children}
</div>
),
Metric: ({ element }) => {
const value = useDataValue(element.props.valuePath);
return <div className="metric">{format(value)}</div>;
},
Button: ({ element, onAction }) => (
<button onClick={() => onAction(element.props.action)}>
{element.props.label}
Metric: ({ props }) => (
<div className="metric">
<span>{props.label}</span>
<span>{format(props.value, props.format)}</span>
</div>
),
Button: ({ props, onAction }) => (
<button onClick={() => onAction?.(props.action)}>
{props.label}
</button>
),
};
});
```

### 3. Let AI Generate
### 3. Render AI-Generated Specs

```tsx
import { DataProvider, ActionProvider, Renderer, useUIStream } from '@json-render/react';

function Dashboard() {
const { tree, send } = useUIStream({ api: '/api/generate' });
import { Renderer } from "@json-render/react";

function Dashboard({ spec }) {
return (
<DataProvider initialData={{ revenue: 125000, growth: 0.15 }}>
<ActionProvider actions={{
export_report: () => downloadPDF(),
refresh_data: () => refetch(),
}}>
<input
placeholder="Create a revenue dashboard..."
onKeyDown={(e) => e.key === 'Enter' && send(e.target.value)}
/>
<Renderer tree={tree} components={registry} />
</ActionProvider>
</DataProvider>
<Renderer
spec={spec}
catalog={catalog}
components={components}
/>
);
}
```
Expand All @@ -102,85 +102,119 @@ function Dashboard() {

---

## Features
## Packages

### Conditional Visibility
| Package | Description |
|---------|-------------|
| `@json-render/core` | Schemas, catalogs, AI prompts, SpecStream utilities |
| `@json-render/react` | React renderer, contexts, hooks |
| `@json-render/remotion` | Remotion video renderer, timeline schema |

Show/hide components based on data, auth, or complex logic:
## Renderers

```json
{
"type": "Alert",
"props": { "message": "Error occurred" },
"visible": {
"and": [
{ "path": "/form/hasError" },
{ "not": { "path": "/form/errorDismissed" } }
### React (UI)

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

// Element tree spec format
const spec = {
root: {
type: "Card",
props: { title: "Hello" },
children: [
{ type: "Button", props: { label: "Click me" } }
]
}
}
};

<Renderer spec={spec} catalog={catalog} components={components} />
```

```json
{
"type": "AdminPanel",
"visible": { "auth": "signedIn" }
}
### 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: [] }
};

<Player
component={Renderer}
inputProps={{ spec }}
durationInFrames={spec.composition.durationInFrames}
fps={spec.composition.fps}
compositionWidth={spec.composition.width}
compositionHeight={spec.composition.height}
/>
```

### Rich Actions
## Features

Actions with confirmation dialogs and callbacks:
### 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": "Button",
"props": {
"label": "Refund Payment",
"action": {
"name": "refund",
"params": {
"paymentId": { "path": "/selected/id" },
"amount": { "path": "/refund/amount" }
},
"confirm": {
"title": "Confirm Refund",
"message": "Refund ${/refund/amount} to customer?",
"variant": "danger"
},
"onSuccess": { "set": { "/ui/success": true } },
"onError": { "set": { "/ui/error": "$error.message" } }
}
"type": "Alert",
"props": { "message": "Error occurred" },
"visible": {
"and": [
{ "path": "/form/hasError" },
{ "not": { "path": "/form/errorDismissed" } }
]
}
}
```

### Built-in Validation
### Data Binding

```json
{
"type": "TextField",
"type": "Metric",
"props": {
"label": "Email",
"valuePath": "/form/email",
"checks": [
{ "fn": "required", "message": "Email is required" },
{ "fn": "email", "message": "Invalid email" }
],
"validateOn": "blur"
"label": "Revenue",
"value": "{{data.revenue}}"
}
}
```

---

## Packages

| Package | Description |
|---------|-------------|
| `@json-render/core` | Types, schemas, visibility, actions, validation |
| `@json-render/react` | React renderer, providers, hooks |

## Demo

```bash
Expand All @@ -192,32 +226,19 @@ pnpm dev

- http://localhost:3000 — Docs & Playground
- http://localhost:3001 — Example Dashboard

## Project Structure

```
json-render/
├── packages/
│ ├── core/ → @json-render/core
│ └── react/ → @json-render/react
├── apps/
│ └── web/ → Docs & Playground site
└── examples/
└── dashboard/ → Example dashboard app
```
- http://localhost:3002 — Remotion Video Example

## How It Works

```
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ User Prompt │────▶│ AI + Catalog│────▶│ JSON Tree │
│ "dashboard" │ │ (guardrailed)│ │(predictable)│
└─────────────┘ └──────────────┘ └─────────────┘
┌──────────────┐ │
│ Your React │◀───────────┘
│ Components │ (streamed)
└──────────────┘
```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
Expand Down
Loading