Compare commits

..
Author SHA1 Message Date
github-actions[bot] 96c7a452b3 chore: version packages (#71)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-06 14:33:12 -06:00
Chris Tate 844f18d1ee fix deps (#70) 2026-02-06 14:32:10 -06:00
Chris Tate 54bce097a5 add defineRegistry function (#67) 2026-02-06 14:30:06 -06:00
Chris Tate e2cb239c88 fix version (#69) 2026-02-06 14:28:38 -06:00
Chris Tate 90c59c674d update registry page (#68) 2026-02-06 14:27:07 -06:00
Chris Tate bba38727e8 ignore private packages (#66)
* ignore private packages

* fix ignore

* better ignore
2026-02-06 14:19:34 -06:00
Chris Tate 1d547b579c changeset (#65) 2026-02-06 14:12:00 -06:00
13 changed files with 187 additions and 146 deletions
+4 -1
View File
@@ -14,5 +14,8 @@
"access": "public", "access": "public",
"baseBranch": "main", "baseBranch": "main",
"updateInternalDependencies": "patch", "updateInternalDependencies": "patch",
"ignore": [] "privatePackages": {
"version": false,
"tag": false
}
} }
-2
View File
@@ -24,8 +24,6 @@ jobs:
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
+139 -112
View File
@@ -14,82 +14,86 @@ export default function RegistryPage() {
life. life.
</p> </p>
{/* Components Section */} {/* defineRegistry */}
<h2 className="text-xl font-semibold mt-12 mb-4">Component Registry</h2> <h2 className="text-xl font-semibold mt-12 mb-4">defineRegistry</h2>
<p className="text-sm text-muted-foreground mb-4"> <p className="text-sm text-muted-foreground mb-4">
Create a registry that maps catalog component types to React components: Use <code>defineRegistry</code> to create a type-safe registry from your
catalog. Pass your components, actions, or both in a single call:
</p> </p>
<Code lang="tsx">{`import { useAction } from '@json-render/react'; <Code lang="tsx">{`import { defineRegistry } from '@json-render/react';
import { myCatalog } from './catalog';
const registry = { export const { registry, handlers, executeAction } = defineRegistry(myCatalog, {
Card: ({ props, children }) => ( components: {
<div className="card"> Card: ({ props, children }) => (
<h2>{props.title}</h2> <div className="card">
{props.description && <p>{props.description}</p>} <h2>{props.title}</h2>
{children} {props.description && <p>{props.description}</p>}
</div> {children}
), </div>
),
Button: ({ props }) => {
const executeAction = useAction(props.action); Button: ({ props, onAction }) => (
return ( <button onClick={() => onAction?.({ name: props.action })}>
<button onClick={() => executeAction({})}>
{props.label} {props.label}
</button> </button>
); ),
}, },
};`}</Code>
actions: {
submit_form: async (params, setData) => {
const res = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(params),
});
const result = await res.json();
setData((prev) => ({ ...prev, formResult: result }));
},
export_data: async (params) => {
const blob = await generateExport(params.format);
downloadBlob(blob, \`export.\${params.format}\`);
},
},
});`}</Code>
<p className="text-sm text-muted-foreground mt-4 mb-4">
The returned object contains:
</p>
<ul className="list-disc list-inside text-sm text-muted-foreground mb-4 space-y-1">
<li>
<code>registry</code> - component registry for{" "}
<code>{"<Renderer />"}</code>
</li>
<li>
<code>handlers</code> - factory for ActionProvider-compatible handlers
</li>
<li>
<code>executeAction</code> - imperative action dispatch (for use
outside the React tree)
</li>
</ul>
{/* Component Props */}
<h2 className="text-xl font-semibold mt-12 mb-4">Component Props</h2> <h2 className="text-xl font-semibold mt-12 mb-4">Component Props</h2>
<p className="text-sm text-muted-foreground mb-4"> <p className="text-sm text-muted-foreground mb-4">
Each component receives these props: Each component in the registry receives a <code>ComponentContext</code>{" "}
object:
</p> </p>
<Code lang="typescript">{`interface ComponentProps<T = Record<string, unknown>> { <Code lang="typescript">{`interface ComponentContext {
props: T; // Component props from the spec props: T; // Type-safe props from your catalog
children?: React.ReactNode; // Rendered children (for slot components) children?: React.ReactNode; // Rendered children (for slot components)
} onAction?: (action: ActionTrigger) => void; // Dispatch an action
loading?: boolean; // Whether the renderer is in a loading state
}`}</Code>
// Type-safe props by extracting from your catalog <p className="text-sm text-muted-foreground mt-4 mb-4">
type CardProps = ComponentProps<{ Props are automatically inferred from your catalog, so{" "}
title: string; <code>props.title</code> is typed as <code>string</code> if your catalog
description: string | null; defines it that way.
}>;
// Use hooks for actions and data within components
import { useAction, useDataValue } from '@json-render/react';`}</Code>
<h2 className="text-xl font-semibold mt-12 mb-4">Using Data Binding</h2>
<p className="text-sm text-muted-foreground mb-4">
Use hooks to read and write data:
</p> </p>
<Code lang="tsx">{`import { useDataValue, useDataBinding } from '@json-render/react';
const Metric = ({ props }) => { {/* Action Handlers */}
// Read-only value from data context
const value = useDataValue(props.valuePath);
return (
<div className="metric">
<span className="label">{props.label}</span>
<span className="value">{formatValue(value)}</span>
</div>
);
};
const TextField = ({ props }) => {
// Two-way binding to data context
const [value, setValue] = useDataBinding(props.valuePath);
return (
<input
value={value || ''}
onChange={(e) => setValue(e.target.value)}
placeholder={props.placeholder}
/>
);
};`}</Code>
{/* Actions Section */}
<h2 className="text-xl font-semibold mt-12 mb-4">Action Handlers</h2> <h2 className="text-xl font-semibold mt-12 mb-4">Action Handlers</h2>
<p className="text-sm text-muted-foreground mb-4"> <p className="text-sm text-muted-foreground mb-4">
Instead of AI generating arbitrary code, it declares <em>intent</em> by Instead of AI generating arbitrary code, it declares <em>intent</em> by
@@ -117,9 +121,6 @@ const catalog = defineCatalog(schema, {
export_data: { export_data: {
params: z.object({ params: z.object({
format: z.enum(['csv', 'pdf', 'json']), format: z.enum(['csv', 'pdf', 'json']),
filters: z.object({
dateRange: z.string().nullable(),
}).nullable(),
}), }),
}, },
navigate: { navigate: {
@@ -130,78 +131,104 @@ const catalog = defineCatalog(schema, {
}, },
});`}</Code> });`}</Code>
<h3 className="text-lg font-medium mt-8 mb-3">ActionProvider</h3> <h3 className="text-lg font-medium mt-8 mb-3">
Implementing Action Handlers
</h3>
<p className="text-sm text-muted-foreground mb-4"> <p className="text-sm text-muted-foreground mb-4">
Provide action handlers to your app: Action handlers receive <code>(params, setData, data)</code> and are
defined inside <code>defineRegistry</code>:
</p> </p>
<Code lang="tsx">{`import { ActionProvider } from '@json-render/react'; <Code lang="tsx">{`export const { handlers, executeAction } = defineRegistry(catalog, {
actions: {
function App() { submit_form: async (params, setData) => {
const handlers = {
submit_form: async (params) => {
const response = await fetch('/api/submit', { const response = await fetch('/api/submit', {
method: 'POST', method: 'POST',
body: JSON.stringify({ formId: params.formId }), body: JSON.stringify({ formId: params.formId }),
}); });
return response.json(); const result = await response.json();
setData((prev) => ({ ...prev, formResult: result }));
}, },
export_data: async (params) => { export_data: async (params) => {
const blob = await generateExport(params.format, params.filters); const blob = await generateExport(params.format);
downloadBlob(blob, \`export.\${params.format}\`); downloadBlob(blob, \`export.\${params.format}\`);
}, },
navigate: (params) => { navigate: (params) => {
window.location.href = params.url; window.location.href = params.url;
}, },
}; },
});`}</Code>
{/* Using Data Binding */}
<h2 className="text-xl font-semibold mt-12 mb-4">Using Data Binding</h2>
<p className="text-sm text-muted-foreground mb-4">
Use hooks inside your registry components to read and write data:
</p>
<Code lang="tsx">{`import { useData } from '@json-render/react';
import { getByPath } from '@json-render/core';
// Inside defineRegistry components:
Metric: ({ props }) => {
const { data } = useData();
const value = getByPath(data, props.valuePath);
return ( return (
<ActionProvider handlers={handlers}> <div className="metric">
{/* Your UI */} <span className="label">{props.label}</span>
</ActionProvider> <span className="value">{formatValue(value)}</span>
</div>
); );
}`}</Code> },
<h3 className="text-lg font-medium mt-8 mb-3"> TextField: ({ props }) => {
Using Actions in Components const { data, set } = useData();
</h3> const value = getByPath(data, props.valuePath) as string;
<Code lang="tsx">{`import { useAction } from '@json-render/react';
// Using the useAction hook (recommended)
const Button = ({ props }) => {
const executeAction = useAction(props.action);
return ( return (
<button onClick={() => executeAction({})}> <input
{props.label} value={value || ''}
</button> onChange={(e) => set(props.valuePath, e.target.value)}
placeholder={props.placeholder}
/>
); );
}; },`}</Code>
// Or for standalone use
function SubmitButton() {
const submitForm = useAction('submit_form');
return (
<button onClick={() => submitForm({ formId: 'contact' })}>
Submit
</button>
);
}`}</Code>
{/* Renderer Section */} {/* Renderer Section */}
<h2 className="text-xl font-semibold mt-12 mb-4">Using the Renderer</h2> <h2 className="text-xl font-semibold mt-12 mb-4">Using the Renderer</h2>
<Code lang="tsx">{`import { Renderer, ActionProvider } from '@json-render/react'; <p className="text-sm text-muted-foreground mb-4">
Wire everything together with providers and the{" "}
<code>{"<Renderer />"}</code> component:
</p>
<Code lang="tsx">{`import { useMemo, useRef } from 'react';
import {
Renderer,
DataProvider,
VisibilityProvider,
ActionProvider,
} from '@json-render/react';
import { registry, handlers } from './registry';
function App({ spec, data, setData }) {
const dataRef = useRef(data);
const setDataRef = useRef(setData);
dataRef.current = data;
setDataRef.current = setData;
const actionHandlers = useMemo(
() => handlers(() => setDataRef.current, () => dataRef.current),
[],
);
function App() {
return ( return (
<ActionProvider handlers={actionHandlers}> <DataProvider initialData={data}>
<Renderer <VisibilityProvider>
spec={uiSpec} <ActionProvider handlers={actionHandlers}>
registry={registry} <Renderer spec={spec} registry={registry} />
/> </ActionProvider>
</ActionProvider> </VisibilityProvider>
</DataProvider>
); );
}`}</Code> }`}</Code>
+2 -2
View File
@@ -6,8 +6,8 @@
"license": "~~proprietary~~", "license": "~~proprietary~~",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@json-render/core": "^0.4.0", "@json-render/core": "workspace:*",
"@json-render/react": "^0.4.0", "@json-render/react": "workspace:*",
"@stripe/ui-extension-sdk": "^9.1.0", "@stripe/ui-extension-sdk": "^9.1.0",
"stripe": "^13.11.0" "stripe": "^13.11.0"
}, },
+9
View File
@@ -0,0 +1,9 @@
# @json-render/codegen
## 0.4.2
### Patch Changes
- 54bce09: add defineRegistry function
- Updated dependencies [54bce09]
- @json-render/core@0.4.2
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@json-render/codegen", "name": "@json-render/codegen",
"version": "0.4.0", "version": "0.4.2",
"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": [
+7
View File
@@ -0,0 +1,7 @@
# @json-render/core
## 0.4.2
### Patch Changes
- 54bce09: add defineRegistry function
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@json-render/core", "name": "@json-render/core",
"version": "0.4.0", "version": "0.4.2",
"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": [
+9
View File
@@ -0,0 +1,9 @@
# @json-render/react
## 0.4.2
### Patch Changes
- 54bce09: add defineRegistry function
- Updated dependencies [54bce09]
- @json-render/core@0.4.2
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@json-render/react", "name": "@json-render/react",
"version": "0.4.0", "version": "0.4.2",
"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": [
+9
View File
@@ -0,0 +1,9 @@
# @json-render/remotion
## 0.4.2
### Patch Changes
- 54bce09: add defineRegistry function
- Updated dependencies [54bce09]
- @json-render/core@0.4.2
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@json-render/remotion", "name": "@json-render/remotion",
"version": "0.4.1", "version": "0.4.2",
"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": [
+4 -25
View File
@@ -348,11 +348,11 @@ importers:
examples/stripe-app: examples/stripe-app:
dependencies: dependencies:
'@json-render/core': '@json-render/core':
specifier: ^0.4.0 specifier: workspace:*
version: 0.4.0(zod@4.3.5) version: link:../../packages/core
'@json-render/react': '@json-render/react':
specifier: ^0.4.0 specifier: workspace:*
version: 0.4.0(react@18.3.1)(zod@4.3.5) version: link:../../packages/react
'@stripe/ui-extension-sdk': '@stripe/ui-extension-sdk':
specifier: ^9.1.0 specifier: ^9.1.0
version: 9.1.0(stripe@13.11.0) version: 9.1.0(stripe@13.11.0)
@@ -1793,16 +1793,6 @@ packages:
'@jridgewell/trace-mapping@0.3.31': '@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@json-render/core@0.4.0':
resolution: {integrity: sha512-zcmNNetyXoqShG9qfXnipnkaGLn3XC7Kec/t3+C39mXAStg2RX29ciZwsZT+Fzo900LeRHRIGel7L/IHCdktrA==}
peerDependencies:
zod: ^4.0.0
'@json-render/react@0.4.0':
resolution: {integrity: sha512-OAXdWdOrAXHEFzpEF7xV+84D00JEmLMKlt5u0wc+C/P+q4q6TnpAWx2j28PBpDB2mpidkW9VnTUM+SCH9J8Lrw==}
peerDependencies:
react: ^19.0.0
'@manypkg/find-root@1.1.0': '@manypkg/find-root@1.1.0':
resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
@@ -8002,17 +7992,6 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
'@json-render/core@0.4.0(zod@4.3.5)':
dependencies:
zod: 4.3.5
'@json-render/react@0.4.0(react@18.3.1)(zod@4.3.5)':
dependencies:
'@json-render/core': 0.4.0(zod@4.3.5)
react: 18.3.1
transitivePeerDependencies:
- zod
'@manypkg/find-root@1.1.0': '@manypkg/find-root@1.1.0':
dependencies: dependencies:
'@babel/runtime': 7.28.6 '@babel/runtime': 7.28.6