You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(core,cli,engine,producer): getVariables() helper + --variables render flag (PR 1/4) (#600)
## What
Adds the parametrized-render primitive from [hf#592](#592) by introducing a `getVariables()` runtime helper plus a CLI `--variables` / `--variables-file` flag. Compositions declare variables once on the root `<html>` element (the existing `data-composition-variables` attribute, which already drives Studio editing UI), read them at runtime via `window.__hyperframes.getVariables()`, and CLI users override them at render time without touching the composition source.
This is **PR 1 of a 4-PR stack**:
1. **PR 1 (this one)** — runtime helper + CLI flag + engine injection (top-level renders).
2. PR 2 — sub-comp per-instance scoping (carry the host's `data-variable-values` into the inlined sub-comp's `getVariables()`).
3. PR 3 — schema validation + lint rules (warn on undeclared variable IDs, optional `--strict-variables`).
4. PR 4 — skill / scaffold distribution (SKILL.md, AGENTS.md scaffolds, openai/plugins mirror).
## Why
The existing `data-composition-variables` schema declares variable types and defaults but isn't readable from composition scripts and can't be overridden at render time. To produce N variations of a composition today, an agent has to fork the composition or edit the source HTML before each render. `--variables` collapses that into one render call per variation, matching Editframe's `--data` UX without copying their `getRenderData` framing — `getVariables()` is named for the codebase's existing "variables" terminology and works equally in dev preview and at render time.
## How
- **Runtime helper** (`packages/core/src/runtime/getVariables.ts`): reads `data-composition-variables` from `document.documentElement`, extracts `{id: default}` defaults, merges `window.__hfVariables` (override) on top, returns `Partial<T>`. Same code path in dev preview (no override) and at render (with override). Generic parameter for typed editor ergonomics. Exposed both as a named export from `@hyperframes/core` and on `window.__hyperframes.getVariables` for vanilla compositions.
- **CLI flag** (`packages/cli/src/commands/render.ts`): `--variables '<json>'` and `--variables-file <path>`. `parseVariablesArg` is split out as a pure function (returns a discriminated `{ ok: true } | { ok: false }` union) so all validation paths are unit-testable; the side-effecting `resolveVariablesArg` wraps it with `errorBox` + `process.exit`. Mutually exclusive with `--variables-file`; fail-fast on conflicts, missing file, unparseable JSON, or non-object payloads (string, number, array, null).
- **Engine injection** (`packages/engine/src/services/frameCapture.ts`): added an `evaluateOnNewDocument` step right after the `__name` polyfill that sets `window.__hfVariables` to the parsed JSON before any page script runs. Skipped when payload is empty so we don't add pointless init scripts. Plumbed through `CaptureOptions.variables` and `RenderConfig.variables`. Docker mode forwards the flag to the in-container CLI via `dockerRunArgs`.
- **Why a separate `__hfVariables` global** instead of writing into `__hyperframes.getVariables()` directly: the helper is an IIFE that has to be defined before composition scripts execute, but the *override* needs to land before *that*. `evaluateOnNewDocument` is the only reliable hook that runs before the runtime IIFE evaluates. Storing the raw value on `__hfVariables` and merging in the helper keeps both paths order-independent.
## Test plan
- [x] Unit tests added/updated
- 9 jsdom tests for `getVariables()` covering empty state, declared defaults only, override merge, override-wins, declared-only, invalid JSON, non-array payloads, non-object overrides, typed generic.
- 7 tests for `parseVariablesArg` covering all validation paths.
- 2 integration tests for `renderLocal` confirming `variables` reach `createRenderJob`.
- 3 new `dockerRunArgs` assertions for `--variables` passthrough (set / not-set / empty-object).
- All existing tests green: core 611, cli 208, engine 519.
- [x] Manual testing performed
- `npx tsx packages/cli/src/cli.ts render --help` shows both flags + the two new examples.
- [x] Documentation updated
- `docs/packages/cli.mdx` — added flags to the table and a "Parametrized renders" section with a worked example.
- `docs/concepts/data-attributes.mdx` — added `data-composition-variables` row.
## Backwards compatibility
Fully backwards compatible. Compositions without `data-composition-variables` work unchanged; `getVariables()` returns `{}` and the engine skips the injection step.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Copy file name to clipboardExpand all lines: docs/concepts/data-attributes.mdx
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -30,6 +30,7 @@ Hyperframes uses HTML data attributes to control timing, media playback, and [co
30
30
|`data-height`|`"1080"`| Composition height in pixels |
31
31
|`data-composition-src`|`"./intro.html"`| Path to external [composition](/concepts/compositions) HTML file |
32
32
|`data-variable-values`|`'{"title":"Hello"}'`| JSON object of values passed to a nested composition. HyperFrames carries these values through, but your composition script must read and apply them manually. |
33
+
|`data-composition-variables`|`'[{"id":"title","type":"string","label":"Title","default":"Hello"}]'`| JSON array of declared variables (`id`, `type`, `label`, `default`). Drives Studio editing UI and provides defaults read by `window.__hyperframes.getVariables()`. The CLI flag `hyperframes render --variables '<json>'` overrides these defaults at render time. |
Copy file name to clipboardExpand all lines: docs/packages/cli.mdx
+40Lines changed: 40 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -606,9 +606,49 @@ This is suppressed in CI environments, non-TTY shells, and when `HYPERFRAMES_NO_
606
606
|`--browser-gpu` / `--no-browser-gpu`| — | on locally, off in Docker | Use or opt out of host GPU acceleration for local Chrome/WebGL capture |
607
607
|`--docker`| — | off | Use Docker for [deterministic rendering](/concepts/determinism)|
608
608
|`--quiet`| — | off | Suppress verbose output |
609
+
|`--variables`| JSON object | — | Variable overrides merged over `data-composition-variables` defaults. Read via `window.__hyperframes.getVariables()`|
610
+
|`--variables-file`| path | — | Path to a JSON file with variable overrides (alternative to `--variables`) |
609
611
610
612
CRF and target bitrate default to the `--quality` preset. Use `--crf` or `--video-bitrate` for fine-grained overrides; `RenderConfig.crf` and `RenderConfig.videoBitrate` accept the same overrides programmatically.
611
613
614
+
#### Parametrized renders
615
+
616
+
Render the same composition with different content by declaring variables on the composition root and overriding them at render time:
`getVariables()` returns the merged result of declared defaults and any `--variables` overrides, so the same composition runs unchanged in dev preview and in production renders.
651
+
612
652
#### WebM with Transparency
613
653
614
654
Use `--format webm` to render compositions with a transparent background. This produces VP9 video with alpha channel in a WebM container — the standard format for overlayable video.
description: "Max concurrent renders when using the producer server (1-10). Default: 2.",
126
134
},
135
+
variables: {
136
+
type: "string",
137
+
description:
138
+
'JSON object of variable values, merged over the composition\'s data-composition-variables defaults. Example: --variables \'{"title":"Hello"}\'. Read inside the composition via window.__hyperframes.getVariables().',
139
+
},
140
+
"variables-file": {
141
+
type: "string",
142
+
description:
143
+
"Path to a JSON file with variable values (alternative to --variables). The file must contain a single JSON object.",
0 commit comments