mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-03 04:18:15 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edbeb5a637 | ||
|
|
d9a4efdbeb | ||
|
|
f435643817 | ||
|
|
458f3a728c |
@@ -1,8 +1,8 @@
|
|||||||
# json-render
|
# json-render
|
||||||
|
|
||||||
**Predictable. Guardrailed. Fast.**
|
**The framework for User-Generated Interfaces (UGI).**
|
||||||
|
|
||||||
Let end users generate dashboards, widgets, apps, and videos from prompts — safely constrained to components you define.
|
Dynamic, personalized UIs per user without sacrificing reliability. Predefined components and actions for safe, predictable output.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @json-render/core @json-render/react
|
npm install @json-render/core @json-render/react
|
||||||
@@ -14,11 +14,12 @@ npm install @json-render/core @json-render/remotion
|
|||||||
|
|
||||||
## Why json-render?
|
## Why json-render?
|
||||||
|
|
||||||
When users prompt for UI, you need guarantees. json-render gives AI a **constrained vocabulary** so output is always predictable:
|
json-render enables **User-Generated Interfaces**: dynamic UIs that end users create through natural language prompts, powered by Generative UI. You define the guardrails, AI generates within them:
|
||||||
|
|
||||||
- **Guardrailed** — AI can only use components in your catalog
|
- **Guardrailed** - AI can only use components in your catalog
|
||||||
- **Predictable** — JSON output matches your schema, every time
|
- **Predictable** - JSON output matches your schema, every time
|
||||||
- **Fast** — Stream and render progressively as the model responds
|
- **Fast** - Stream and render progressively as the model responds
|
||||||
|
- **Cross-Platform** - React (web) and React Native (mobile) from the same catalog
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -237,8 +238,8 @@ Any prop value can be data-driven using expressions:
|
|||||||
|
|
||||||
Two expression forms:
|
Two expression forms:
|
||||||
|
|
||||||
- **`{ "$path": "/state/key" }`** -- reads a value from the data model
|
- **`{ "$path": "/state/key" }`** - reads a value from the data model
|
||||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- evaluates a condition (same syntax as visibility conditions) and picks a branch
|
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - evaluates a condition (same syntax as visibility conditions) and picks a branch
|
||||||
|
|
||||||
### Actions
|
### Actions
|
||||||
|
|
||||||
@@ -265,9 +266,9 @@ pnpm install
|
|||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
- http://localhost:3000 -- Docs & Playground
|
- http://localhost:3000 - Docs & Playground
|
||||||
- http://localhost:3001 -- Example Dashboard
|
- http://localhost:3001 - Example Dashboard
|
||||||
- http://localhost:3002 -- Remotion Video Example
|
- http://localhost:3002 - Remotion Video Example
|
||||||
- React Native example: run `npx expo start` in `examples/react-native`
|
- React Native example: run `npx expo start` in `examples/react-native`
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
@@ -283,10 +284,10 @@ flowchart LR
|
|||||||
D -.- G([streamed])
|
D -.- G([streamed])
|
||||||
```
|
```
|
||||||
|
|
||||||
1. **Define the guardrails** — what components, actions, and data bindings AI can use
|
1. **Define the guardrails** - what components, actions, and data bindings AI can use
|
||||||
2. **Users prompt** — end users describe what they want in natural language
|
2. **Users generate** - end users describe what they want in natural language
|
||||||
3. **AI generates JSON** — output is always predictable, constrained to your catalog
|
3. **AI generates JSON** - output is always predictable, constrained to your catalog
|
||||||
4. **Render fast** — stream and render progressively as the model responds
|
4. **Render fast** - stream and render progressively as the model responds
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ React Native renderer with standard components, providers, and hooks.
|
|||||||
|
|
||||||
## defineRegistry
|
## defineRegistry
|
||||||
|
|
||||||
Create a type-safe component registry. Standard components are built-in -- only register custom components.
|
Create a type-safe component registry. Standard components are built-in; only register custom components.
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { defineRegistry, type Components } from '@json-render/react-native';
|
import { defineRegistry, type Components } from '@json-render/react-native';
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ const fixed = autoFixSpec(spec);
|
|||||||
|
|
||||||
### Improved: State Management
|
### 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 `$path` expressions, and the built-in `setState` action updates state directly.
|
`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 `$path` expressions, and the built-in `setState` action updates state directly.
|
||||||
|
|
||||||
### Improved: AI Prompts
|
### Improved: AI Prompts
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ export const metadata = { title: "Introduction" }
|
|||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
Predictable. Guardrailed. Fast. Let users generate dashboards, widgets, apps, and data visualizations from prompts.
|
The framework for User-Generated Interfaces (UGI). Dynamic, personalized UIs per user without sacrificing reliability.
|
||||||
|
|
||||||
## What is json-render?
|
## What is json-render?
|
||||||
|
|
||||||
json-render lets end users generate UI from natural language prompts — safely constrained to components you define. You set the guardrails: what components exist, what props they take, what actions are available. AI generates JSON that matches your schema, and your components render it natively.
|
json-render is the framework for **User-Generated Interfaces**: dynamic UIs that end users generate through natural language prompts, powered by Generative UI. You define the guardrails: what components exist, what props they take, what actions are available. AI generates JSON that matches your schema, and your components render it natively on web or mobile.
|
||||||
|
|
||||||
|
Every interface is unique to the user, but every interface is safe and predictable.
|
||||||
|
|
||||||
## Why json-render?
|
## Why json-render?
|
||||||
|
|
||||||
### Guardrailed
|
### Guardrailed
|
||||||
|
|
||||||
AI can only use components in your catalog. No arbitrary code generation.
|
AI can only use components in your catalog. No arbitrary code generation. Predefined components and actions for safe, predictable output.
|
||||||
|
|
||||||
### Predictable
|
### Predictable
|
||||||
|
|
||||||
@@ -22,9 +24,13 @@ JSON output matches your schema, every time. Actions are declared by name, you c
|
|||||||
|
|
||||||
Stream and render progressively as the model responds. No waiting for completion.
|
Stream and render progressively as the model responds. No waiting for completion.
|
||||||
|
|
||||||
|
### Cross-Platform
|
||||||
|
|
||||||
|
Render on web with React and on mobile with React Native from the same catalog and spec format.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
1. Define the guardrails — what components, actions, and data bindings AI can use
|
1. Define the guardrails - what components, actions, and data bindings AI can use
|
||||||
2. Users prompt — end users describe what they want in natural language
|
2. Users generate - end users describe what they want in natural language
|
||||||
3. AI generates JSON — output is always predictable, constrained to your catalog
|
3. AI generates JSON - output is always predictable, constrained to your catalog
|
||||||
4. Render fast — stream and render progressively as the model responds
|
4. Render fast - stream and render progressively as the model responds
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ export default function Home() {
|
|||||||
<>
|
<>
|
||||||
{/* Hero */}
|
{/* Hero */}
|
||||||
<section className="max-w-5xl mx-auto px-6 pt-24 pb-16 text-center">
|
<section className="max-w-5xl mx-auto px-6 pt-24 pb-16 text-center">
|
||||||
<h1 className="text-5xl sm:text-6xl md:text-7xl font-bold tracking-tighter mb-6">
|
<p className="text-xs sm:text-sm font-medium text-muted-foreground tracking-widest uppercase mb-4">
|
||||||
|
The framework for User-Generated Interfaces
|
||||||
|
</p>
|
||||||
|
<h1 className="text-4xl sm:text-6xl md:text-7xl font-bold tracking-tighter mb-6">
|
||||||
AI → json-render → UI
|
AI → json-render → UI
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg text-muted-foreground max-w-2xl mx-auto mb-12 leading-relaxed">
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto mb-12 leading-relaxed">
|
||||||
Define a component catalog. Users prompt. AI outputs JSON constrained
|
Dynamic, personalized UIs per user without sacrificing reliability.
|
||||||
to your catalog. Your components render it.
|
Predefined components and actions for safe, predictable output.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Demo />
|
<Demo />
|
||||||
@@ -65,10 +68,10 @@ export default function Home() {
|
|||||||
<div className="text-xs text-muted-foreground font-mono mb-3">
|
<div className="text-xs text-muted-foreground font-mono mb-3">
|
||||||
02
|
02
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold mb-2">Users Prompt</h3>
|
<h3 className="text-lg font-semibold mb-2">Users Generate</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
End users describe what they want. AI generates JSON constrained
|
End users describe what they want. AI generates JSON constrained
|
||||||
to your catalog.
|
to your catalog. Every interface is unique to the user.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -244,6 +247,10 @@ export default function Page() {
|
|||||||
<h2 className="text-2xl font-semibold mb-12 text-center">Features</h2>
|
<h2 className="text-2xl font-semibold mb-12 text-center">Features</h2>
|
||||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{[
|
{[
|
||||||
|
{
|
||||||
|
title: "User-Generated Interfaces",
|
||||||
|
desc: "Dynamic, personalized UIs per user powered by Generative UI",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Guardrails",
|
title: "Guardrails",
|
||||||
desc: "AI can only use components you define in the catalog",
|
desc: "AI can only use components you define in the catalog",
|
||||||
@@ -253,20 +260,16 @@ export default function Page() {
|
|||||||
desc: "Progressive rendering as JSON streams from the model",
|
desc: "Progressive rendering as JSON streams from the model",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Code Export",
|
title: "React & React Native",
|
||||||
desc: "Export as standalone React code with no runtime dependencies",
|
desc: "Render on web and mobile from the same catalog and spec format",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Data Binding",
|
title: "Data Binding",
|
||||||
desc: "Two-way binding with JSON Pointer paths",
|
desc: "Two-way state binding with dynamic prop expressions",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Actions",
|
title: "Code Export",
|
||||||
desc: "Named actions handled by your application",
|
desc: "Export as standalone React code with no runtime dependencies",
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Visibility",
|
|
||||||
desc: "Conditional show/hide based on data or auth",
|
|
||||||
},
|
},
|
||||||
].map((feature) => (
|
].map((feature) => (
|
||||||
<div key={feature.title}>
|
<div key={feature.title}>
|
||||||
|
|||||||
+10
-6
@@ -22,11 +22,15 @@ export const metadata: Metadata = {
|
|||||||
template: "%s | json-render",
|
template: "%s | json-render",
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
"Let users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.",
|
"The framework for User-Generated Interfaces (UGI). Let users generate dashboards, widgets, and apps from prompts — safely constrained to components you define.",
|
||||||
keywords: [
|
keywords: [
|
||||||
"json-render",
|
"json-render",
|
||||||
|
"UGI",
|
||||||
|
"User-Generated Interfaces",
|
||||||
"AI UI generation",
|
"AI UI generation",
|
||||||
|
"generative UI",
|
||||||
"React components",
|
"React components",
|
||||||
|
"React Native",
|
||||||
"guardrails",
|
"guardrails",
|
||||||
"structured output",
|
"structured output",
|
||||||
"dashboard builder",
|
"dashboard builder",
|
||||||
@@ -38,23 +42,23 @@ export const metadata: Metadata = {
|
|||||||
locale: "en_US",
|
locale: "en_US",
|
||||||
url: "https://json-render.dev",
|
url: "https://json-render.dev",
|
||||||
siteName: "json-render",
|
siteName: "json-render",
|
||||||
title: "json-render | AI-generated UI with guardrails",
|
title: "json-render | The framework for User-Generated Interfaces",
|
||||||
description:
|
description:
|
||||||
"Let users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.",
|
"The framework for User-Generated Interfaces (UGI). Let users generate dashboards, widgets, and apps from prompts — safely constrained to components you define.",
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: "/og",
|
url: "/og",
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 630,
|
height: 630,
|
||||||
alt: "json-render - AI-generated UI with guardrails",
|
alt: "json-render - The framework for User-Generated Interfaces",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
title: "json-render | AI-generated UI with guardrails",
|
title: "json-render | The framework for User-Generated Interfaces",
|
||||||
description:
|
description:
|
||||||
"Let users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.",
|
"The framework for User-Generated Interfaces (UGI). Let users generate dashboards, widgets, and apps from prompts — safely constrained to components you define.",
|
||||||
images: ["/og"],
|
images: ["/og"],
|
||||||
creator: "@verabornnot",
|
creator: "@verabornnot",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,23 +66,27 @@ export async function renderOgImage(title: string) {
|
|||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
{title.split("\n").map((line, i) => (
|
||||||
style={{
|
<span
|
||||||
fontSize: 72,
|
key={i}
|
||||||
fontFamily: "Geist",
|
style={{
|
||||||
fontWeight: 400,
|
fontSize: 72,
|
||||||
color: "white",
|
fontFamily: "Geist",
|
||||||
letterSpacing: "-0.02em",
|
fontWeight: 400,
|
||||||
textAlign: "center",
|
color: "white",
|
||||||
lineHeight: 1.2,
|
letterSpacing: "-0.02em",
|
||||||
}}
|
textAlign: "center",
|
||||||
>
|
lineHeight: 1.2,
|
||||||
{title}
|
}}
|
||||||
</span>
|
>
|
||||||
|
{line}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
export const PAGE_TITLES: Record<string, string> = {
|
export const PAGE_TITLES: Record<string, string> = {
|
||||||
// Home (no slug)
|
// Home (no slug)
|
||||||
"": "AI-generated UI with guardrails",
|
"": "The framework for\nUser-Generated Interfaces",
|
||||||
|
|
||||||
// Top-level
|
// Top-level
|
||||||
playground: "Playground",
|
playground: "Playground",
|
||||||
|
|||||||
@@ -823,7 +823,12 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
|||||||
const [localValue, setLocalValue] = useState<string>("");
|
const [localValue, setLocalValue] = useState<string>("");
|
||||||
const value = props.statePath ? (boundValue ?? "") : localValue;
|
const value = props.statePath ? (boundValue ?? "") : localValue;
|
||||||
const setValue = props.statePath ? setBoundValue! : setLocalValue;
|
const setValue = props.statePath ? setBoundValue! : setLocalValue;
|
||||||
const options = props.options ?? [];
|
const rawOptions = props.options ?? [];
|
||||||
|
// Coerce options to strings – AI may produce objects/numbers instead of
|
||||||
|
// plain strings which would cause duplicate `[object Object]` keys.
|
||||||
|
const options = rawOptions.map((opt) =>
|
||||||
|
typeof opt === "string" ? opt : String(opt ?? ""),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -839,8 +844,11 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
|||||||
<SelectValue placeholder={props.placeholder ?? "Select..."} />
|
<SelectValue placeholder={props.placeholder ?? "Select..."} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{options.map((opt) => (
|
{options.map((opt, idx) => (
|
||||||
<SelectItem key={opt} value={opt}>
|
<SelectItem
|
||||||
|
key={`${idx}-${opt}`}
|
||||||
|
value={opt || `option-${idx}`}
|
||||||
|
>
|
||||||
{opt}
|
{opt}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -876,7 +884,10 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Radio: ({ props, emit }) => {
|
Radio: ({ props, emit }) => {
|
||||||
const options = props.options ?? [];
|
const rawOptions = props.options ?? [];
|
||||||
|
const options = rawOptions.map((opt) =>
|
||||||
|
typeof opt === "string" ? opt : String(opt ?? ""),
|
||||||
|
);
|
||||||
const [boundValue, setBoundValue] = props.statePath
|
const [boundValue, setBoundValue] = props.statePath
|
||||||
? useStateBinding<string>(props.statePath) // eslint-disable-line react-hooks/rules-of-hooks
|
? useStateBinding<string>(props.statePath) // eslint-disable-line react-hooks/rules-of-hooks
|
||||||
: [undefined, undefined];
|
: [undefined, undefined];
|
||||||
@@ -894,11 +905,17 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
|||||||
emit?.("change");
|
emit?.("change");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{options.map((opt) => (
|
{options.map((opt, idx) => (
|
||||||
<div key={opt} className="flex items-center space-x-2">
|
<div
|
||||||
<RadioGroupItem value={opt} id={`${props.name}-${opt}`} />
|
key={`${idx}-${opt}`}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<RadioGroupItem
|
||||||
|
value={opt || `option-${idx}`}
|
||||||
|
id={`${props.name}-${idx}-${opt}`}
|
||||||
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={`${props.name}-${opt}`}
|
htmlFor={`${props.name}-${idx}-${opt}`}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
{opt}
|
{opt}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @json-render/codegen
|
# @json-render/codegen
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @json-render/core@0.5.1
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/codegen",
|
"name": "@json-render/codegen",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Utilities for generating code from json-render UI trees",
|
"description": "Utilities for generating code from json-render UI trees",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# @json-render/core
|
# @json-render/core
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/core",
|
"name": "@json-render/core",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/react-native
|
# @json-render/react-native
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- d9a4efd: Prevent rendering errors from crashing the application. Added error boundaries to all renderers so a single bad component silently disappears instead of causing a white-screen-of-death. Fixed Select and Radio components to handle non-string option values from AI output.
|
||||||
|
- @json-render/core@0.5.1
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const { registry } = defineRegistry(catalog, {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Standard components (Container, Row, Column, Button, TextInput, etc.) are included by default -- you only need to register custom ones.
|
Standard components (Container, Row, Column, Button, TextInput, etc.) are included by default. You only need to register custom ones.
|
||||||
|
|
||||||
### 3. Render Specs
|
### 3. Render Specs
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/react-native",
|
"name": "@json-render/react-native",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, {
|
import React, {
|
||||||
type ComponentType,
|
type ComponentType,
|
||||||
|
type ErrorInfo,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -81,6 +82,51 @@ export interface RendererProps {
|
|||||||
fallback?: ComponentRenderer;
|
fallback?: ComponentRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ElementErrorBoundary – catches rendering errors in individual elements so
|
||||||
|
// a single bad component never crashes the whole page.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface ElementErrorBoundaryProps {
|
||||||
|
elementType: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ElementErrorBoundaryState {
|
||||||
|
hasError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElementErrorBoundary extends React.Component<
|
||||||
|
ElementErrorBoundaryProps,
|
||||||
|
ElementErrorBoundaryState
|
||||||
|
> {
|
||||||
|
constructor(props: ElementErrorBoundaryProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(): ElementErrorBoundaryState {
|
||||||
|
return { hasError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, info: ErrorInfo) {
|
||||||
|
console.error(
|
||||||
|
`[json-render] Rendering error in <${this.props.elementType}>:`,
|
||||||
|
error,
|
||||||
|
info.componentStack,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
// Render nothing – the element silently disappears rather than
|
||||||
|
// crashing the entire application.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Element renderer component
|
* Element renderer component
|
||||||
*/
|
*/
|
||||||
@@ -214,9 +260,11 @@ function ElementRenderer({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
<ElementErrorBoundary elementType={resolvedElement.type}>
|
||||||
{children}
|
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||||
</Component>
|
{children}
|
||||||
|
</Component>
|
||||||
|
</ElementErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/react
|
# @json-render/react
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- d9a4efd: Prevent rendering errors from crashing the application. Added error boundaries to all renderers so a single bad component silently disappears instead of causing a white-screen-of-death. Fixed Select and Radio components to handle non-string option values from AI output.
|
||||||
|
- @json-render/core@0.5.1
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/react",
|
"name": "@json-render/react",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
type ComponentType,
|
type ComponentType,
|
||||||
|
type ErrorInfo,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -77,6 +78,51 @@ export interface RendererProps {
|
|||||||
fallback?: ComponentRenderer;
|
fallback?: ComponentRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ElementErrorBoundary – catches rendering errors in individual elements so
|
||||||
|
// a single bad component never crashes the whole page.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface ElementErrorBoundaryProps {
|
||||||
|
elementType: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ElementErrorBoundaryState {
|
||||||
|
hasError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElementErrorBoundary extends React.Component<
|
||||||
|
ElementErrorBoundaryProps,
|
||||||
|
ElementErrorBoundaryState
|
||||||
|
> {
|
||||||
|
constructor(props: ElementErrorBoundaryProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(): ElementErrorBoundaryState {
|
||||||
|
return { hasError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, info: ErrorInfo) {
|
||||||
|
console.error(
|
||||||
|
`[json-render] Rendering error in <${this.props.elementType}>:`,
|
||||||
|
error,
|
||||||
|
info.componentStack,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
// Render nothing – the element silently disappears rather than
|
||||||
|
// crashing the entire application.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Element renderer component
|
* Element renderer component
|
||||||
*/
|
*/
|
||||||
@@ -210,9 +256,11 @@ function ElementRenderer({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
<ElementErrorBoundary elementType={resolvedElement.type}>
|
||||||
{children}
|
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||||
</Component>
|
{children}
|
||||||
|
</Component>
|
||||||
|
</ElementErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/remotion
|
# @json-render/remotion
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- d9a4efd: Prevent rendering errors from crashing the application. Added error boundaries to all renderers so a single bad component silently disappears instead of causing a white-screen-of-death. Fixed Select and Radio components to handle non-string option values from AI output.
|
||||||
|
- @json-render/core@0.5.1
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/remotion",
|
"name": "@json-render/remotion",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
|
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import React, { type ErrorInfo, type ReactNode } from "react";
|
||||||
import { AbsoluteFill, Sequence } from "remotion";
|
import { AbsoluteFill, Sequence } from "remotion";
|
||||||
import type { TimelineSpec, ComponentRegistry, Clip } from "./types";
|
import type { TimelineSpec, ComponentRegistry, Clip } from "./types";
|
||||||
|
|
||||||
@@ -33,6 +34,50 @@ export const standardComponents: ComponentRegistry = {
|
|||||||
VideoClip,
|
VideoClip,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ClipErrorBoundary – catches rendering errors in individual clips so
|
||||||
|
// a single bad clip never crashes the entire composition.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface ClipErrorBoundaryProps {
|
||||||
|
clipId: string;
|
||||||
|
component: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClipErrorBoundaryState {
|
||||||
|
hasError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClipErrorBoundary extends React.Component<
|
||||||
|
ClipErrorBoundaryProps,
|
||||||
|
ClipErrorBoundaryState
|
||||||
|
> {
|
||||||
|
constructor(props: ClipErrorBoundaryProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(): ClipErrorBoundaryState {
|
||||||
|
return { hasError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
||||||
|
console.error(
|
||||||
|
`[json-render/remotion] Rendering error in clip "${this.props.clipId}" (<${this.props.component}>):`,
|
||||||
|
error,
|
||||||
|
info.componentStack,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface RendererProps {
|
interface RendererProps {
|
||||||
/** The timeline spec to render */
|
/** The timeline spec to render */
|
||||||
spec: TimelineSpec;
|
spec: TimelineSpec;
|
||||||
@@ -102,7 +147,9 @@ export function Renderer({
|
|||||||
from={clip.from}
|
from={clip.from}
|
||||||
durationInFrames={clip.durationInFrames}
|
durationInFrames={clip.durationInFrames}
|
||||||
>
|
>
|
||||||
<Component clip={clip} />
|
<ClipErrorBoundary clipId={clip.id} component={clip.component}>
|
||||||
|
<Component clip={clip} />
|
||||||
|
</ClipErrorBoundary>
|
||||||
</Sequence>
|
</Sequence>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ const finalSpec = compiler.getResult();
|
|||||||
|
|
||||||
Any prop value can be a dynamic expression resolved at render time:
|
Any prop value can be a dynamic expression resolved at render time:
|
||||||
|
|
||||||
- **`{ "$path": "/state/key" }`** -- reads a value from the state model
|
- **`{ "$path": "/state/key" }`** - reads a value from the state model
|
||||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- evaluates a visibility condition and picks a branch
|
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - evaluates a visibility condition and picks a branch
|
||||||
|
|
||||||
`$cond` uses the same syntax as visibility conditions (`eq`, `neq`, `path`, `and`, `or`, `not`). `$then` and `$else` can themselves be expressions (recursive).
|
`$cond` uses the same syntax as visibility conditions (`eq`, `neq`, `path`, `and`, `or`, `not`). `$then` and `$else` can themselves be expressions (recursive).
|
||||||
|
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ Use `Pressable` with the built-in `setState` action for interactive UIs like tab
|
|||||||
|
|
||||||
Any prop value can be a data-driven expression resolved at render time:
|
Any prop value can be a data-driven expression resolved at render time:
|
||||||
|
|
||||||
- **`{ "$path": "/state/key" }`** -- reads from data model
|
- **`{ "$path": "/state/key" }`** - reads from data model
|
||||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- conditional value
|
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -123,7 +123,7 @@ Any prop value can be a data-driven expression resolved at render time:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Components receive already-resolved props -- no changes needed to component implementations.
|
Components receive already-resolved props. No changes needed to component implementations.
|
||||||
|
|
||||||
## Built-in Actions
|
## Built-in Actions
|
||||||
|
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ The React schema uses an element tree format:
|
|||||||
|
|
||||||
Any prop value can be a data-driven expression resolved by the renderer before components receive props:
|
Any prop value can be a data-driven expression resolved by the renderer before components receive props:
|
||||||
|
|
||||||
- **`{ "$path": "/state/key" }`** -- reads from data model
|
- **`{ "$path": "/state/key" }`** - reads from data model
|
||||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- conditional value
|
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -106,7 +106,7 @@ Any prop value can be a data-driven expression resolved by the renderer before c
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Components receive already-resolved props -- no changes needed to component implementations.
|
Components receive already-resolved props. No changes needed to component implementations.
|
||||||
|
|
||||||
## Event System
|
## Event System
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user