> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reframe-video.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Programmatic API

> Load, validate, and check a scene in-process — the importable surface under the CLI.

The CLI is a thin shell over a server-side, importable API. A backend can do NL → eDSL → IR → (preview + diff) without shelling out or re-implementing the evaluator. Three subpath exports of the `reframe-video` npm package:

| import                   | what                                                                                                                                    |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `reframe-video`          | the core eDSL + IR — `scene`, `compileScene`, `composeScene`, `evaluate`, `sceneManifest`, `lintScene`, `validateScene`                 |
| `reframe-video/compile`  | load + validate + determinism — `loadScene`, `loadSceneFromCode`, `loadModule`, `checkDeterminism`, `SceneLoadError`, `ValidationIssue` |
| `reframe-video/renderer` | `DisplayList` → Canvas 2D — `renderFrame`, `drawDisplayList`, `ImageRegistry`, `VideoRegistry`, `coverRect`                             |

<Note>
  `reframe-video/compile` executes the scene module in-process. Treat untrusted or model-authored source as code: bound it with a timeout and run it where a misbehaving module can't do harm. True sandboxing is a separate concern.
</Note>

## Load & validate a scene

```ts theme={null}
import { loadScene, loadSceneFromCode, SceneLoadError } from "reframe-video/compile";

try {
  const compiled = await loadScene("scene.ts");        // bundle + validate → CompiledScene
  // or: await loadSceneFromCode(sourceString)          // no file on disk
} catch (err) {
  if (err instanceof SceneLoadError) {
    err.kind;     // "bundle" | "eval" | "validation" — which stage failed
    err.issues;   // ValidationIssue[] on a validation failure
  }
}
```

A `ValidationIssue` is structured, not prose — so a UI can point at the exact broken node:

```ts theme={null}
interface ValidationIssue {
  code: string;     // stable category, e.g. "unknown-blend", "duplicate-node-id"
  path: string;     // locator, e.g. "nodes.box", "timeline.beat(intro)[0]", "camera.zoom"
  message: string;  // the human-readable line
}
```

The CLI mirrors this: `reframe compile --json` returns `{ ok: false, error, kind, issues }` on failure.

## Check determinism

```ts theme={null}
import { checkDeterminism } from "reframe-video/compile";

const { deterministic, findings } = await checkDeterminism("scene.ts");
// compiles the source twice and diffs the IR; `findings` pins the first
// differing address when a Math.random() / Date leaked into a prop.
```

This is what `reframe lint` runs for the source-purity half of its gate.

## Render a frame

```ts theme={null}
import { renderFrame } from "reframe-video/renderer";
import { compileScene, evaluate } from "reframe-video";

const ctx = canvas.getContext("2d")!;        // any Canvas 2D context
renderFrame(ctx, compiled, t);                // draw the scene at time t
```

`renderFrame` evaluates the compiled scene at `t` and paints the resulting `DisplayList`; pass an `ImageRegistry` / `VideoRegistry` to supply raster sources for `image` / `video` nodes. This is the same path the live browser preview uses.

## Spatial query — where things are on screen

`sceneManifest` answers *what* you can edit (addresses); the spatial query answers *where* it is and *what's under a point* at time `t` — the other half an editor needs. Pure functions over the DisplayList (each op carries its source `id` + final scene-space transform), with canvas-free Inter metrics, so no canvas is required:

```ts theme={null}
import { compileScene, sceneGeometry, hitTest } from "reframe-video";

const g = sceneGeometry(compiled, 1.5);
// g.nodes:   { id, corners: [x,y][], bounds: {x,y,w,h} }[]   (rotated quad + AABB, scene coords)
// g.groups:  { id, bounds }[]                                 (union of descendants)
// g.waypoints: { label, target, index, x, y }[]               (motionPath control points → timeline.<label>.points)
const id = hitTest(g, 960, 540); // topmost node under the point, or null
```

On the CLI: `reframe geometry <scene> --t <sec> --json`.

## Embedded editor — `player --edit`

`reframe player <scene> --edit -o editor.html` builds an embedded-editor variant: no autoplay, seek-driven, exposing a live API + a host↔iframe `postMessage` channel, so a host (e.g. a desktop or web editor) can click-to-select, draw aligned handles, and preview overlay edits **without rebuilding the HTML**:

```js theme={null}
// inside the player iframe:
window.__reframe = {
  seek(t), play(), pause(),
  hitTest(x, y),     // → nodeId | null   (scene coords)
  bounds(),          // → { nodes, groups, waypoints }
  waypoints(),       // → motionPath handles
  setOverlay(doc),   // re-compose + re-render IN-BROWSER, no reload
};
// host → player:  { type: "seek", t } | { type: "setOverlay", doc } | { type: "hitTest", x, y } | { type: "bounds" }
// player → host:  { type: "ready", size } | { type: "pick", nodeId, sceneXY } | { type: "bounds", … }
```

`setOverlay(doc)` runs `composeScene(baseIR, doc)` → `compileScene` → redraw, so a dragged node or motionPath waypoint round-trips as a regen-stable overlay patch (`nodes.<id>.x`, `timeline.<label>.points`). `--edit-origin <origin>` restricts `event.origin` (default `*`).
