mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-04 04:48:17 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caa90b8b84 | ||
|
|
54a1ecf817 | ||
|
|
dc9e9d17f3 | ||
|
|
1977fb3a11 |
+3
-11
@@ -1,16 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"json-render-react": {
|
||||
"json-render": {
|
||||
"command": "npx",
|
||||
"args": ["tsx", "examples/mcp-react/server.ts", "--stdio"]
|
||||
},
|
||||
"json-render-vue": {
|
||||
"command": "npx",
|
||||
"args": ["tsx", "examples/mcp-vue/server.ts", "--stdio"]
|
||||
},
|
||||
"json-render-svelte": {
|
||||
"command": "npx",
|
||||
"args": ["tsx", "examples/mcp-svelte/server.ts", "--stdio"]
|
||||
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@
|
||||
"json-render": {
|
||||
"type": "stdio",
|
||||
"command": "npx",
|
||||
"args": ["tsx", "examples/mcp-react/server.ts", "--stdio"]
|
||||
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,7 +486,6 @@ pnpm dev
|
||||
- Chat Example: run `pnpm dev` in `examples/chat`
|
||||
- Svelte Example: run `pnpm dev` in `examples/svelte` or `examples/svelte-chat`
|
||||
- Vue Example: run `pnpm dev` in `examples/vue`
|
||||
- MCP Vue Example: run `pnpm build && pnpm start:stdio` in `examples/mcp-vue`
|
||||
- Vite Renderers (React + Vue + Svelte): run `pnpm dev` in `examples/vite-renderers`
|
||||
- React Native example: run `npx expo start` in `examples/react-native`
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -11,20 +11,13 @@ MCP Apps integration for json-render. Serve json-render UIs as interactive [MCP
|
||||
npm install @json-render/mcp @json-render/core @modelcontextprotocol/sdk
|
||||
```
|
||||
|
||||
For the iframe-side UI, install the renderer for your framework:
|
||||
For the iframe-side React UI, also install:
|
||||
|
||||
```bash
|
||||
# React
|
||||
npm install @json-render/react react react-dom
|
||||
|
||||
# Vue
|
||||
npm install @json-render/vue vue
|
||||
|
||||
# Svelte
|
||||
npm install @json-render/svelte svelte
|
||||
```
|
||||
|
||||
See the [MCP React example](https://github.com/vercel-labs/json-render/tree/main/examples/mcp-react) for a full working example.
|
||||
See the [MCP example](https://github.com/vercel-labs/json-render/tree/main/examples/mcp) for a full working example.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -124,22 +117,16 @@ registerJsonRenderResource(server, {
|
||||
});
|
||||
```
|
||||
|
||||
## Client API
|
||||
## Client API (`@json-render/mcp/app`)
|
||||
|
||||
These exports run inside the sandboxed iframe rendered by the MCP host. Framework-specific adapters are available at:
|
||||
These exports run inside the sandboxed iframe rendered by the MCP host.
|
||||
|
||||
- `@json-render/mcp/app/react` -- React hook
|
||||
- `@json-render/mcp/app/vue` -- Vue composable
|
||||
- `@json-render/mcp/app/svelte` -- Svelte stores
|
||||
|
||||
The React hook is also re-exported from `@json-render/mcp/app` for backward compatibility.
|
||||
|
||||
### React -- useJsonRenderApp (`@json-render/mcp/app/react`)
|
||||
### useJsonRenderApp
|
||||
|
||||
React hook that connects to the MCP host, listens for tool results, and maintains the current json-render spec.
|
||||
|
||||
```tsx
|
||||
import { useJsonRenderApp } from "@json-render/mcp/app/react";
|
||||
import { useJsonRenderApp } from "@json-render/mcp/app";
|
||||
import { JSONUIProvider, Renderer } from "@json-render/react";
|
||||
|
||||
function McpAppView({ registry }) {
|
||||
@@ -159,51 +146,7 @@ function McpAppView({ registry }) {
|
||||
}
|
||||
```
|
||||
|
||||
### Vue -- useJsonRenderApp (`@json-render/mcp/app/vue`)
|
||||
|
||||
Vue composable with the same API shape. Values are Vue refs and computed properties.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useJsonRenderApp } from "@json-render/mcp/app/vue";
|
||||
import { Renderer } from "@json-render/vue";
|
||||
|
||||
const { spec, loading, error } = useJsonRenderApp({ name: "my-app" });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="error">Error: {{ error.message }}</div>
|
||||
<div v-else-if="!spec">Waiting...</div>
|
||||
<Renderer v-else :spec="spec" :registry="registry" :loading="loading" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### Svelte -- createJsonRenderApp (`@json-render/mcp/app/svelte`)
|
||||
|
||||
Creates a json-render MCP App client using Svelte stores. Call `destroy()` in `onDestroy` to clean up.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { createJsonRenderApp } from "@json-render/mcp/app/svelte";
|
||||
import { Renderer } from "@json-render/svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
const { spec, loading, error, destroy } = createJsonRenderApp({ name: "my-app" });
|
||||
onDestroy(destroy);
|
||||
</script>
|
||||
|
||||
{#if $error}
|
||||
<div>Error: {$error.message}</div>
|
||||
{:else if !$spec}
|
||||
<div>Waiting...</div>
|
||||
{:else}
|
||||
<Renderer spec={$spec} {registry} loading={$loading} />
|
||||
{/if}
|
||||
```
|
||||
|
||||
### Return values
|
||||
|
||||
All three adapters return the same logical state:
|
||||
#### UseJsonRenderAppReturn
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
@@ -252,11 +195,9 @@ All three adapters return the same logical state:
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
In React, values are plain primitives. In Vue, they are `Ref`/`ShallowRef`/`ComputedRef`. In Svelte, they are readable stores (use `$` prefix to subscribe). The Svelte adapter also returns a `destroy` function for cleanup.
|
||||
### buildAppHtml
|
||||
|
||||
### buildAppHtml (`@json-render/mcp/app`)
|
||||
|
||||
Generate a self-contained HTML page from bundled JavaScript and CSS. Framework-agnostic.
|
||||
Generate a self-contained HTML page from bundled JavaScript and CSS.
|
||||
|
||||
```typescript
|
||||
import { buildAppHtml } from "@json-render/mcp/app";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -98,7 +98,7 @@ export const docsNavigation: NavSection[] = [
|
||||
},
|
||||
{
|
||||
title: "MCP App",
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/mcp-react",
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/mcp",
|
||||
external: true,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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.",
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -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,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; img-src * data: blob:; font-src * data:; connect-src *; media-src *;" />
|
||||
<title>json-render MCP App (Svelte)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "example-mcp-svelte",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"start": "tsx server.ts --stdio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@json-render/core": "workspace:*",
|
||||
"@json-render/mcp": "workspace:*",
|
||||
"@json-render/svelte": "workspace:*",
|
||||
"@modelcontextprotocol/ext-apps": "^1.2.0",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"svelte": "^5.49.2",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-singlefile": "^2.3.0"
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { createMcpApp } from "@json-render/mcp";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { catalog } from "./src/catalog.js";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function loadHtml(): string {
|
||||
const htmlPath = path.join(__dirname, "dist", "index.html");
|
||||
if (!fs.existsSync(htmlPath)) {
|
||||
throw new Error(
|
||||
`Built HTML not found at ${htmlPath}. Run 'pnpm build' first.`,
|
||||
);
|
||||
}
|
||||
return fs.readFileSync(htmlPath, "utf-8");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const html = loadHtml();
|
||||
const server = await createMcpApp({
|
||||
name: "json-render Svelte Example",
|
||||
version: "1.0.0",
|
||||
catalog,
|
||||
html,
|
||||
});
|
||||
await server.connect(new StdioServerTransport());
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createJsonRenderApp } from "@json-render/mcp/app/svelte";
|
||||
import { JsonUIProvider, Renderer } from "@json-render/svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import { registry } from "./registry";
|
||||
|
||||
const { spec, loading, connecting, error, destroy } = createJsonRenderApp({
|
||||
name: "json-render-mcp-svelte",
|
||||
version: "1.0.0",
|
||||
});
|
||||
onDestroy(destroy);
|
||||
</script>
|
||||
|
||||
{#if $error}
|
||||
<div class="p-4 text-destructive font-mono text-sm">
|
||||
{$error.message}
|
||||
</div>
|
||||
{:else if !$spec}
|
||||
<div class="p-4 text-muted-foreground text-sm">
|
||||
{$connecting ? "Connecting to host..." : "Waiting for UI spec..."}
|
||||
</div>
|
||||
{:else}
|
||||
<JsonUIProvider initialState={$spec.state ?? {}}>
|
||||
<Renderer spec={$spec} {registry} loading={$loading} />
|
||||
</JsonUIProvider>
|
||||
{/if}
|
||||
@@ -1,54 +0,0 @@
|
||||
import { schema } from "@json-render/svelte/schema";
|
||||
import { z } from "zod";
|
||||
|
||||
export const catalog = schema.createCatalog({
|
||||
components: {
|
||||
Stack: {
|
||||
props: z.object({
|
||||
gap: z.number().optional(),
|
||||
padding: z.number().optional(),
|
||||
direction: z.enum(["vertical", "horizontal"]).optional(),
|
||||
align: z.enum(["start", "center", "end"]).optional(),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description:
|
||||
"Layout container that stacks children vertically or horizontally",
|
||||
},
|
||||
Card: {
|
||||
props: z.object({
|
||||
title: z.string().optional(),
|
||||
subtitle: z.string().optional(),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description: "A card container with optional title and subtitle",
|
||||
},
|
||||
Text: {
|
||||
props: z.object({
|
||||
content: z.string(),
|
||||
size: z.enum(["sm", "md", "lg", "xl"]).optional(),
|
||||
weight: z.enum(["normal", "medium", "bold"]).optional(),
|
||||
color: z.string().optional(),
|
||||
}),
|
||||
slots: [],
|
||||
description: "Displays a text string",
|
||||
},
|
||||
Button: {
|
||||
props: z.object({
|
||||
label: z.string(),
|
||||
variant: z.enum(["primary", "secondary", "danger"]).optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
}),
|
||||
slots: [],
|
||||
description: "A clickable button that emits a 'press' event",
|
||||
},
|
||||
Badge: {
|
||||
props: z.object({
|
||||
label: z.string(),
|
||||
color: z.string().optional(),
|
||||
}),
|
||||
slots: [],
|
||||
description: "A small badge/tag label",
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
label: string;
|
||||
color?: string;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-3 py-1 text-xs font-medium bg-secondary text-secondary-foreground border border-border"
|
||||
style:background-color={props.color ? `${props.color}20` : undefined}
|
||||
style:color={props.color ?? undefined}
|
||||
style:border-color={props.color ? `${props.color}40` : undefined}>
|
||||
{props.label}
|
||||
</span>
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
label: string;
|
||||
variant?: "primary" | "secondary" | "danger";
|
||||
disabled?: boolean;
|
||||
}> {}
|
||||
|
||||
let { props, emit }: Props = $props();
|
||||
|
||||
const variantClasses: Record<string, string> = {
|
||||
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
danger: "bg-destructive/10 text-destructive hover:bg-destructive/20 border border-destructive/20",
|
||||
};
|
||||
|
||||
let variant = $derived(props.variant ?? "primary");
|
||||
</script>
|
||||
|
||||
<button
|
||||
disabled={props.disabled}
|
||||
onclick={() => emit("press")}
|
||||
class="inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium transition-colors {variantClasses[variant] ?? variantClasses.primary} {props.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}">
|
||||
{props.label}
|
||||
</button>
|
||||
@@ -1,29 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
}> {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { props, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="rounded-xl border border-border bg-card text-card-foreground p-5 shadow-sm">
|
||||
{#if props.title}
|
||||
<h2 class="text-base font-semibold text-card-foreground mb-1">
|
||||
{props.title}
|
||||
</h2>
|
||||
{/if}
|
||||
{#if props.subtitle}
|
||||
<p class="text-sm text-muted-foreground mb-3">
|
||||
{props.subtitle}
|
||||
</p>
|
||||
{/if}
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,33 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
gap?: number;
|
||||
padding?: number;
|
||||
direction?: "vertical" | "horizontal";
|
||||
align?: "start" | "center" | "end";
|
||||
}> {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { props, children }: Props = $props();
|
||||
let horizontal = $derived(props.direction === "horizontal");
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex"
|
||||
style:flex-direction={horizontal ? "row" : "column"}
|
||||
style:gap="{props.gap ?? 0}px"
|
||||
style:padding="{props.padding ?? 0}px"
|
||||
style:align-items={props.align === "start"
|
||||
? "flex-start"
|
||||
: props.align === "end"
|
||||
? "flex-end"
|
||||
: horizontal
|
||||
? "center"
|
||||
: "stretch"}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
content: string;
|
||||
size?: "sm" | "md" | "lg" | "xl";
|
||||
weight?: "normal" | "medium" | "bold";
|
||||
color?: string;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
|
||||
const sizeClasses: Record<string, string> = {
|
||||
sm: "text-xs",
|
||||
md: "text-sm",
|
||||
lg: "text-base",
|
||||
xl: "text-2xl",
|
||||
};
|
||||
|
||||
const weightClasses: Record<string, string> = {
|
||||
normal: "font-normal",
|
||||
medium: "font-medium",
|
||||
bold: "font-bold",
|
||||
};
|
||||
</script>
|
||||
|
||||
<span
|
||||
class="{sizeClasses[props.size ?? 'md'] ?? 'text-sm'} {weightClasses[props.weight ?? 'normal'] ?? 'font-normal'} text-foreground"
|
||||
style:color={props.color ?? undefined}>
|
||||
{String(props.content ?? "")}
|
||||
</span>
|
||||
@@ -1,98 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@custom-variant dark (&:is([data-theme="dark"] *));
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-2xl: calc(var(--radius) + 8px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--background: var(--color-background-secondary, var(--vscode-editor-background, oklch(0.145 0 0)));
|
||||
--foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||
--card: var(--color-background-primary, var(--vscode-sideBar-background, oklch(0.205 0 0)));
|
||||
--card-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||
--primary: var(--color-text-primary, var(--vscode-foreground, oklch(0.922 0 0)));
|
||||
--primary-foreground: var(--color-background-primary, var(--vscode-sideBar-background, oklch(0.205 0 0)));
|
||||
--secondary: var(--color-background-tertiary, var(--vscode-activityBar-background, oklch(0.269 0 0)));
|
||||
--secondary-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||
--muted: var(--color-background-tertiary, var(--vscode-activityBar-background, oklch(0.269 0 0)));
|
||||
--muted-foreground: var(--color-text-secondary, var(--vscode-descriptionForeground, oklch(0.708 0 0)));
|
||||
--accent: var(--color-background-tertiary, var(--vscode-activityBar-background, oklch(0.269 0 0)));
|
||||
--accent-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||
--destructive: var(--color-text-danger, var(--vscode-errorForeground, oklch(0.704 0.191 22.216)));
|
||||
--border: var(--color-border-primary, var(--vscode-widget-border, oklch(1 0 0 / 10%)));
|
||||
--input: var(--color-border-secondary, var(--vscode-editorWidget-border, oklch(1 0 0 / 15%)));
|
||||
--ring: var(--color-ring-primary, var(--vscode-focusBorder, oklch(0.556 0 0)));
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
html, body, #app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
font-family: var(--vscode-font-family, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply text-foreground;
|
||||
background-color: var(--color-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import "./globals.css";
|
||||
import { mount } from "svelte";
|
||||
import App from "./App.svelte";
|
||||
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
document.documentElement.setAttribute(
|
||||
"data-theme",
|
||||
prefersDark ? "dark" : "light",
|
||||
);
|
||||
document.documentElement.style.colorScheme = prefersDark ? "dark" : "light";
|
||||
|
||||
mount(App, { target: document.getElementById("app")! });
|
||||
@@ -1,18 +0,0 @@
|
||||
import { type Components, defineRegistry } from "@json-render/svelte";
|
||||
import { catalog } from "./catalog";
|
||||
|
||||
import Stack from "./components/Stack.svelte";
|
||||
import Card from "./components/Card.svelte";
|
||||
import Text from "./components/Text.svelte";
|
||||
import Button from "./components/Button.svelte";
|
||||
import Badge from "./components/Badge.svelte";
|
||||
|
||||
const components: Components<typeof catalog> = {
|
||||
Stack,
|
||||
Card,
|
||||
Text,
|
||||
Button,
|
||||
Badge,
|
||||
};
|
||||
|
||||
export const { registry } = defineRegistry(catalog, { components });
|
||||
@@ -1 +0,0 @@
|
||||
export default {};
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src", "server.ts"]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { viteSingleFile } from "vite-plugin-singlefile";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [svelte(), viteSingleFile()],
|
||||
build: {
|
||||
outDir: "dist",
|
||||
emptyOutDir: false,
|
||||
rollupOptions: {
|
||||
input: "index.html",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; img-src * data: blob:; font-src * data:; connect-src *; media-src *;" />
|
||||
<title>json-render MCP App (Vue)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "example-mcp-vue",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"start": "tsx server.ts",
|
||||
"start:stdio": "tsx server.ts --stdio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@json-render/core": "workspace:*",
|
||||
"@json-render/mcp": "workspace:*",
|
||||
"@json-render/vue": "workspace:*",
|
||||
"@modelcontextprotocol/ext-apps": "^1.2.0",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"vue": "^3.5.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-singlefile": "^2.3.0"
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { createMcpApp } from "@json-render/mcp";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { catalog } from "./src/catalog.js";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function loadHtml(): string {
|
||||
const htmlPath = path.join(__dirname, "dist", "index.html");
|
||||
if (!fs.existsSync(htmlPath)) {
|
||||
throw new Error(
|
||||
`Built HTML not found at ${htmlPath}. Run 'pnpm build' first.`,
|
||||
);
|
||||
}
|
||||
return fs.readFileSync(htmlPath, "utf-8");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const html = loadHtml();
|
||||
const server = await createMcpApp({
|
||||
name: "json-render Vue Example",
|
||||
version: "1.0.0",
|
||||
catalog,
|
||||
html,
|
||||
});
|
||||
await server.connect(new StdioServerTransport());
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useJsonRenderApp } from "@json-render/mcp/app/vue";
|
||||
import { Renderer, defineRegistry, JSONUIProvider } from "@json-render/vue";
|
||||
import { catalog } from "./catalog";
|
||||
import { components } from "./registry";
|
||||
|
||||
const { registry } = defineRegistry(catalog, { components });
|
||||
|
||||
const { spec, loading, connecting, error } = useJsonRenderApp({
|
||||
name: "json-render-mcp-vue",
|
||||
version: "1.0.0",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="error"
|
||||
class="p-4 font-mono text-sm text-destructive"
|
||||
>
|
||||
{{ error.message }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!spec"
|
||||
class="p-4 text-sm text-muted-foreground"
|
||||
>
|
||||
{{ connecting ? "Connecting to host..." : "Waiting for UI spec..." }}
|
||||
</div>
|
||||
<JSONUIProvider v-else :registry="registry" :initialState="spec.state ?? {}">
|
||||
<Renderer :spec="spec" :registry="registry" :loading="loading" />
|
||||
</JSONUIProvider>
|
||||
</template>
|
||||
@@ -1,56 +0,0 @@
|
||||
import { schema } from "@json-render/vue/schema";
|
||||
import { z } from "zod";
|
||||
|
||||
export const catalog = schema.createCatalog({
|
||||
components: {
|
||||
Stack: {
|
||||
props: z.object({
|
||||
gap: z.number().optional(),
|
||||
padding: z.number().optional(),
|
||||
direction: z.enum(["vertical", "horizontal"]).optional(),
|
||||
align: z.enum(["start", "center", "end"]).optional(),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description:
|
||||
"Layout container that stacks children vertically or horizontally",
|
||||
},
|
||||
Card: {
|
||||
props: z.object({
|
||||
title: z.string().optional(),
|
||||
subtitle: z.string().optional(),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description: "A card container with optional title and subtitle",
|
||||
},
|
||||
Text: {
|
||||
props: z.object({
|
||||
content: z.string(),
|
||||
size: z.enum(["sm", "md", "lg", "xl"]).optional(),
|
||||
weight: z.enum(["normal", "medium", "bold"]).optional(),
|
||||
color: z.string().optional(),
|
||||
}),
|
||||
slots: [],
|
||||
description: "Displays a text string",
|
||||
},
|
||||
Button: {
|
||||
props: z.object({
|
||||
label: z.string(),
|
||||
variant: z.enum(["primary", "secondary", "danger"]).optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
}),
|
||||
slots: [],
|
||||
description: "A clickable button that emits a 'press' event",
|
||||
},
|
||||
Badge: {
|
||||
props: z.object({
|
||||
label: z.string(),
|
||||
color: z.string().optional(),
|
||||
}),
|
||||
slots: [],
|
||||
description: "A small badge/tag label",
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
|
||||
export type AppCatalog = typeof catalog;
|
||||
@@ -1,98 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@custom-variant dark (&:is([data-theme="dark"] *));
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-2xl: calc(var(--radius) + 8px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--background: var(--color-background-secondary, var(--vscode-editor-background, oklch(0.145 0 0)));
|
||||
--foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||
--card: var(--color-background-primary, var(--vscode-sideBar-background, oklch(0.205 0 0)));
|
||||
--card-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||
--primary: var(--color-text-primary, var(--vscode-foreground, oklch(0.922 0 0)));
|
||||
--primary-foreground: var(--color-background-primary, var(--vscode-sideBar-background, oklch(0.205 0 0)));
|
||||
--secondary: var(--color-background-tertiary, var(--vscode-activityBar-background, oklch(0.269 0 0)));
|
||||
--secondary-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||
--muted: var(--color-background-tertiary, var(--vscode-activityBar-background, oklch(0.269 0 0)));
|
||||
--muted-foreground: var(--color-text-secondary, var(--vscode-descriptionForeground, oklch(0.708 0 0)));
|
||||
--accent: var(--color-background-tertiary, var(--vscode-activityBar-background, oklch(0.269 0 0)));
|
||||
--accent-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||
--destructive: var(--color-text-danger, var(--vscode-errorForeground, oklch(0.704 0.191 22.216)));
|
||||
--border: var(--color-border-primary, var(--vscode-widget-border, oklch(1 0 0 / 10%)));
|
||||
--input: var(--color-border-secondary, var(--vscode-editorWidget-border, oklch(1 0 0 / 15%)));
|
||||
--ring: var(--color-ring-primary, var(--vscode-focusBorder, oklch(0.556 0 0)));
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
html, body, #app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
font-family: var(--vscode-font-family, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply text-foreground;
|
||||
background-color: var(--color-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import "./globals.css";
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
// Fallback theme before MCP host provides context
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
document.documentElement.setAttribute(
|
||||
"data-theme",
|
||||
prefersDark ? "dark" : "light",
|
||||
);
|
||||
document.documentElement.style.colorScheme = prefersDark ? "dark" : "light";
|
||||
|
||||
createApp(App).mount("#app");
|
||||
@@ -1,116 +0,0 @@
|
||||
import { h } from "vue";
|
||||
import type { Components } from "@json-render/vue";
|
||||
import type { AppCatalog } from "./catalog";
|
||||
|
||||
export const components: Components<AppCatalog> = {
|
||||
Stack: ({ props, children }) => {
|
||||
const horizontal = props.direction === "horizontal";
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "flex",
|
||||
style: {
|
||||
flexDirection: horizontal ? "row" : "column",
|
||||
gap: props.gap ? `${props.gap}px` : undefined,
|
||||
padding: props.padding ? `${props.padding}px` : undefined,
|
||||
alignItems: props.align ?? (horizontal ? "center" : "stretch"),
|
||||
},
|
||||
},
|
||||
children,
|
||||
);
|
||||
},
|
||||
|
||||
Card: ({ props, children }) =>
|
||||
h(
|
||||
"div",
|
||||
{
|
||||
class:
|
||||
"rounded-xl border border-border bg-card text-card-foreground p-5 shadow-sm",
|
||||
},
|
||||
[
|
||||
props.title &&
|
||||
h(
|
||||
"h2",
|
||||
{
|
||||
class: "text-base font-semibold text-card-foreground mb-1",
|
||||
},
|
||||
props.title,
|
||||
),
|
||||
props.subtitle &&
|
||||
h(
|
||||
"p",
|
||||
{
|
||||
class: "text-sm text-muted-foreground mb-3",
|
||||
},
|
||||
props.subtitle,
|
||||
),
|
||||
children,
|
||||
],
|
||||
),
|
||||
|
||||
Text: ({ props }) => {
|
||||
const sizeClasses: Record<string, string> = {
|
||||
sm: "text-xs",
|
||||
md: "text-sm",
|
||||
lg: "text-base",
|
||||
xl: "text-2xl",
|
||||
};
|
||||
const weightClasses: Record<string, string> = {
|
||||
normal: "font-normal",
|
||||
medium: "font-medium",
|
||||
bold: "font-bold",
|
||||
};
|
||||
return h(
|
||||
"span",
|
||||
{
|
||||
class: [
|
||||
sizeClasses[props.size ?? "md"] ?? "text-sm",
|
||||
weightClasses[props.weight ?? "normal"] ?? "font-normal",
|
||||
"text-foreground",
|
||||
].join(" "),
|
||||
style: props.color ? { color: props.color } : undefined,
|
||||
},
|
||||
String(props.content ?? ""),
|
||||
);
|
||||
},
|
||||
|
||||
Button: ({ props, emit }) => {
|
||||
const variant = props.variant ?? "primary";
|
||||
const variantClasses: Record<string, string> = {
|
||||
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
danger:
|
||||
"bg-destructive/10 text-destructive hover:bg-destructive/20 border border-destructive/20",
|
||||
};
|
||||
return h(
|
||||
"button",
|
||||
{
|
||||
disabled: props.disabled,
|
||||
onClick: () => emit("press"),
|
||||
class: [
|
||||
"inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium transition-colors",
|
||||
variantClasses[variant] ?? variantClasses.primary,
|
||||
props.disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
||||
].join(" "),
|
||||
},
|
||||
props.label,
|
||||
);
|
||||
},
|
||||
|
||||
Badge: ({ props }) =>
|
||||
h(
|
||||
"span",
|
||||
{
|
||||
class:
|
||||
"inline-flex items-center rounded-full px-3 py-1 text-xs font-medium bg-secondary text-secondary-foreground border border-border",
|
||||
style: props.color
|
||||
? {
|
||||
backgroundColor: `${props.color}20`,
|
||||
color: props.color,
|
||||
borderColor: `${props.color}40`,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
props.label,
|
||||
),
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": ["src", "server.ts"]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { viteSingleFile } from "vite-plugin-singlefile";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), tailwindcss(), viteSingleFile()],
|
||||
build: {
|
||||
outDir: "dist",
|
||||
emptyOutDir: false,
|
||||
rollupOptions: {
|
||||
input: "index.html",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
@@ -20,7 +20,7 @@ Add to `.cursor/mcp.json`:
|
||||
"mcpServers": {
|
||||
"json-render": {
|
||||
"command": "npx",
|
||||
"args": ["tsx", "examples/mcp-react/server.ts", "--stdio"]
|
||||
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
||||
"mcpServers": {
|
||||
"json-render": {
|
||||
"command": "npx",
|
||||
"args": ["tsx", "/absolute/path/to/examples/mcp-react/server.ts", "--stdio"]
|
||||
"args": ["tsx", "/absolute/path/to/examples/mcp/server.ts", "--stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-mcp-react",
|
||||
"version": "0.1.1",
|
||||
"name": "example-mcp",
|
||||
"version": "0.1.2",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -16,8 +16,8 @@
|
||||
"@json-render/shadcn": "workspace:*",
|
||||
"@modelcontextprotocol/ext-apps": "^1.2.0",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { JSONUIProvider, Renderer } from "@json-render/react";
|
||||
import { shadcnComponents } from "@json-render/shadcn";
|
||||
import { defineRegistry } from "@json-render/react";
|
||||
import { useJsonRenderApp } from "@json-render/mcp/app/react";
|
||||
import { useJsonRenderApp } from "@json-render/mcp/app";
|
||||
import { catalog } from "./catalog";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
@@ -5,6 +5,9 @@ import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss(), viteSingleFile()],
|
||||
resolve: {
|
||||
dedupe: ["react", "react-dom"],
|
||||
},
|
||||
build: {
|
||||
outDir: "dist",
|
||||
emptyOutDir: false,
|
||||
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -112,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";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -563,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.",
|
||||
@@ -1014,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",
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
-58
@@ -46,12 +46,10 @@ await server.connect(new StdioServerTransport());
|
||||
|
||||
### 3. Build the UI (iframe)
|
||||
|
||||
Create an app that connects to the MCP host and renders specs. Framework-specific adapters are available for React, Vue, and Svelte.
|
||||
|
||||
#### React
|
||||
Create a React app that uses `useJsonRenderApp` from `@json-render/mcp/app`:
|
||||
|
||||
```tsx
|
||||
import { useJsonRenderApp } from "@json-render/mcp/app/react";
|
||||
import { useJsonRenderApp } from "@json-render/mcp/app";
|
||||
import { JSONUIProvider, Renderer } from "@json-render/react";
|
||||
|
||||
function McpAppView({ registry }) {
|
||||
@@ -68,44 +66,6 @@ function McpAppView({ registry }) {
|
||||
}
|
||||
```
|
||||
|
||||
#### Vue
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useJsonRenderApp } from "@json-render/mcp/app/vue";
|
||||
import { Renderer } from "@json-render/vue";
|
||||
|
||||
const { spec, loading, error } = useJsonRenderApp();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="error">Error: {{ error.message }}</div>
|
||||
<div v-else-if="!spec">Waiting for spec...</div>
|
||||
<Renderer v-else :spec="spec" :registry="registry" :loading="loading" />
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Svelte
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { createJsonRenderApp } from "@json-render/mcp/app/svelte";
|
||||
import { Renderer } from "@json-render/svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
const { spec, loading, error, destroy } = createJsonRenderApp();
|
||||
onDestroy(destroy);
|
||||
</script>
|
||||
|
||||
{#if $error}
|
||||
<div>Error: {$error.message}</div>
|
||||
{:else if !$spec}
|
||||
<div>Waiting for spec...</div>
|
||||
{:else}
|
||||
<Renderer spec={$spec} {registry} loading={$loading} />
|
||||
{/if}
|
||||
```
|
||||
|
||||
Bundle with Vite + `vite-plugin-singlefile` into a single HTML file, then pass it to `createMcpApp` as the `html` option.
|
||||
|
||||
### 4. Connect to a client
|
||||
@@ -147,29 +107,17 @@ Register a json-render tool on an existing `McpServer`.
|
||||
|
||||
Register a json-render UI resource on an existing `McpServer`.
|
||||
|
||||
### Client Side
|
||||
### Client Side (`@json-render/mcp/app`)
|
||||
|
||||
#### `buildAppHtml(options)` — `@json-render/mcp/app`
|
||||
|
||||
Generate a self-contained HTML string from bundled JS/CSS for use as a UI resource.
|
||||
|
||||
#### `useJsonRenderApp(options?)` — `@json-render/mcp/app/react`
|
||||
#### `useJsonRenderApp(options?)`
|
||||
|
||||
React hook for the iframe-side app. Connects to the MCP host, receives tool results, and maintains the current json-render spec.
|
||||
|
||||
Returns `{ spec, loading, connected, connecting, error, app, callServerTool }`.
|
||||
|
||||
#### `useJsonRenderApp(options?)` — `@json-render/mcp/app/vue`
|
||||
#### `buildAppHtml(options)`
|
||||
|
||||
Vue composable with the same API shape. Values are Vue refs/computed properties.
|
||||
|
||||
Returns `{ spec, loading, connected, connecting, error, app, callServerTool }`.
|
||||
|
||||
#### `createJsonRenderApp(options?)` — `@json-render/mcp/app/svelte`
|
||||
|
||||
Creates a json-render MCP App client using Svelte stores. Values are readable stores.
|
||||
|
||||
Returns `{ spec, loading, connected, connecting, error, app, callServerTool, destroy }`.
|
||||
Generate a self-contained HTML string from bundled JS/CSS for use as a UI resource.
|
||||
|
||||
## Client Support
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
@@ -43,20 +43,10 @@
|
||||
"import": "./dist/app.mjs",
|
||||
"require": "./dist/app.js"
|
||||
},
|
||||
"./app/react": {
|
||||
"types": "./dist/app/react.d.ts",
|
||||
"import": "./dist/app/react.mjs",
|
||||
"require": "./dist/app/react.js"
|
||||
},
|
||||
"./app/vue": {
|
||||
"types": "./dist/app/vue.d.ts",
|
||||
"import": "./dist/app/vue.mjs",
|
||||
"require": "./dist/app/vue.js"
|
||||
},
|
||||
"./app/svelte": {
|
||||
"types": "./dist/app/svelte.d.ts",
|
||||
"import": "./dist/app/svelte.mjs",
|
||||
"require": "./dist/app/svelte.js"
|
||||
"./build-app-html": {
|
||||
"types": "./dist/build-app-html-entry.d.ts",
|
||||
"import": "./dist/build-app-html-entry.mjs",
|
||||
"require": "./dist/build-app-html-entry.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
@@ -75,16 +65,12 @@
|
||||
"devDependencies": {
|
||||
"@internal/typescript-config": "workspace:*",
|
||||
"@types/react": "19.2.3",
|
||||
"svelte": "^5.0.0",
|
||||
"tsup": "^8.0.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vue": "^3.5.0"
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"svelte": "^5.0.0",
|
||||
"vue": "^3.5.0"
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
@@ -92,12 +78,6 @@
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"svelte": {
|
||||
"optional": true
|
||||
},
|
||||
"vue": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user