Labsco
vercel-labs logo

core

β˜… 15,600

by vercel Β· part of vercel-labs/json-render

Schema definition, catalog creation, and AI prompt generation for json-render specs. Define schemas with typed specs and catalogs using defineSchema , then create catalogs mapping component names to their props and descriptions Generate AI system prompts from catalogs with optional custom rules; supports spec streaming via JSONL patches with createSpecStreamCompiler Dynamic prop expressions enable state binding ( $state , $bindState ), conditionals ( $cond ), templating ( $template ), and...

πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedFreeAdvanced setup
🧰 Not standalone. This skill ships with vercel-labs/json-render and only works together with that tool β€” install the tool first, then add this skill.

This is the playbook your agent receives when the skill activates β€” you don't need to read it to use the skill, but it's here to audit before installing.

by vercel

Schema definition, catalog creation, and AI prompt generation for json-render specs. Define schemas with typed specs and catalogs using defineSchema , then create catalogs mapping component names to their props and descriptions Generate AI system prompts from catalogs with optional custom rules; supports spec streaming via JSONL patches with createSpecStreamCompiler Dynamic prop expressions enable state binding ( $state , $bindState ), conditionals ( $cond ), templating ( $template ), and... npx skills add https://github.com/vercel-labs/json-render --skill core Download ZIPGitHub15.6k

@json-render/core

Core package for schema definition, catalog creation, and spec streaming.

Key Concepts

  • Schema: Defines the structure of specs and catalogs (use defineSchema)

  • Catalog: Maps component/action names to their definitions (use defineCatalog)

  • Spec: JSON output from AI that conforms to the schema

  • SpecStream: JSONL streaming format for progressive spec building

Defining a Schema

import { defineSchema } from "@json-render/core";

export const schema = defineSchema((s) => ({
 spec: s.object({
 // Define spec structure
 }),
 catalog: s.object({
 components: s.map({
 props: s.zod(),
 description: s.string(),
 }),
 }),
}), {
 promptTemplate: myPromptTemplate, // Optional custom AI prompt
});

Creating a Catalog

import { defineCatalog } from "@json-render/core";
import { schema } from "./schema";
import { z } from "zod";

export const catalog = defineCatalog(schema, {
 components: {
 Button: {
 props: z.object({
 label: z.string(),
 variant: z.enum(["primary", "secondary"]).nullable(),
 }),
 description: "Clickable button component",
 },
 },
});

Generating AI Prompts

const systemPrompt = catalog.prompt(); // Uses schema's promptTemplate
const systemPrompt = catalog.prompt({ customRules: ["Rule 1", "Rule 2"] });

SpecStream Utilities

For streaming AI responses (JSONL patches):

import { createSpecStreamCompiler } from "@json-render/core";

const compiler = createSpecStreamCompiler ();

// Process streaming chunks
const { result, newPatches } = compiler.push(chunk);

// Get final result
const finalSpec = compiler.getResult();

Dynamic Prop Expressions

Any prop value can be a dynamic expression resolved at render time:

  • { "$state": "/state/key" } - reads a value from the state model (one-way read)

  • { "$bindState": "/path" } - two-way binding: reads from state and enables write-back. Use on the natural value prop (value, checked, pressed, etc.) of form components.

  • { "$bindItem": "field" } - two-way binding to a repeat item field. Use inside repeat scopes.

  • { "$cond": <condition>, "$then": <value>, "$else": <value> } - evaluates a visibility condition and picks a branch

  • { "$template": "Hello, ${/user/name}!" } - interpolates ${/path} references with state values

  • { "$computed": "fnName", "args": { "key": <expression> } } - calls a registered function with resolved args

$cond uses the same syntax as visibility conditions ($state, eq, neq, not, arrays for AND). $then and $else can themselves be expressions (recursive).

Components do not use a statePath prop for two-way binding. Instead, use { "$bindState": "/path" } on the natural value prop (e.g. value, checked, pressed).

{
 "color": {
 "$cond": { "$state": "/activeTab", "eq": "home" },
 "$then": "#007AFF",
 "$else": "#8E8E93"
 },
 "label": { "$template": "Welcome, ${/user/name}!" },
 "fullName": {
 "$computed": "fullName",
 "args": {
 "first": { "$state": "/form/firstName" },
 "last": { "$state": "/form/lastName" }
 }
 }
}
import { resolvePropValue, resolveElementProps } from "@json-render/core";

const resolved = resolveElementProps(element.props, { stateModel: myState });

State Watchers

Elements can declare a watch field (top-level, sibling of type/props/children) to trigger actions when state values change:

{
 "type": "Select",
 "props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
 "watch": {
 "/form/country": { "action": "loadCities", "params": { "country": { "$state": "/form/country" } } }
 },
 "children": []
}

Watchers only fire on value changes, not on initial render.

Validation

Built-in validation functions: required, email, url, numeric, minLength, maxLength, min, max, pattern, matches, equalTo, lessThan, greaterThan, requiredIf.

Cross-field validation uses $state expressions in args:

import { check } from "@json-render/core";

check.required("Field is required");
check.matches("/form/password", "Passwords must match");
check.lessThan("/form/endDate", "Must be before end date");
check.greaterThan("/form/startDate", "Must be after start date");
check.requiredIf("/form/enableNotifications", "Required when enabled");

User Prompt Builder

Build structured user prompts with optional spec refinement and state context:

import { buildUserPrompt } from "@json-render/core";

// Fresh generation
buildUserPrompt({ prompt: "create a todo app" });

// Refinement with edit modes (default: patch-only)
buildUserPrompt({ prompt: "add a toggle", currentSpec: spec, editModes: ["patch", "merge"] });

// With runtime state
buildUserPrompt({ prompt: "show data", state: { todos: [] } });

Available edit modes: "patch" (RFC 6902 JSON Patch), "merge" (RFC 7396 Merge Patch), "diff" (unified diff).

Spec Validation

Validate spec structure and auto-fix common issues:

import { validateSpec, autoFixSpec } from "@json-render/core";

const { valid, issues } = validateSpec(spec);
// issues include: missing_child, invalid_visible (malformed conditions),
// repeat_without_children, repeat_state_mismatch (statePath not an array in state)

const { spec: fixed, fixDetails } = autoFixSpec(spec);
// fixDetails entries are { message, lossy }. Lossless fixes relocate
// misplaced fields; lossy fixes prune dangling children references.
// In a repair loop, withhold lossy fixes until retries are exhausted:
const attempt = autoFixSpec(spec, { lossy: retriesExhausted });

Visibility Conditions

Control element visibility with state-based conditions. VisibilityContext is { stateModel: StateModel }.

import { visibility } from "@json-render/core";

// Syntax
{ "$state": "/path" } // truthiness
{ "$state": "/path", "not": true } // falsy
{ "$state": "/path", "eq": value } // equality
[ cond1, cond2 ] // implicit AND

// Helpers
visibility.when("/path") // { $state: "/path" }
visibility.unless("/path") // { $state: "/path", not: true }
visibility.eq("/path", val) // { $state: "/path", eq: val }
visibility.and(cond1, cond2) // { $and: [cond1, cond2] }
visibility.or(cond1, cond2) // { $or: [cond1, cond2] }
visibility.always // true
visibility.never // false

Built-in Actions in Schema

Schemas can declare builtInActions -- actions that are always available at runtime and auto-injected into prompts:

const schema = defineSchema(builder, {
 builtInActions: [
 { name: "setState", description: "Update a value in the state model" },
 ],
});

These appear in prompts as [built-in] and don't require handlers in defineRegistry.

StateStore

The StateStore interface allows external state management libraries (Redux, Zustand, XState, etc.) to be plugged into json-render renderers. The createStateStore factory creates a simple in-memory implementation:

import { createStateStore, type StateStore } from "@json-render/core";

const store = createStateStore({ count: 0 });

store.get("/count"); // 0
store.set("/count", 1); // updates and notifies subscribers
store.update({ "/a": 1, "/b": 2 }); // batch update

store.subscribe(() => {
 console.log(store.getSnapshot()); // { count: 1 }
});

The StateStore interface: get(path), set(path, value), update(updates), getSnapshot(), subscribe(listener).

Key Exports

Export Purpose defineSchema Create a new schema defineCatalog Create a catalog from schema createStateStore Create a framework-agnostic in-memory StateStore resolvePropValue Resolve a single prop expression against data resolveElementProps Resolve all prop expressions in an element buildUserPrompt Build user prompts with refinement and state context buildEditUserPrompt Build user prompt for editing existing specs buildEditInstructions Generate prompt section for available edit modes isNonEmptySpec Check if spec has root and at least one element deepMergeSpec RFC 7396 deep merge (null deletes, arrays replace, objects recurse) diffToPatches Generate RFC 6902 JSON Patch operations from object diff EditMode Type: "patch" | "merge" | "diff" validateSpec Validate spec structure autoFixSpec Auto-fix common spec issues; classifies fixes lossy/lossless, { lossy: false } withholds pruning createSpecStreamCompiler Stream JSONL patches into spec createJsonRenderTransform TransformStream separating text from JSONL in mixed streams parseSpecStreamLine Parse single JSONL line applySpecStreamPatch Apply patch to object StateStore Interface for plugging in external state management ComputedFunction Function signature for $computed expressions check TypeScript helpers for creating validation checks BuiltInAction Type for built-in action definitions (name + description) ActionBinding Action binding type (includes preventDefault field)