The RuntimePlan is the intermediate representation (IR) at the heart of Renderify. Every piece of UI that Renderify renders — whether generated by an LLM, authored by hand, or loaded from a file — is expressed as a RuntimePlan.
interface RuntimePlan {
specVersion?: string; // Protocol version, e.g. "runtime-plan/v1"
id: string; // Unique plan identifier
version: number; // Positive integer, incremented on updates
root: RuntimeNode; // The UI tree
capabilities: RuntimeCapabilities; // Execution permissions and limits
state?: RuntimeStateModel; // Optional reactive state
imports?: string[]; // Module specifiers to pre-load
moduleManifest?: RuntimeModuleManifest; // Resolved module URLs
source?: RuntimeSourceModule; // Optional executable source code
metadata?: RuntimePlanMetadata; // Provenance and tags
}id— non-empty string identifying the plan. Used for state management and caching.version— positive integer. Must be >= 1.root— the top-level node of the UI tree.capabilities— declares what the plan needs (even if empty{}).
The specVersion field declares the protocol contract. Currently the only version is "runtime-plan/v1". In strict and balanced security profiles, this field is required.
const RUNTIME_PLAN_SPEC_VERSION_V1 = "runtime-plan/v1";The UI tree is composed of three node types:
interface RuntimeTextNode {
type: "text";
value: string; // Text content, supports template interpolation
}Template interpolation resolves {{state.count}}, {{context.userId}}, {{vars.name}} at render time.
interface RuntimeElementNode {
type: "element";
tag: string; // HTML tag name
props?: Record<string, JsonValue>; // Attributes and properties
children?: RuntimeNode[]; // Child nodes
}Props support standard HTML attributes. Event handlers use the onClick, onInput format and are converted to runtime event bindings.
interface RuntimeComponentNode {
type: "component";
module: string; // Module specifier (bare, URL, or relative)
exportName?: string; // Named export (default: "default")
props?: Record<string, JsonValue>;
children?: RuntimeNode[];
}Component nodes reference external modules loaded via the JSPM module loader. The module specifier is resolved through the module manifest or JSPM CDN.
interface RuntimeCapabilities {
domWrite?: boolean; // Allow DOM manipulation
networkHosts?: string[]; // Allowed network hosts
allowedModules?: string[]; // Permitted module specifiers
timers?: boolean; // Allow setTimeout/setInterval
storage?: Array<"localStorage" | "sessionStorage">;
executionProfile?: RuntimeExecutionProfile;
maxImports?: number; // Maximum import count
maxComponentInvocations?: number; // Maximum component renders
maxExecutionMs?: number; // Maximum execution time (ms)
}type RuntimeExecutionProfile =
| "standard" // Default, in-page execution
| "isolated-vm" // VM-isolated sync execution
| "sandbox-worker" // Web Worker sandbox
| "sandbox-iframe" // iframe sandbox
| "sandbox-shadowrealm"; // ShadowRealm sandboxThree budget dimensions are enforced at runtime:
| Budget | Field | Description |
|---|---|---|
| Import count | maxImports |
Number of module imports allowed |
| Component invocations | maxComponentInvocations |
Number of component renders |
| Wall-clock time | maxExecutionMs |
Total execution time in milliseconds |
When a budget is exceeded, execution stops and a diagnostic error is emitted.
interface RuntimeStateModel {
initial: RuntimeStateSnapshot; // Required: initial state values
transitions?: Record<string, RuntimeAction[]>; // Event-driven mutations
}
type RuntimeStateSnapshot = Record<string, JsonValue>;When state is present, state.initial is required. The runtime maintains a snapshot per plan ID.
Four action types are available for state transitions:
// Set a value at a path
interface RuntimeSetAction {
type: "set";
path: string; // Dot-separated path, e.g. "user.name"
value: JsonValue | { $from: string }; // Literal or reference
}
// Increment a numeric value
interface RuntimeIncrementAction {
type: "increment";
path: string;
by?: number; // Default: 1
}
// Toggle a boolean value
interface RuntimeToggleAction {
type: "toggle";
path: string;
}
// Push a value to an array
interface RuntimePushAction {
type: "push";
path: string;
value: JsonValue | { $from: string };
}The $from syntax references values from other contexts:
{ "$from": "state.count" } // Current state
{ "$from": "event.payload.id" } // Event payload
{ "$from": "context.userId" } // Execution context
{ "$from": "vars.theme" } // Context variablesAll state paths are validated against prototype pollution. The following segments are rejected:
__proto__prototypeconstructor
type RuntimeModuleManifest = Record<string, RuntimeModuleDescriptor>;
interface RuntimeModuleDescriptor {
resolvedUrl: string; // Full URL to the module
integrity?: string; // SRI hash (e.g. "sha384-...")
version?: string; // Package version
signer?: string; // Signing authority
}The manifest provides deterministic module resolution. In strict security profile, bare specifiers (like "recharts") must have a manifest entry. In strict mode, remote modules must also include integrity hashes.
{
"preact": {
"resolvedUrl": "https://ga.jspm.io/npm:preact@10.28.3/dist/preact.module.js",
"version": "10.28.3"
},
"recharts": {
"resolvedUrl": "https://ga.jspm.io/npm:recharts@3.3.0/es6/index.js",
"version": "3.3.0"
}
}interface RuntimeSourceModule {
code: string; // Source code
language: "js" | "jsx" | "ts" | "tsx"; // Source language
exportName?: string; // Export to render (default: "default")
runtime?: "renderify" | "preact"; // JSX runtime target
}Source modules enable richer interactivity than the declarative node tree. The source code is transpiled via Babel, imports are rewritten to CDN URLs, and the default export is rendered as a Preact component (when runtime: "preact") or as a RuntimeNode tree.
{
"source": {
"code": "import { useState } from 'preact/hooks';\nexport default function Counter() {\n const [count, setCount] = useState(0);\n return <button onClick={() => setCount(c => c+1)}>Count: {count}</button>;\n}",
"language": "tsx",
"runtime": "preact"
}
}interface RuntimePlanMetadata {
sourcePrompt?: string; // The prompt that generated this plan
sourceModel?: string; // LLM model used
tags?: string[]; // Classification tags
[key: string]: JsonValue | undefined; // Arbitrary extensions
}The IR package includes pre-resolved module mappings for the React/Preact ecosystem:
| Specifier | Resolved To |
|---|---|
preact |
preact@10.28.3/dist/preact.module.js |
preact/hooks |
preact@10.28.3/hooks/dist/hooks.module.js |
preact/compat |
preact@10.28.3/compat/dist/compat.module.js |
react |
preact@10.28.3/compat/dist/compat.module.js |
react-dom |
preact@10.28.3/compat/dist/compat.module.js |
react-dom/client |
preact@10.28.3/compat/dist/compat.module.js |
react/jsx-runtime |
preact@10.28.3/jsx-runtime/dist/jsxRuntime.module.js |
recharts |
recharts@3.3.0/es6/index.js |
These aliases mean LLMs can generate standard React code that runs directly via Preact.
createTextNode("Hello");
createElementNode("div", { class: "card" }, [createTextNode("Content")]);
createComponentNode("recharts", "LineChart", { data: [...] });isRuntimePlan(value); // Full plan validation
isRuntimeNode(value); // Node type check
isRuntimeAction(value); // Action validation
isRuntimeStateModel(value); // State model validation
isRuntimeCapabilities(value); // Capabilities validation
isRuntimeSourceModule(value); // Source module validation
isRuntimeModuleManifest(value); // Manifest validation
isJsonValue(value); // JSON-safe value check// Walk all nodes depth-first
walkRuntimeNode(plan.root, (node, depth) => {
console.log(`${" ".repeat(depth * 2)}${node.type}`);
});
// Collect all component module specifiers
const modules = collectComponentModules(plan.root);
// => ["recharts", "my-component"]getValueByPath(state, "user.name"); // => "Alice"
setValueByPath(state, "user.name", "Bob");
isSafePath("user.name"); // => true
isSafePath("__proto__.hack"); // => false{
"specVersion": "runtime-plan/v1",
"id": "analytics-dashboard",
"version": 1,
"root": {
"type": "element",
"tag": "div",
"props": { "style": "padding: 16px" },
"children": [
{
"type": "element",
"tag": "h1",
"children": [{ "type": "text", "value": "Dashboard" }]
},
{
"type": "element",
"tag": "p",
"children": [{ "type": "text", "value": "Count: {{state.count}}" }]
}
]
},
"capabilities": {
"domWrite": true,
"maxExecutionMs": 5000,
"maxComponentInvocations": 100
},
"state": {
"initial": { "count": 0 },
"transitions": {
"increment": [{ "type": "increment", "path": "count" }]
}
},
"imports": ["recharts"],
"moduleManifest": {
"recharts": {
"resolvedUrl": "https://ga.jspm.io/npm:recharts@3.3.0/es6/index.js",
"version": "3.3.0"
}
},
"metadata": {
"sourcePrompt": "Build an analytics dashboard",
"sourceModel": "gpt-5-mini",
"tags": ["dashboard", "analytics"]
}
}