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.
In the declarative RuntimeNode lane, those bindings dispatch static runtime events. They do not extract live browser values such as event.target.value, checked, or FormData. Use source.runtime: "preact" when the UI needs value-driven forms or richer DOM event handling.
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.
The referenced export is invoked as a Renderify component factory and must return a RuntimeNode tree or a string. Component nodes are not React or Preact component hosts.
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 ("preact" is the trusted browser source lane)
}Source modules enable richer interactivity than the declarative node tree. The source code is transpiled via Babel, imports are rewritten to browser-resolvable URLs, and the default export is rendered as a Preact component (when runtime: "preact") or as a RuntimeNode tree.
runtime: "renderify"expects the exported function to returnRuntimeNode | stringand can participate in the declarative execution/sandbox model.runtime: "preact"expects a Preact-compatible component and should be rendered withrenderTrustedPlanInBrowseror thetrustedsecurity profile.runtime: "preact"does not run inside the browser worker/iframe/ShadowRealm sandbox adapters.
{
"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("npm:@acme/renderify-widgets", "SummaryCard", {
title: "Quarterly Report",
});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"]
}
}