Skip to content

Latest commit

 

History

History
369 lines (294 loc) · 11.4 KB

File metadata and controls

369 lines (294 loc) · 11.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.

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.

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.

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.

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 ("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 return RuntimeNode | string and can participate in the declarative execution/sandbox model.
  • runtime: "preact" expects a Preact-compatible component and should be rendered with renderTrustedPlanInBrowser or the trusted security profile.
  • runtime: "preact" does not run inside the browser worker/iframe/ShadowRealm sandbox adapters.

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("npm:@acme/renderify-widgets", "SummaryCard", {
  title: "Quarterly Report",
});

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