mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-02 20:08:16 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edbeb5a637 | ||
|
|
d9a4efdbeb | ||
|
|
f435643817 | ||
|
|
458f3a728c |
@@ -1,8 +1,8 @@
|
||||
# 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
|
||||
npm install @json-render/core @json-render/react
|
||||
@@ -14,11 +14,12 @@ npm install @json-render/core @json-render/remotion
|
||||
|
||||
## 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
|
||||
- **Predictable** — JSON output matches your schema, every time
|
||||
- **Fast** — Stream and render progressively as the model responds
|
||||
- **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 (web) and React Native (mobile) from the same catalog
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -237,8 +238,8 @@ Any prop value can be data-driven using expressions:
|
||||
|
||||
Two expression forms:
|
||||
|
||||
- **`{ "$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
|
||||
- **`{ "$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
|
||||
|
||||
### Actions
|
||||
|
||||
@@ -265,9 +266,9 @@ pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- http://localhost:3000 -- Docs & Playground
|
||||
- http://localhost:3001 -- Example Dashboard
|
||||
- http://localhost:3002 -- Remotion Video Example
|
||||
- http://localhost:3000 - Docs & Playground
|
||||
- http://localhost:3001 - Example Dashboard
|
||||
- http://localhost:3002 - Remotion Video Example
|
||||
- React Native example: run `npx expo start` in `examples/react-native`
|
||||
|
||||
## How It Works
|
||||
@@ -283,10 +284,10 @@ flowchart LR
|
||||
D -.- G([streamed])
|
||||
```
|
||||
|
||||
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
|
||||
3. **AI generates JSON** — output is always predictable, constrained to your catalog
|
||||
4. **Render fast** — stream and render progressively as the model responds
|
||||
1. **Define the guardrails** - what components, actions, and data bindings AI can use
|
||||
2. **Users generate** - end users describe what they 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
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ React Native renderer with standard components, providers, and hooks.
|
||||
|
||||
## 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
|
||||
import { defineRegistry, type Components } from '@json-render/react-native';
|
||||
|
||||
@@ -100,7 +100,7 @@ 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 `$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
|
||||
|
||||
|
||||
@@ -2,17 +2,19 @@ export const metadata = { title: "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?
|
||||
|
||||
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?
|
||||
|
||||
### 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
### Cross-Platform
|
||||
|
||||
Render on web with React and on mobile with React Native from the same catalog and spec format.
|
||||
|
||||
## How it works
|
||||
|
||||
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
|
||||
3. AI generates JSON — output is always predictable, constrained to your catalog
|
||||
4. Render fast — stream and render progressively as the model responds
|
||||
1. Define the guardrails - what components, actions, and data bindings AI can use
|
||||
2. Users generate - end users describe what they 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
|
||||
|
||||
@@ -9,12 +9,15 @@ export default function Home() {
|
||||
<>
|
||||
{/* Hero */}
|
||||
<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
|
||||
</h1>
|
||||
<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
|
||||
to your catalog. Your components render it.
|
||||
Dynamic, personalized UIs per user without sacrificing reliability.
|
||||
Predefined components and actions for safe, predictable output.
|
||||
</p>
|
||||
|
||||
<Demo />
|
||||
@@ -65,10 +68,10 @@ export default function Home() {
|
||||
<div className="text-xs text-muted-foreground font-mono mb-3">
|
||||
02
|
||||
</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">
|
||||
End users describe what they want. AI generates JSON constrained
|
||||
to your catalog.
|
||||
to your catalog. Every interface is unique to the user.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -244,6 +247,10 @@ export default function Page() {
|
||||
<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">
|
||||
{[
|
||||
{
|
||||
title: "User-Generated Interfaces",
|
||||
desc: "Dynamic, personalized UIs per user powered by Generative UI",
|
||||
},
|
||||
{
|
||||
title: "Guardrails",
|
||||
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",
|
||||
},
|
||||
{
|
||||
title: "Code Export",
|
||||
desc: "Export as standalone React code with no runtime dependencies",
|
||||
title: "React & React Native",
|
||||
desc: "Render on web and mobile from the same catalog and spec format",
|
||||
},
|
||||
{
|
||||
title: "Data Binding",
|
||||
desc: "Two-way binding with JSON Pointer paths",
|
||||
desc: "Two-way state binding with dynamic prop expressions",
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
desc: "Named actions handled by your application",
|
||||
},
|
||||
{
|
||||
title: "Visibility",
|
||||
desc: "Conditional show/hide based on data or auth",
|
||||
title: "Code Export",
|
||||
desc: "Export as standalone React code with no runtime dependencies",
|
||||
},
|
||||
].map((feature) => (
|
||||
<div key={feature.title}>
|
||||
|
||||
+10
-6
@@ -22,11 +22,15 @@ export const metadata: Metadata = {
|
||||
template: "%s | json-render",
|
||||
},
|
||||
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: [
|
||||
"json-render",
|
||||
"UGI",
|
||||
"User-Generated Interfaces",
|
||||
"AI UI generation",
|
||||
"generative UI",
|
||||
"React components",
|
||||
"React Native",
|
||||
"guardrails",
|
||||
"structured output",
|
||||
"dashboard builder",
|
||||
@@ -38,23 +42,23 @@ export const metadata: Metadata = {
|
||||
locale: "en_US",
|
||||
url: "https://json-render.dev",
|
||||
siteName: "json-render",
|
||||
title: "json-render | AI-generated UI with guardrails",
|
||||
title: "json-render | The framework for User-Generated Interfaces",
|
||||
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: [
|
||||
{
|
||||
url: "/og",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "json-render - AI-generated UI with guardrails",
|
||||
alt: "json-render - The framework for User-Generated Interfaces",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "json-render | AI-generated UI with guardrails",
|
||||
title: "json-render | The framework for User-Generated Interfaces",
|
||||
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"],
|
||||
creator: "@verabornnot",
|
||||
},
|
||||
|
||||
@@ -66,23 +66,27 @@ export async function renderOgImage(title: string) {
|
||||
style={{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 72,
|
||||
fontFamily: "Geist",
|
||||
fontWeight: 400,
|
||||
color: "white",
|
||||
letterSpacing: "-0.02em",
|
||||
textAlign: "center",
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
{title.split("\n").map((line, i) => (
|
||||
<span
|
||||
key={i}
|
||||
style={{
|
||||
fontSize: 72,
|
||||
fontFamily: "Geist",
|
||||
fontWeight: 400,
|
||||
color: "white",
|
||||
letterSpacing: "-0.02em",
|
||||
textAlign: "center",
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{line}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>,
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
export const PAGE_TITLES: Record<string, string> = {
|
||||
// Home (no slug)
|
||||
"": "AI-generated UI with guardrails",
|
||||
"": "The framework for\nUser-Generated Interfaces",
|
||||
|
||||
// Top-level
|
||||
playground: "Playground",
|
||||
|
||||
@@ -823,7 +823,12 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
const [localValue, setLocalValue] = useState<string>("");
|
||||
const value = props.statePath ? (boundValue ?? "") : localValue;
|
||||
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 (
|
||||
<div className="space-y-2">
|
||||
@@ -839,8 +844,11 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
<SelectValue placeholder={props.placeholder ?? "Select..."} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{options.map((opt) => (
|
||||
<SelectItem key={opt} value={opt}>
|
||||
{options.map((opt, idx) => (
|
||||
<SelectItem
|
||||
key={`${idx}-${opt}`}
|
||||
value={opt || `option-${idx}`}
|
||||
>
|
||||
{opt}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -876,7 +884,10 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
},
|
||||
|
||||
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
|
||||
? useStateBinding<string>(props.statePath) // eslint-disable-line react-hooks/rules-of-hooks
|
||||
: [undefined, undefined];
|
||||
@@ -894,11 +905,17 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
emit?.("change");
|
||||
}}
|
||||
>
|
||||
{options.map((opt) => (
|
||||
<div key={opt} className="flex items-center space-x-2">
|
||||
<RadioGroupItem value={opt} id={`${props.name}-${opt}`} />
|
||||
{options.map((opt, idx) => (
|
||||
<div
|
||||
key={`${idx}-${opt}`}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<RadioGroupItem
|
||||
value={opt || `option-${idx}`}
|
||||
id={`${props.name}-${idx}-${opt}`}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${props.name}-${opt}`}
|
||||
htmlFor={`${props.name}-${idx}-${opt}`}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{opt}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @json-render/codegen
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @json-render/core@0.5.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/codegen",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Utilities for generating code from json-render UI trees",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @json-render/core
|
||||
|
||||
## 0.5.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/core",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @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
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react-native",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, {
|
||||
type ComponentType,
|
||||
type ErrorInfo,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
@@ -81,6 +82,51 @@ export interface RendererProps {
|
||||
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
|
||||
*/
|
||||
@@ -214,9 +260,11 @@ function ElementRenderer({
|
||||
);
|
||||
|
||||
return (
|
||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||
{children}
|
||||
</Component>
|
||||
<ElementErrorBoundary elementType={resolvedElement.type}>
|
||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||
{children}
|
||||
</Component>
|
||||
</ElementErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @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
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
||||
"keywords": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React, {
|
||||
type ComponentType,
|
||||
type ErrorInfo,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
@@ -77,6 +78,51 @@ export interface RendererProps {
|
||||
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
|
||||
*/
|
||||
@@ -210,9 +256,11 @@ function ElementRenderer({
|
||||
);
|
||||
|
||||
return (
|
||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||
{children}
|
||||
</Component>
|
||||
<ElementErrorBoundary elementType={resolvedElement.type}>
|
||||
<Component element={resolvedElement} emit={emit} loading={loading}>
|
||||
{children}
|
||||
</Component>
|
||||
</ElementErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @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
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/remotion",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { type ErrorInfo, type ReactNode } from "react";
|
||||
import { AbsoluteFill, Sequence } from "remotion";
|
||||
import type { TimelineSpec, ComponentRegistry, Clip } from "./types";
|
||||
|
||||
@@ -33,6 +34,50 @@ export const standardComponents: ComponentRegistry = {
|
||||
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 {
|
||||
/** The timeline spec to render */
|
||||
spec: TimelineSpec;
|
||||
@@ -102,7 +147,9 @@ export function Renderer({
|
||||
from={clip.from}
|
||||
durationInFrames={clip.durationInFrames}
|
||||
>
|
||||
<Component clip={clip} />
|
||||
<ClipErrorBoundary clipId={clip.id} component={clip.component}>
|
||||
<Component clip={clip} />
|
||||
</ClipErrorBoundary>
|
||||
</Sequence>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -81,8 +81,8 @@ const finalSpec = compiler.getResult();
|
||||
|
||||
Any prop value can be a dynamic expression resolved at render time:
|
||||
|
||||
- **`{ "$path": "/state/key" }`** -- reads a value from the state model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- evaluates a visibility condition and picks a branch
|
||||
- **`{ "$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` 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:
|
||||
|
||||
- **`{ "$path": "/state/key" }`** -- reads from data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- conditional value
|
||||
- **`{ "$path": "/state/key" }`** - reads from data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
|
||||
|
||||
```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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
- **`{ "$path": "/state/key" }`** -- reads from data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- conditional value
|
||||
- **`{ "$path": "/state/key" }`** - reads from data model
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
|
||||
|
||||
```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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user