mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-03 20:38:21 +08:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caa90b8b84 | ||
|
|
54a1ecf817 | ||
|
|
dc9e9d17f3 |
@@ -4,6 +4,7 @@
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
.pnpm-store/
|
||||
|
||||
# Local env files
|
||||
.env*
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# web
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/codegen@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -3,7 +3,7 @@ export const metadata = pageMetadata("docs/ai-sdk")
|
||||
|
||||
# AI SDK Integration
|
||||
|
||||
Use json-render with the [Vercel AI SDK](https://sdk.vercel.ai) for seamless streaming. json-render supports two modes: **Generate** (standalone UI) and **Chat** (UI embedded in conversation). See [Generation Modes](/docs/generation-modes) for a detailed comparison.
|
||||
Use json-render with the [Vercel AI SDK](https://sdk.vercel.ai) for seamless streaming. json-render supports two modes: **Standalone** (standalone UI) and **Inline** (UI embedded in conversation). See [Generation Modes](/docs/generation-modes) for a detailed comparison.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -11,9 +11,9 @@ Use json-render with the [Vercel AI SDK](https://sdk.vercel.ai) for seamless str
|
||||
npm install ai @ai-sdk/react
|
||||
```
|
||||
|
||||
## Generate Mode
|
||||
## Standalone Mode
|
||||
|
||||
In generate mode, the AI outputs only JSONL patches. The entire response is a UI spec with no prose. This is the default mode and is ideal for playgrounds, builders, and dashboard generators.
|
||||
In standalone mode, the AI outputs only JSONL patches. The entire response is a UI spec with no prose. This is the default mode and is ideal for playgrounds, builders, and dashboard generators.
|
||||
|
||||
### API Route
|
||||
|
||||
@@ -73,9 +73,9 @@ function GenerativeUI() {
|
||||
}
|
||||
```
|
||||
|
||||
## Chat Mode
|
||||
## Inline Mode
|
||||
|
||||
In chat mode, the AI responds conversationally and includes JSONL patches inline. Text-only replies are allowed when no UI is needed. This is ideal for chatbots, copilots, and educational assistants.
|
||||
In inline mode, the AI responds conversationally and includes JSONL patches inline. Text-only replies are allowed when no UI is needed. This is ideal for chatbots, copilots, and educational assistants.
|
||||
|
||||
### API Route
|
||||
|
||||
@@ -96,7 +96,7 @@ export async function POST(req: Request) {
|
||||
|
||||
const result = streamText({
|
||||
model: yourModel,
|
||||
system: catalog.prompt({ mode: "chat" }),
|
||||
system: catalog.prompt({ mode: "inline" }),
|
||||
messages,
|
||||
});
|
||||
|
||||
@@ -182,13 +182,13 @@ const systemPrompt = catalog.prompt({
|
||||
});
|
||||
```
|
||||
|
||||
### Chat Mode Prompt
|
||||
### Inline Mode Prompt
|
||||
|
||||
```typescript
|
||||
const chatPrompt = catalog.prompt({ mode: "chat" });
|
||||
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||
```
|
||||
|
||||
In chat mode, the prompt instructs the AI to respond conversationally first, then include JSONL patches on their own lines when UI is needed. Text-only replies are allowed.
|
||||
In inline mode, the prompt instructs the AI to respond conversationally first, then include JSONL patches on their own lines when UI is needed. Text-only replies are allowed.
|
||||
|
||||
## Which Mode?
|
||||
|
||||
@@ -197,8 +197,8 @@ In chat mode, the prompt instructs the AI to respond conversationally first, the
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Generate</th>
|
||||
<th>Chat</th>
|
||||
<th>Standalone</th>
|
||||
<th>Inline</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -215,7 +215,7 @@ In chat mode, the prompt instructs the AI to respond conversationally first, the
|
||||
<tr>
|
||||
<td>System prompt</td>
|
||||
<td><code>catalog.prompt()</code></td>
|
||||
<td><code>{"catalog.prompt({ mode: \"chat\" })"}</code></td>
|
||||
<td><code>{"catalog.prompt({ mode: \"inline\" })"}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stream utility</td>
|
||||
|
||||
@@ -75,7 +75,7 @@ interface Catalog {
|
||||
interface PromptOptions {
|
||||
system?: string; // Custom system message intro
|
||||
customRules?: string[]; // Additional rules to append
|
||||
mode?: "generate" | "chat"; // Output mode (default: "generate")
|
||||
mode?: "standalone" | "inline" | "generate" | "chat"; // Output mode (default: "standalone")
|
||||
}
|
||||
|
||||
interface SpecValidationResult<T> {
|
||||
@@ -370,7 +370,7 @@ Most users should use `pipeJsonRender()` instead, which wraps this transform for
|
||||
|
||||
### createMixedStreamParser
|
||||
|
||||
Parse a mixed stream of text and JSONL patches (used for Chat + GenUI mode):
|
||||
Parse a mixed stream of text and JSONL patches (used for Inline mode):
|
||||
|
||||
```typescript
|
||||
import { createMixedStreamParser } from '@json-render/core';
|
||||
@@ -389,7 +389,7 @@ parser.flush();
|
||||
|
||||
### pipeJsonRender
|
||||
|
||||
Pipe an AI SDK `UIMessageStream` through the json-render transform. Lines that parse as JSONL patches are emitted as `data-spec` parts; everything else passes through as text. Used in Chat mode API routes.
|
||||
Pipe an AI SDK `UIMessageStream` through the json-render transform. Lines that parse as JSONL patches are emitted as `data-spec` parts; everything else passes through as text. Used in Inline mode API routes.
|
||||
|
||||
```typescript
|
||||
import { pipeJsonRender } from '@json-render/core';
|
||||
@@ -403,7 +403,7 @@ const stream = createUIMessageStream({
|
||||
return createUIMessageStreamResponse({ stream });
|
||||
```
|
||||
|
||||
See [Generation Modes](/docs/generation-modes) for full Chat mode setup.
|
||||
See [Generation Modes](/docs/generation-modes) for full Inline mode setup.
|
||||
|
||||
### SpecStream Types
|
||||
|
||||
|
||||
@@ -341,6 +341,8 @@ February 2026
|
||||
|
||||
### New: Chat Mode (Inline GenUI)
|
||||
|
||||
> **Note:** These modes were renamed in v0.12.1 — "Generate" is now "Standalone" and "Chat" is now "Inline". The old names are accepted as deprecated aliases.
|
||||
|
||||
json-render now supports two generation modes: **Generate** (JSONL-only, the default) and **Chat** (text + JSONL inline). Chat mode lets the AI respond conversationally with embedded UI specs, ideal for chatbots and copilot experiences.
|
||||
|
||||
```typescript
|
||||
|
||||
@@ -3,15 +3,15 @@ export const metadata = pageMetadata("docs/generation-modes")
|
||||
|
||||
# Generation Modes
|
||||
|
||||
json-render supports two modes for AI-generated UI: **Generate mode** for standalone UI and **Chat mode** for inline UI within a conversation.
|
||||
json-render supports two modes for AI-generated UI: **Standalone mode** for standalone UI and **Inline mode** for inline UI within a conversation.
|
||||
|
||||
The mode controls how the AI formats its output and how your app processes the stream. The underlying JSONL patch format is the same in both modes.
|
||||
|
||||
<GenerationModesDiagram />
|
||||
|
||||
## Generate Mode (Standalone)
|
||||
## Standalone Mode
|
||||
|
||||
In generate mode, the AI outputs **only JSONL patches** — no prose, no markdown. The entire response is a UI spec.
|
||||
In standalone mode, the AI outputs **only JSONL patches** — no prose, no markdown. The entire response is a UI spec.
|
||||
|
||||
This is the default mode and is ideal for:
|
||||
|
||||
@@ -25,7 +25,7 @@ This is the default mode and is ideal for:
|
||||
```typescript
|
||||
import { streamText } from "ai";
|
||||
|
||||
// Generate mode is the default (no mode option needed)
|
||||
// Standalone mode is the default (no mode option needed)
|
||||
const systemPrompt = catalog.prompt({
|
||||
customRules: [
|
||||
"Use Card as root for forms and small UIs.",
|
||||
@@ -74,9 +74,9 @@ The AI outputs only JSONL — one patch per line, no surrounding text:
|
||||
{"op":"add","path":"/elements/submit","value":{"type":"Button","props":{"label":"Sign In"}}}
|
||||
```
|
||||
|
||||
## Chat Mode (Inline)
|
||||
## Inline Mode
|
||||
|
||||
In chat mode, the AI responds **conversationally first**, then outputs JSONL patches on their own lines. Text-only replies are allowed when no UI is needed (e.g. greetings, clarifying questions).
|
||||
In inline mode, the AI responds **conversationally first**, then outputs JSONL patches on their own lines. Text-only replies are allowed when no UI is needed (e.g. greetings, clarifying questions).
|
||||
|
||||
This is ideal for:
|
||||
|
||||
@@ -92,8 +92,8 @@ import { streamText } from "ai";
|
||||
import { pipeJsonRender } from "@json-render/core";
|
||||
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";
|
||||
|
||||
// Enable chat mode
|
||||
const systemPrompt = catalog.prompt({ mode: "chat" });
|
||||
// Enable inline mode
|
||||
const systemPrompt = catalog.prompt({ mode: "inline" });
|
||||
|
||||
const result = streamText({
|
||||
model: yourModel,
|
||||
@@ -180,8 +180,8 @@ If the user asks a simple question ("what does BTC stand for?"), the AI replies
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>Generate</th>
|
||||
<th>Chat</th>
|
||||
<th>Standalone</th>
|
||||
<th>Inline</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -198,7 +198,7 @@ If the user asks a simple question ("what does BTC stand for?"), the AI replies
|
||||
<tr>
|
||||
<td>System prompt</td>
|
||||
<td><code>{"catalog.prompt()"}</code></td>
|
||||
<td><code>{'catalog.prompt({ mode: "chat" })'}</code></td>
|
||||
<td><code>{'catalog.prompt({ mode: "inline" })'}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stream utility</td>
|
||||
|
||||
@@ -309,10 +309,35 @@ const catalog = defineCatalog(schema, {
|
||||
|
||||
const prompt = catalog.prompt();
|
||||
|
||||
// Chat mode prompt
|
||||
// Inline mode prompt (formerly "chat")
|
||||
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||
```
|
||||
|
||||
## Generation Modes
|
||||
|
||||
The generation mode values passed to `catalog.prompt()` have been renamed for clarity:
|
||||
|
||||
- `"generate"` is now `"standalone"`
|
||||
- `"chat"` is now `"inline"`
|
||||
|
||||
The old names are accepted as deprecated aliases, so existing code will continue to work. Update when convenient.
|
||||
|
||||
**Before:**
|
||||
|
||||
```typescript
|
||||
const prompt = catalog.prompt({ mode: "generate" });
|
||||
const chatPrompt = catalog.prompt({ mode: "chat" });
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```typescript
|
||||
const prompt = catalog.prompt({ mode: "standalone" });
|
||||
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||
```
|
||||
|
||||
The default mode (when no `mode` option is provided) is `"standalone"`, which behaves identically to the previous `"generate"` default.
|
||||
|
||||
## Validation
|
||||
|
||||
`ValidationCheck` now uses `type` instead of `fn`, `ValidationProvider` uses `customFunctions` instead of `functions`, and `useFieldValidation` takes a config object instead of a checks array.
|
||||
|
||||
@@ -91,7 +91,7 @@ The result is a native UI built from your own components — not an iframe, not
|
||||
- **[Streaming](/docs/streaming)** — Render progressively as the AI responds. Each JSONL patch adds to the spec and the UI updates in real time.
|
||||
- **[Data Binding](/docs/data-binding)** — Bind props to runtime data with `$state` paths, repeat elements over arrays, and wire two-way input bindings.
|
||||
- **[Visibility](/docs/visibility)** — Show or hide elements based on state conditions. The AI can generate conditional UIs without writing logic.
|
||||
- **[Generation Modes](/docs/generation-modes)** — Generate standalone UI (playground/builder) or inline UI within a chat conversation.
|
||||
- **[Generation Modes](/docs/generation-modes)** — Standalone mode for full-page generated UI or inline mode for UI embedded in a conversation.
|
||||
|
||||
## Next
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ function FormUI() {
|
||||
);
|
||||
}
|
||||
|
||||
function ChatModeDiagram() {
|
||||
function InlineModeDiagram() {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="text-xs font-medium text-muted-foreground mb-3 text-center">
|
||||
Chat Mode
|
||||
Inline Mode
|
||||
</div>
|
||||
<div className="flex-1 border border-border rounded-lg bg-background overflow-hidden flex flex-col">
|
||||
{/* Chat area */}
|
||||
@@ -69,11 +69,11 @@ function ChatModeDiagram() {
|
||||
);
|
||||
}
|
||||
|
||||
function GenerateModeDiagram() {
|
||||
function StandaloneModeDiagram() {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="text-xs font-medium text-muted-foreground mb-3 text-center">
|
||||
Generate Mode
|
||||
Standalone Mode
|
||||
</div>
|
||||
<div className="flex-1 border border-border rounded-lg bg-background overflow-hidden flex flex-row">
|
||||
{/* Left panel - prompt */}
|
||||
@@ -104,10 +104,10 @@ export function GenerationModesDiagram() {
|
||||
<div className="not-prose my-8">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div className="h-[280px]">
|
||||
<ChatModeDiagram />
|
||||
<InlineModeDiagram />
|
||||
</div>
|
||||
<div className="h-[280px]">
|
||||
<GenerateModeDiagram />
|
||||
<StandaloneModeDiagram />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# example-chat
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
- @json-render/shadcn@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -130,7 +130,7 @@ When the user asks for a quiz, test, or Q&A, build an interactive experience:
|
||||
4. You can also add a final score section that becomes visible when all questions are submitted.
|
||||
|
||||
${explorerCatalog.prompt({
|
||||
mode: "chat",
|
||||
mode: "inline",
|
||||
customRules: [
|
||||
"NEVER use viewport height classes (min-h-screen, h-screen) — the UI renders inside a fixed-size container.",
|
||||
"Prefer Grid with columns='2' or columns='3' for side-by-side layouts.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-chat",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# example-dashboard
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/codegen@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-dashboard",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# example-image
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/image@0.12.1
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-image",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# example-mcp
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/mcp@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
- @json-render/shadcn@0.12.1
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-mcp",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# example-no-ai
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
- @json-render/shadcn@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-no-ai",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# example-react-email
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react-email@0.12.1
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-react-email",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# example-react-native
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react-native@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-react-native",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"private": true,
|
||||
"main": "expo-router/entry",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# example-react-pdf
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react-pdf@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-react-pdf",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# example-remotion
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/remotion@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-remotion",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# com.example.json-render-demo
|
||||
|
||||
## 0.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "com.example.json-render-demo",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"description": "Test",
|
||||
"private": true,
|
||||
"license": "~~proprietary~~",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# com.example.json-render-fullpage-demo
|
||||
|
||||
## 0.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "com.example.json-render-fullpage-demo",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"description": "Full-page Stripe App example (alpha)",
|
||||
"private": true,
|
||||
"license": "~~proprietary~~",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# svelte-chat
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/svelte@0.12.1
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "svelte-chat",
|
||||
"private": true,
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
||||
@@ -62,7 +62,7 @@ INPUT COMPONENTS:
|
||||
- Button: Clickable button. Use on.press to trigger actions.
|
||||
|
||||
${explorerCatalog.prompt({
|
||||
mode: "chat",
|
||||
mode: "inline",
|
||||
customRules: [
|
||||
"NEVER use viewport height classes (min-h-screen, h-screen) — the UI renders inside a fixed-size container.",
|
||||
"Prefer Grid with columns='2' or columns='3' for side-by-side layouts.",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# example-svelte
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/svelte@0.12.1
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-svelte",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# vite-renderers
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
- @json-render/svelte@0.12.1
|
||||
- @json-render/vue@0.12.1
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vite-renderers",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# example-vue
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/vue@0.12.1
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-vue",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"predev": "command -v portless >/dev/null 2>&1 || (echo '\\nportless is required but not installed. Run: npm i -g portless\\nSee: https://github.com/vercel-labs/portless\\n' && exit 1)",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/codegen
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/codegen",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Utilities for generating code from json-render UI trees",
|
||||
"keywords": [
|
||||
@@ -15,7 +15,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/codegen"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# @json-render/core
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 54a1ecf: Rename generation modes and fix MCP React duplicate module error.
|
||||
|
||||
### Changed:
|
||||
- **`@json-render/core`** — Renamed generation modes from `"generate"` / `"chat"` to `"standalone"` / `"inline"`. The old names still work but emit a deprecation warning.
|
||||
|
||||
### Fixed:
|
||||
- **`@json-render/mcp`** — Resolved React duplicate module error (`useRef` returning null) by adding `resolve.dedupe` Vite configuration. Added `./build-app-html` export entry point.
|
||||
|
||||
### Other:
|
||||
- Updated `homepage` URLs across all packages to point to `https://json-render.dev`.
|
||||
- Reorganized skills directory structure for cleaner naming.
|
||||
- Added skills documentation page to the web app.
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -227,7 +227,7 @@ Schema options:
|
||||
| `ActionBinding` | Action binding with `action`, `params`, `confirm`, `preventDefault`, etc. |
|
||||
| `BuiltInAction` | Built-in action definition with `name` and `description` |
|
||||
|
||||
### Chat Mode (Mixed Streams)
|
||||
### Inline Mode (Mixed Streams)
|
||||
|
||||
| Export | Purpose |
|
||||
|--------|---------|
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/core",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/core"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -160,7 +160,6 @@ export { defineSchema } from "./schema";
|
||||
// Catalog — defines the vocabulary (what components and actions are available)
|
||||
export type {
|
||||
Catalog,
|
||||
JsonSchemaOptions,
|
||||
PromptOptions,
|
||||
PromptContext,
|
||||
SpecValidationResult,
|
||||
|
||||
@@ -257,7 +257,7 @@ describe("catalog.prompt", () => {
|
||||
expect(prompt).toContain("Keep UIs simple");
|
||||
});
|
||||
|
||||
it("generates chat mode prompt when mode is chat", () => {
|
||||
it("generates inline mode prompt when mode is inline", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
@@ -268,13 +268,13 @@ describe("catalog.prompt", () => {
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const prompt = catalog.prompt({ mode: "chat" });
|
||||
const prompt = catalog.prompt({ mode: "inline" });
|
||||
expect(prompt).toContain("```spec");
|
||||
expect(prompt).toContain("conversationally");
|
||||
expect(prompt).toContain("text + JSONL");
|
||||
});
|
||||
|
||||
it("generates generate mode prompt by default", () => {
|
||||
it("generates standalone mode prompt by default", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
@@ -290,6 +290,38 @@ describe("catalog.prompt", () => {
|
||||
expect(prompt).not.toContain("conversationally");
|
||||
});
|
||||
|
||||
it("accepts deprecated 'chat' as alias for 'inline'", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({}),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||
const chatPrompt = catalog.prompt({ mode: "chat" });
|
||||
expect(chatPrompt).toEqual(inlinePrompt);
|
||||
});
|
||||
|
||||
it("accepts deprecated 'generate' as alias for 'standalone'", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({}),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const standalonePrompt = catalog.prompt({ mode: "standalone" });
|
||||
const generatePrompt = catalog.prompt({ mode: "generate" });
|
||||
expect(generatePrompt).toEqual(standalonePrompt);
|
||||
});
|
||||
|
||||
it("uses actual catalog component names in examples", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
@@ -560,194 +592,6 @@ describe("catalog.jsonSchema", () => {
|
||||
expect(jsonSchema).not.toBeNull();
|
||||
expect(typeof jsonSchema).toBe("object");
|
||||
});
|
||||
|
||||
describe("strict mode (LLM structured output compatible)", () => {
|
||||
function hasNoPropertyNames(obj: unknown): boolean {
|
||||
if (typeof obj !== "object" || obj === null) return true;
|
||||
if ("propertyNames" in obj) return false;
|
||||
return Object.values(obj).every(hasNoPropertyNames);
|
||||
}
|
||||
|
||||
function allObjectsHaveAdditionalPropertiesFalse(obj: unknown): boolean {
|
||||
if (typeof obj !== "object" || obj === null) return true;
|
||||
const record = obj as Record<string, unknown>;
|
||||
if (record.type === "object") {
|
||||
if (record.additionalProperties !== false) return false;
|
||||
}
|
||||
return Object.values(record).every(
|
||||
allObjectsHaveAdditionalPropertiesFalse,
|
||||
);
|
||||
}
|
||||
|
||||
function allObjectPropertiesRequired(obj: unknown): boolean {
|
||||
if (typeof obj !== "object" || obj === null) return true;
|
||||
const record = obj as Record<string, unknown>;
|
||||
if (
|
||||
record.type === "object" &&
|
||||
record.properties &&
|
||||
typeof record.properties === "object"
|
||||
) {
|
||||
const propKeys = Object.keys(record.properties);
|
||||
const required = (record.required as string[]) ?? [];
|
||||
if (!propKeys.every((k) => required.includes(k))) return false;
|
||||
}
|
||||
return Object.values(record).every(allObjectPropertiesRequired);
|
||||
}
|
||||
|
||||
it("sets additionalProperties: false on all nested objects", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Card: {
|
||||
props: z.object({
|
||||
title: z.string(),
|
||||
subtitle: z.string().optional(),
|
||||
}),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const schema = catalog.jsonSchema({ strict: true });
|
||||
expect(allObjectsHaveAdditionalPropertiesFalse(schema)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not emit propertyNames", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({ content: z.string() }),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const schema = catalog.jsonSchema({ strict: true });
|
||||
expect(hasNoPropertyNames(schema)).toBe(true);
|
||||
});
|
||||
|
||||
it("lists all properties in required (optional uses nullable)", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Card: {
|
||||
props: z.object({
|
||||
title: z.string(),
|
||||
subtitle: z.string().optional(),
|
||||
}),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const schema = catalog.jsonSchema({ strict: true });
|
||||
expect(allObjectPropertiesRequired(schema)).toBe(true);
|
||||
});
|
||||
|
||||
it("converts record types without additionalProperties schema value", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({ content: z.string() }),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const schema = catalog.jsonSchema({
|
||||
strict: true,
|
||||
}) as Record<string, unknown>;
|
||||
|
||||
// Walk the schema and ensure no additionalProperties is set to a non-false value
|
||||
function noAdditionalPropertiesSchema(obj: unknown): boolean {
|
||||
if (typeof obj !== "object" || obj === null) return true;
|
||||
const rec = obj as Record<string, unknown>;
|
||||
if (
|
||||
"additionalProperties" in rec &&
|
||||
rec.additionalProperties !== false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return Object.values(rec).every(noAdditionalPropertiesSchema);
|
||||
}
|
||||
expect(noAdditionalPropertiesSchema(schema)).toBe(true);
|
||||
});
|
||||
|
||||
it("wraps optional properties with anyOf nullable", () => {
|
||||
// Use a schema where propsOf resolves to a single component's props
|
||||
// (no record wrapper around the props) so the optional anyOf handling
|
||||
// is directly visible in the JSON Schema output.
|
||||
const flatSchema = defineSchema((s) => ({
|
||||
spec: s.object({
|
||||
component: s.object({
|
||||
type: s.ref("catalog.components"),
|
||||
props: s.propsOf("catalog.components"),
|
||||
}),
|
||||
}),
|
||||
catalog: s.object({
|
||||
components: s.map({
|
||||
props: s.zod(),
|
||||
description: s.string(),
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const catalog = defineCatalog(flatSchema, {
|
||||
components: {
|
||||
Card: {
|
||||
props: z.object({
|
||||
heading: z.string(),
|
||||
caption: z.string().optional(),
|
||||
}),
|
||||
description: "",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const schema = catalog.jsonSchema({ strict: true }) as {
|
||||
properties: {
|
||||
component: {
|
||||
properties: { props: Record<string, unknown> };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const propsSchema = schema.properties.component.properties.props;
|
||||
|
||||
// caption is optional – in strict mode it must be in `required`
|
||||
// and wrapped in anyOf with null
|
||||
const captionSchema = (
|
||||
propsSchema as {
|
||||
properties: { caption: Record<string, unknown> };
|
||||
}
|
||||
).properties.caption;
|
||||
expect(captionSchema).toEqual({
|
||||
anyOf: [{ type: "string" }, { type: "null" }],
|
||||
});
|
||||
|
||||
const propsRequired = (propsSchema as { required: string[] }).required;
|
||||
expect(propsRequired).toContain("heading");
|
||||
expect(propsRequired).toContain("caption");
|
||||
});
|
||||
|
||||
it("does not affect default (non-strict) output", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({ content: z.string() }),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const defaultSchema = catalog.jsonSchema();
|
||||
const defaultSchema2 = catalog.jsonSchema({ strict: false });
|
||||
expect(defaultSchema).toEqual(defaultSchema2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
|
||||
+55
-152
@@ -92,7 +92,7 @@ export interface Catalog<
|
||||
/** Generate system prompt for AI */
|
||||
prompt(options?: PromptOptions): string;
|
||||
/** Export as JSON Schema for structured outputs */
|
||||
jsonSchema(options?: JsonSchemaOptions): object;
|
||||
jsonSchema(): object;
|
||||
/** Validate a spec against this catalog */
|
||||
validate(spec: unknown): SpecValidationResult<InferSpec<TDef, TCatalog>>;
|
||||
/** Get the Zod schema for the spec */
|
||||
@@ -101,28 +101,6 @@ export interface Catalog<
|
||||
readonly _specType: InferSpec<TDef, TCatalog>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for JSON Schema export
|
||||
*/
|
||||
export interface JsonSchemaOptions {
|
||||
/**
|
||||
* When true, produces a strict JSON Schema subset compatible with
|
||||
* LLM structured output APIs (OpenAI, Google Gemini, Anthropic, etc.).
|
||||
* This ensures:
|
||||
* - `additionalProperties: false` on every object
|
||||
* - All object properties listed in `required` (optionals use nullable types)
|
||||
* - Record/map types converted to fixed-key objects
|
||||
*
|
||||
* **Limitation:** Record types (dynamic-key maps) cannot be represented in
|
||||
* strict JSON Schema because `additionalProperties` must be `false`. They
|
||||
* are emitted as `{ type: "object", properties: {}, additionalProperties: false }`.
|
||||
* The LLM prompt (via `catalog.prompt()`) still describes the full structure,
|
||||
* so the model can produce valid output even though the JSON Schema for
|
||||
* record entries is opaque.
|
||||
*/
|
||||
strict?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt generation options
|
||||
*/
|
||||
@@ -134,11 +112,14 @@ export interface PromptOptions {
|
||||
/**
|
||||
* Output mode for the generated prompt.
|
||||
*
|
||||
* - `"generate"` (default): The LLM should output only JSONL patches (no prose).
|
||||
* - `"chat"`: The LLM should respond conversationally first, then output JSONL patches.
|
||||
* - `"standalone"` (default): The LLM should output only JSONL patches (no prose).
|
||||
* - `"inline"`: The LLM should respond conversationally first, then output JSONL patches.
|
||||
* Includes rules about interleaving text with JSONL and not wrapping in code fences.
|
||||
*
|
||||
* @deprecated `"generate"` — use `"standalone"` instead.
|
||||
* @deprecated `"chat"` — use `"inline"` instead.
|
||||
*/
|
||||
mode?: "generate" | "chat";
|
||||
mode?: "standalone" | "inline" | "generate" | "chat";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -433,8 +414,8 @@ function createCatalogFromSchema<TDef extends SchemaDefinition, TCatalog>(
|
||||
return generatePrompt(this, options);
|
||||
},
|
||||
|
||||
jsonSchema(options: JsonSchemaOptions = {}): object {
|
||||
return zodToJsonSchema(zodSchema, options.strict ?? false);
|
||||
jsonSchema(): object {
|
||||
return zodToJsonSchema(zodSchema);
|
||||
},
|
||||
|
||||
validate(spec: unknown): SpecValidationResult<InferSpec<TDef, TCatalog>> {
|
||||
@@ -585,15 +566,28 @@ function generatePrompt<TDef extends SchemaDefinition, TCatalog>(
|
||||
const {
|
||||
system = "You are a UI generator that outputs JSON.",
|
||||
customRules = [],
|
||||
mode = "generate",
|
||||
mode: rawMode = "standalone",
|
||||
} = options;
|
||||
|
||||
const mode: "standalone" | "inline" =
|
||||
rawMode === "chat"
|
||||
? (console.warn(
|
||||
'[json-render] mode "chat" is deprecated, use "inline" instead',
|
||||
),
|
||||
"inline")
|
||||
: rawMode === "generate"
|
||||
? (console.warn(
|
||||
'[json-render] mode "generate" is deprecated, use "standalone" instead',
|
||||
),
|
||||
"standalone")
|
||||
: rawMode;
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push(system);
|
||||
lines.push("");
|
||||
|
||||
// Output format section - explain JSONL streaming patch format
|
||||
if (mode === "chat") {
|
||||
if (mode === "inline") {
|
||||
lines.push("OUTPUT FORMAT (text + JSONL, RFC 6902 JSON Patch):");
|
||||
lines.push(
|
||||
"You respond conversationally. When generating UI, first write a brief explanation (1-3 sentences), then output JSONL patch lines wrapped in a ```spec code fence.",
|
||||
@@ -1036,7 +1030,7 @@ Note: state patches appear right after the elements that use them, so the UI fil
|
||||
// Rules
|
||||
lines.push("RULES:");
|
||||
const baseRules =
|
||||
mode === "chat"
|
||||
mode === "inline"
|
||||
? [
|
||||
"When generating UI, wrap all JSONL patches in a ```spec code fence - one JSON object per line inside the fence",
|
||||
"Write a brief conversational response before any JSONL output",
|
||||
@@ -1325,111 +1319,44 @@ function formatZodType(schema: z.ZodType): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the Zod type name from a schema's internal definition.
|
||||
* Supports both Zod 3 (`_def.typeName`) and Zod 4 (`_def.type`).
|
||||
* Convert Zod schema to JSON Schema
|
||||
*/
|
||||
function zodTypeName(def: Record<string, unknown>): string {
|
||||
// Zod 4 uses _def.type as a plain string (e.g. "string", "object")
|
||||
if (typeof def.type === "string") return def.type;
|
||||
// Zod 3 uses _def.typeName (e.g. "ZodString", "ZodObject")
|
||||
if (typeof def.typeName === "string") return def.typeName;
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise a Zod type name to a canonical lowercase form.
|
||||
* Handles both Zod 3 ("ZodString") and Zod 4 ("string") conventions.
|
||||
*/
|
||||
function normalizeTypeName(raw: string): string {
|
||||
// Zod 3 names start with "Zod", e.g. "ZodString" → "string"
|
||||
if (raw.startsWith("Zod")) {
|
||||
return raw.slice(3).toLowerCase();
|
||||
}
|
||||
return raw.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Zod schema to JSON Schema.
|
||||
*
|
||||
* When `strict` is true the output conforms to the JSON Schema subset required
|
||||
* by LLM structured output APIs (no `propertyNames`, `additionalProperties: false`
|
||||
* everywhere, all properties listed in `required`).
|
||||
*/
|
||||
function zodToJsonSchema(schema: z.ZodType, strict = false): object {
|
||||
function zodToJsonSchema(schema: z.ZodType): object {
|
||||
// Simplified JSON Schema conversion
|
||||
const def = schema._def as unknown as Record<string, unknown>;
|
||||
const kind = normalizeTypeName(zodTypeName(def));
|
||||
const typeName = (def.typeName as string) ?? "";
|
||||
|
||||
switch (kind) {
|
||||
case "string":
|
||||
switch (typeName) {
|
||||
case "ZodString":
|
||||
return { type: "string" };
|
||||
case "number":
|
||||
case "ZodNumber":
|
||||
return { type: "number" };
|
||||
case "boolean":
|
||||
case "ZodBoolean":
|
||||
return { type: "boolean" };
|
||||
case "literal": {
|
||||
// Zod 4: _def.values (array), Zod 3: _def.value (single)
|
||||
const values = def.values as unknown[] | undefined;
|
||||
const value = values ? values[0] : def.value;
|
||||
return { const: value };
|
||||
}
|
||||
case "enum": {
|
||||
// Zod 4: _def.entries (object { a:"a", b:"b" }), Zod 3: _def.values (string[])
|
||||
const entries = def.entries as Record<string, string> | undefined;
|
||||
const values = entries
|
||||
? Object.values(entries)
|
||||
: (def.values as string[] | undefined);
|
||||
return { enum: values ?? [] };
|
||||
}
|
||||
case "array": {
|
||||
// Zod 4: _def.element, Zod 3: _def.type
|
||||
const inner = (def.element ?? def.type) as z.ZodType | undefined;
|
||||
case "ZodLiteral":
|
||||
return { const: def.value };
|
||||
case "ZodEnum":
|
||||
return { enum: def.values };
|
||||
case "ZodArray": {
|
||||
const inner = def.type as z.ZodType | undefined;
|
||||
return {
|
||||
type: "array",
|
||||
items: inner ? zodToJsonSchema(inner, strict) : {},
|
||||
items: inner ? zodToJsonSchema(inner) : {},
|
||||
};
|
||||
}
|
||||
case "object": {
|
||||
// Zod 4: _def.shape is an object, Zod 3: _def.shape is a function
|
||||
const rawShape = def.shape;
|
||||
const shape: Record<string, z.ZodType> | undefined =
|
||||
typeof rawShape === "function"
|
||||
? (rawShape as () => Record<string, z.ZodType>)()
|
||||
: (rawShape as Record<string, z.ZodType> | undefined);
|
||||
|
||||
if (!shape) {
|
||||
if (strict) {
|
||||
return {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: [],
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
return { type: "object" };
|
||||
}
|
||||
|
||||
case "ZodObject": {
|
||||
const shape = (def.shape as () => Record<string, z.ZodType>)?.();
|
||||
if (!shape) return { type: "object" };
|
||||
const properties: Record<string, object> = {};
|
||||
const required: string[] = [];
|
||||
for (const [key, value] of Object.entries(shape)) {
|
||||
properties[key] = zodToJsonSchema(value);
|
||||
const innerDef = value._def as unknown as Record<string, unknown>;
|
||||
const innerKind = normalizeTypeName(zodTypeName(innerDef));
|
||||
const isOptional = innerKind === "optional" || innerKind === "nullable";
|
||||
|
||||
if (strict) {
|
||||
// In strict mode, all properties must be in required.
|
||||
// Optional properties are represented as nullable types.
|
||||
if (
|
||||
innerDef.typeName !== "ZodOptional" &&
|
||||
innerDef.typeName !== "ZodNullable"
|
||||
) {
|
||||
required.push(key);
|
||||
if (isOptional) {
|
||||
const unwrapped = zodToJsonSchema(value, strict);
|
||||
properties[key] = { anyOf: [unwrapped, { type: "null" }] };
|
||||
} else {
|
||||
properties[key] = zodToJsonSchema(value, strict);
|
||||
}
|
||||
} else {
|
||||
properties[key] = zodToJsonSchema(value);
|
||||
if (!isOptional) {
|
||||
required.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -1439,47 +1366,23 @@ function zodToJsonSchema(schema: z.ZodType, strict = false): object {
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
case "record": {
|
||||
case "ZodRecord": {
|
||||
const valueType = def.valueType as z.ZodType | undefined;
|
||||
if (strict) {
|
||||
// LLM strict schemas require `additionalProperties: false` and do not
|
||||
// permit a schema value for `additionalProperties`. Since record types
|
||||
// have dynamic keys that cannot be enumerated at schema-generation time,
|
||||
// we emit an opaque object. The LLM prompt still describes the expected
|
||||
// structure so the model can produce valid output.
|
||||
return {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: [],
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: "object",
|
||||
additionalProperties: valueType ? zodToJsonSchema(valueType) : true,
|
||||
};
|
||||
}
|
||||
case "optional":
|
||||
case "nullable": {
|
||||
case "ZodOptional":
|
||||
case "ZodNullable": {
|
||||
const inner = def.innerType as z.ZodType | undefined;
|
||||
return inner ? zodToJsonSchema(inner, strict) : {};
|
||||
return inner ? zodToJsonSchema(inner) : {};
|
||||
}
|
||||
case "union": {
|
||||
case "ZodUnion": {
|
||||
const options = def.options as z.ZodType[] | undefined;
|
||||
return options
|
||||
? { anyOf: options.map((o) => zodToJsonSchema(o, strict)) }
|
||||
: {};
|
||||
return options ? { anyOf: options.map(zodToJsonSchema) } : {};
|
||||
}
|
||||
case "any":
|
||||
case "unknown":
|
||||
if (strict) {
|
||||
return {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: [],
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
case "ZodAny":
|
||||
return {};
|
||||
default:
|
||||
return {};
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/image
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/image",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Image renderer for @json-render/core. JSON becomes SVG and PNG images via Satori.",
|
||||
"keywords": [
|
||||
@@ -20,7 +20,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/image"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/jotai
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/jotai",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Jotai adapter for json-render StateStore",
|
||||
"keywords": [
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/jotai"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @json-render/mcp
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 54a1ecf: Rename generation modes and fix MCP React duplicate module error.
|
||||
|
||||
### Changed:
|
||||
- **`@json-render/core`** — Renamed generation modes from `"generate"` / `"chat"` to `"standalone"` / `"inline"`. The old names still work but emit a deprecation warning.
|
||||
|
||||
### Fixed:
|
||||
- **`@json-render/mcp`** — Resolved React duplicate module error (`useRef` returning null) by adding `resolve.dedupe` Vite configuration. Added `./build-app-html` export entry point.
|
||||
|
||||
### Other:
|
||||
- Updated `homepage` URLs across all packages to point to `https://json-render.dev`.
|
||||
- Reorganized skills directory structure for cleaner naming.
|
||||
- Added skills documentation page to the web app.
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/mcp",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "MCP Apps integration for @json-render/core. Serve json-render UIs as interactive MCP Apps in Claude, ChatGPT, Cursor, and VS Code.",
|
||||
"keywords": [
|
||||
@@ -22,7 +22,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/mcp"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/react-email
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react-email",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React Email renderer for @json-render/core. JSON becomes HTML emails.",
|
||||
"keywords": [
|
||||
@@ -18,7 +18,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/react-email"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/react-native
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react-native",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/react-native"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/react-pdf
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react-pdf",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React PDF renderer for @json-render/core. JSON becomes PDF documents.",
|
||||
"keywords": [
|
||||
@@ -18,7 +18,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/react-pdf"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @internal/react-state
|
||||
|
||||
## 0.8.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.8.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@internal/react-state",
|
||||
"version": "0.8.5",
|
||||
"version": "0.8.6",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "Shared React state context for json-render renderer packages",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/react
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/react"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/redux
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/redux",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Redux adapter for json-render StateStore",
|
||||
"keywords": [
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/redux"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/remotion
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/remotion",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/remotion"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @json-render/shadcn
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/shadcn",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "shadcn/ui component library for @json-render/core. JSON becomes beautiful Tailwind-styled React components.",
|
||||
"keywords": [
|
||||
@@ -22,7 +22,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/shadcn"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/svelte
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/svelte",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Svelte 5 renderer for @json-render/core. JSON becomes Svelte components.",
|
||||
"keywords": [
|
||||
@@ -21,7 +21,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/svelte"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/vue
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/vue",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Vue renderer for @json-render/core. JSON becomes Vue components.",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/vue"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/xstate
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/xstate",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "XState Store adapter for json-render StateStore",
|
||||
"keywords": [
|
||||
@@ -15,7 +15,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/xstate"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @json-render/zustand
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/zustand",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Zustand adapter for json-render StateStore",
|
||||
"keywords": [
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/zustand"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user