Skip to content

Latest commit

 

History

History
359 lines (287 loc) · 10.4 KB

File metadata and controls

359 lines (287 loc) · 10.4 KB

RuntimePlan IR Reference

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.

RuntimePlan Structure

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
}

Required Fields

  • 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 {}).

Spec Version

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";

Node Types

The UI tree is composed of three node types:

Text Node

interface RuntimeTextNode {
  type: "text";
  value: string; // Text content, supports template interpolation
}

Template interpolation resolves {{state.count}}, {{context.userId}}, {{vars.name}} at render time.

Element Node

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.

Component Node

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.

Capabilities

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)
}

Execution Profiles

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 sandbox

Execution Budgets

Three 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.

State Model

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.

Actions

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 };
}

Value References

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 variables

Path Safety

All state paths are validated against prototype pollution. The following segments are rejected:

  • __proto__
  • prototype
  • constructor

Module Manifest

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.

Example Manifest

{
  "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"
  }
}

Source Module

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.

Example Source Module

{
  "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"
  }
}

Metadata

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
}

Built-in Compatibility Aliases

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.

Utility Functions

Node Creation

createTextNode("Hello");
createElementNode("div", { class: "card" }, [createTextNode("Content")]);
createComponentNode("recharts", "LineChart", { data: [...] });

Validation Guards

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

Tree Utilities

// 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"]

Path Utilities

getValueByPath(state, "user.name"); // => "Alice"
setValueByPath(state, "user.name", "Bob");
isSafePath("user.name"); // => true
isSafePath("__proto__.hack"); // => false

Complete Example

{
  "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"]
  }
}