Compare commits

..
Author SHA1 Message Date
github-actions[bot] edbeb5a637 chore: version packages (#87)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-09 07:09:15 -06:00
Chris Tate d9a4efdbeb changeset (#86) 2026-02-09 07:07:17 -06:00
Chris Tate f435643817 more resilient (#85)
* more resilient

* more resilient
2026-02-09 07:05:16 -06:00
Chris Tate 458f3a728c update docs (#84)
* update docs

* fix og
2026-02-09 02:16:06 -06:00
14 changed files with 209 additions and 20 deletions
+25 -8
View File
@@ -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}
+6
View File
@@ -1,5 +1,11 @@
# @json-render/codegen
## 0.5.1
### Patch Changes
- @json-render/core@0.5.1
## 0.5.0
### Minor Changes
+1 -1
View File
@@ -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": [
+2
View File
@@ -1,5 +1,7 @@
# @json-render/core
## 0.5.1
## 0.5.0
### Minor Changes
+1 -1
View File
@@ -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": [
+7
View File
@@ -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
+1 -1
View File
@@ -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": [
+51 -3
View File
@@ -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>
);
}
+7
View File
@@ -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 -1
View File
@@ -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": [
+51 -3
View File
@@ -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>
);
}
+7
View File
@@ -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 -1
View File
@@ -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": [
+48 -1
View File
@@ -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>
);
};