Skip to content

Live preview

View .mdllms.txt

startPreview serves a page that loads your composition’s module and renders it live with WebGPU in the browser — not a video stream. The server only hands over the bundle, your assets, and the soundtrack mixed by the export engine.

src/render.ts (preview mode)
import { startPreview } from "framefields";
const session = await startPreview(
{ entry: new URL("./film.ts", import.meta.url), export: "buildFilm" },
{ title: "Launch film" },
);
console.log(`Preview at ${session.url}`);
await session.closed; // "idle" | "replaced" | "closed"
process.exit(0);

export names a function returning the Composition (may be async) or a Composition itself.

Option Default Notes
port derived from the project directory one stable URL per project; a new run replaces the old server and open tabs reload in place
host 127.0.0.1 never leaves the machine
open false open the system browser; otherwise show session.url where you are
title — page header
audio true mix and play the soundtrack
idleCloseMs 5000 stop after the last tab closes; false keeps serving until close()
root project of the entry extra folders the page may load assets from
log stderr where page errors and lifecycle lines go; false silences
notesDir .framefields/preview-notes false turns notes off

The browser runs film.ts itself. Build asset paths from the module URL, not process.cwd():

import path from "node:path";
import { fileURLToPath } from "node:url";
const HERE = path.dirname(fileURLToPath(import.meta.url));
const FONTS = path.resolve(HERE, "../assets/fonts");

node:path, fileURLToPath and synchronous fs reads work through shims; writes are skipped.

  • Run the preview as a background command and read its output: it reports when the page opened, each distinct page error (build failure, a frame that threw, missing font, no WebGPU), and why it stopped.
  • After editing, run it again — same URL, and the open tab reloads at the same moment.
  • Deep link: #t=12.5 (seconds) or #frame=750.
  • Script it: window.framefieldsPreview has seek(seconds), seekFrame(n), play(), pause(), notes(), state(). seek resolves once the frame is drawn — screenshot after it. <html data-preview="ready"> marks the first drawn frame.

Paused, a person clicks a spot (or drags an area) and writes what should change. Each note is printed in the preview output and saved:

.framefields/preview-notes/
notes.json [{ id, frame, time, x, y, w?, h?, text, done }]
note-<id>.jpg the frame with the spot marked

To act on notes: read notes.json, open each open note’s picture, find the layer from the moment and the spot, edit, rerun the preview. Set "done": true yourself or let the person tick them off.

Play runs in real time on the soundtrack’s clock and skips frames a slow device can’t draw; the page shows the frame rate it reaches. Paused, it draws the exact frame. Requires WebGPU (current Chrome, Edge or Safari).