Skip to content

feat(core,cli,engine,producer): getVariables() helper + --variables render flag (PR 1/4)#600

Merged
jrusso1020 merged 2 commits intomainfrom
feat/get-variables
May 4, 2026
Merged

feat(core,cli,engine,producer): getVariables() helper + --variables render flag (PR 1/4)#600
jrusso1020 merged 2 commits intomainfrom
feat/get-variables

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 commented May 3, 2026

What

Adds the parametrized-render primitive from hf#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

  • 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.
  • Manual testing performed
    • npx tsx packages/cli/src/cli.ts render --help shows both flags + the two new examples.
  • 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

…bles render flag

Adds the parametrized-render primitive from hf#592 by reusing the existing
data-composition-variables schema as the source of declared defaults.

- Runtime helper window.__hyperframes.getVariables() (also exported from
  @hyperframes/core) reads data-composition-variables defaults from the
  document root and merges window.__hfVariables (CLI override) on top.
  Returns Partial<T> for typed access; supports a generic for editor
  ergonomics. Same code path runs in dev preview and at render time.
- CLI render --variables '<json>' / --variables-file <path> populates the
  override. Mutually exclusive; fail-fast on conflicting flags, missing
  file, unparseable JSON, or non-object payloads. parseVariablesArg is
  exported as a pure function so validation paths stay unit-testable.
- Engine injects window.__hfVariables via evaluateOnNewDocument before
  any page script runs, so the helper sees the merged values on its
  first call. Empty payloads are skipped to avoid pointless init scripts.
- Producer threads variables through RenderConfig and into the engine's
  CaptureOptions; Docker mode forwards --variables to the in-container
  CLI invocation via dockerRunArgs.

Composition authors declare variables once on the root <html> element:

  <html data-composition-variables='[
    {"id":"title","type":"string","label":"Title","default":"Hello"}
  ]'>

and read them in any composition script:

  const { title } = window.__hyperframes.getVariables();

A render with `--variables '{"title":"Q4 Report"}'` overrides the default
without modifying the composition source. Missing keys fall through to
the declared defaults, so dev preview and CLI renders without --variables
behave identically.

This is PR 1 of a 4-PR stack. Sub-comp per-instance scoping (carrying
host data-variable-values through the inlined sub-comp's getVariables()
call) lands in PR 2; schema validation and lint in PR 3; skill / scaffold
distribution in PR 4.

Tests: 9 new unit tests for getVariables() (jsdom), 11 new CLI tests
covering parseVariablesArg validation paths and Docker passthrough,
2 new dockerRunArgs assertions for the --variables flag. All existing
tests green (core 611, cli 208, engine 519).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify
Copy link
Copy Markdown

mintlify Bot commented May 3, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
hyperframes 🟢 Ready View Preview May 3, 2026, 12:19 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

- core/runtime/getVariables.ts: collapse the noisy three-step type-guard
  re-cast into a single `Record<string, unknown>` narrow with early-continue
  guards. Same behaviour, ~6 lines shorter.
- cli/commands/render.ts: separate VariablesParseError from UI strings.
  parseVariablesArg now returns a kind-discriminated error
  (`conflict | read-error | parse-error | shape-error`) and the wrapper
  resolveVariablesArg owns the title/message mapping via
  `variablesErrorMessage`. Keeps the parser pure of presentation strings.
- cli/commands/render.test.ts: lift the `await import("./render.js")` into
  a `beforeAll`, add a typed `expectErr` helper, assert on the structured
  error kind instead of message-string regexes. Same coverage, less noise.
- engine/services/frameCapture.ts: replace the `as unknown as { ... }`
  double-cast with a single named `WindowWithVariables` alias inside the
  page closure.

All affected suites green (core getVariables 9, cli render 12, cli
dockerRunArgs 13).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jrusso1020 added a commit that referenced this pull request May 3, 2026
…stack

End-to-end Docker regression test that exercises the full variables
chain: meta.json renderConfig.variables → harness → createRenderJob →
RenderConfig → CaptureOptions → engine evaluateOnNewDocument →
window.__hfVariables → getVariables() → DOM text → rendered pixels.

Fixture (packages/producer/tests/variables-prod/):
- src/index.html: composition with three declared variables (title,
  subtitle, bgColor) read via window.__hyperframes.getVariables() and
  rendered as positioned text on a colored background. No animation —
  keeps the regression frame-stable so it isolates "did the variables
  flow through?" from motion concerns.
- meta.json: tags ["variables", "composition"] (runs in the existing
  fast shard's tag filter), renderConfig.variables provides override
  values the baseline reflects ("Override Title", "Override subtitle",
  #0a3d62). Defaults would produce a visibly different frame, so a
  failing baseline that reflects defaults means the variables didn't
  propagate.
- output/output.mp4: Docker-generated baseline per the project's
  CLAUDE.md golden-baseline rule.

Harness change (packages/producer/src/regression-harness.ts):
- TestMetadata.renderConfig gains an optional variables field,
  validated as a JSON object in the meta.json validator.
- The createRenderJob call site forwards renderConfig.variables to
  RenderConfig.variables, which the engine already consumes via
  evaluateOnNewDocument (PR #600).

Verified: docker:test variables-prod passes 100/100 visual checkpoints
and audio correlation 1.000 against the committed baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jrusso1020 added a commit that referenced this pull request May 3, 2026
…stack

End-to-end Docker regression test that exercises the full variables
chain: meta.json renderConfig.variables → harness → createRenderJob →
RenderConfig → CaptureOptions → engine evaluateOnNewDocument →
window.__hfVariables → getVariables() → DOM text → rendered pixels.

Fixture (packages/producer/tests/variables-prod/):
- src/index.html: composition with three declared variables (title,
  subtitle, bgColor) read via window.__hyperframes.getVariables() and
  rendered as positioned text on a colored background. No animation —
  keeps the regression frame-stable so it isolates "did the variables
  flow through?" from motion concerns.
- meta.json: tags ["variables", "composition"] (runs in the existing
  fast shard's tag filter), renderConfig.variables provides override
  values the baseline reflects ("Override Title", "Override subtitle",
  #0a3d62). Defaults would produce a visibly different frame, so a
  failing baseline that reflects defaults means the variables didn't
  propagate.
- output/output.mp4: Docker-generated baseline per the project's
  CLAUDE.md golden-baseline rule.

Harness change (packages/producer/src/regression-harness.ts):
- TestMetadata.renderConfig gains an optional variables field,
  validated as a JSON object in the meta.json validator.
- The createRenderJob call site forwards renderConfig.variables to
  RenderConfig.variables, which the engine already consumes via
  evaluateOnNewDocument (PR #600).

Verified: docker:test variables-prod passes 100/100 visual checkpoints
and audio correlation 1.000 against the committed baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jrusso1020 jrusso1020 merged commit 03b82e6 into main May 4, 2026
56 checks passed
@jrusso1020 jrusso1020 deleted the feat/get-variables branch May 4, 2026 19:41
jrusso1020 added a commit that referenced this pull request May 4, 2026
…stack

End-to-end Docker regression test that exercises the full variables
chain: meta.json renderConfig.variables → harness → createRenderJob →
RenderConfig → CaptureOptions → engine evaluateOnNewDocument →
window.__hfVariables → getVariables() → DOM text → rendered pixels.

Fixture (packages/producer/tests/variables-prod/):
- src/index.html: composition with three declared variables (title,
  subtitle, bgColor) read via window.__hyperframes.getVariables() and
  rendered as positioned text on a colored background. No animation —
  keeps the regression frame-stable so it isolates "did the variables
  flow through?" from motion concerns.
- meta.json: tags ["variables", "composition"] (runs in the existing
  fast shard's tag filter), renderConfig.variables provides override
  values the baseline reflects ("Override Title", "Override subtitle",
  #0a3d62). Defaults would produce a visibly different frame, so a
  failing baseline that reflects defaults means the variables didn't
  propagate.
- output/output.mp4: Docker-generated baseline per the project's
  CLAUDE.md golden-baseline rule.

Harness change (packages/producer/src/regression-harness.ts):
- TestMetadata.renderConfig gains an optional variables field,
  validated as a JSON object in the meta.json validator.
- The createRenderJob call site forwards renderConfig.variables to
  RenderConfig.variables, which the engine already consumes via
  evaluateOnNewDocument (PR #600).

Verified: docker:test variables-prod passes 100/100 visual checkpoints
and audio correlation 1.000 against the committed baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jrusso1020 added a commit that referenced this pull request May 4, 2026
## What

Building on **PR #600** (`getVariables()` helper + `--variables` flag), this PR scopes the helper so embedded sub-compositions see their own per-instance values. The same composition source can now be embedded N times with different content via `data-variable-values` on each host.

This is **PR 2 of a 4-PR stack** — based on `feat/get-variables`. Will retarget to `main` after PR #600 merges.

## Why

`data-variable-values` was already documented as the per-instance attribute for nested compositions, but readers had to hand-roll `JSON.parse(host.dataset.variableValues)` and there was no scoping system — every sub-comp script had to re-implement the same pattern. This PR routes the values to a scoped `getVariables()` so authors use one API in both top-level and sub-comp contexts.

## How

```
[host element]                                    [sub-comp source HTML]
data-variable-values='{...}'        +    <html data-composition-variables='[...]'>
                                                  ^ declared defaults
        ↓                                                  ↓
        └──────────── compositionLoader ────────────────────┘
                            ↓
            window.__hfVariablesByComp[compId] = merged
                            ↓
                  scripts wrapped by compositionScoping
                            ↓
            __hyperframes.getVariables() → scoped lookup
```

Three small surgical changes:

- **`compositionLoader.ts`** — before injecting wrapped scripts, parse the host's `data-variable-values` JSON, merge it over `readDeclaredDefaults(doc.documentElement)` (reused from PR 1's runtime helper), and write the result to `window.__hfVariablesByComp[compositionId]`. Skipped when both sides are empty so the table only grows when there's actual data. Inline templates (no separate `<html>` root) get host overrides only.

- **`compositionScoping.ts`** — wrapper IIFE now takes a fourth parameter `__hyperframes` alongside the existing scoped `document` / `gsap` / `window`. Same shadowing pattern that already works for the other three. The scoped `__hyperframes` overrides `getVariables` to read from `__hfVariablesByComp[__hfCompId]` and returns a fresh object each call so script mutations don't leak into the shared table.

- **`getVariables.ts`** — `readDeclaredDefaults` becomes a named export (was a private helper) so the loader reuses the exact same defaults-extraction logic the top-level helper uses.

Top-level scripts that aren't wrapped by `compositionScoping` keep calling `window.__hyperframes.getVariables()` and get the unscoped path from PR 1 (declared defaults + CLI `--variables` override).

## Test plan

- [x] Unit tests added/updated
  - **3 new `compositionScoping.test.ts`** — scoped `getVariables` invocation routes to `__hfVariablesByComp[compId]`; missing-entry returns `{}`; mutation of the returned object doesn't leak into the shared table.
  - **5 new `compositionLoader.test.ts`** — merge order (host overrides win); declared-only path when host has no `data-variable-values`; empty-skip when neither side has data; invalid-host-JSON falls through to declared defaults; per-instance scoping across two hosts that share a source.
  - **3 new `getVariables.test.ts`** — covering the newly-public `readDeclaredDefaults` export (null root, valid attribute, invalid JSON / non-array).
  - All 622 existing core tests green.
- [x] Manual flow walkthrough — host with `data-variable-values='{"title":"Pro"}'` → sub-comp's `__hyperframes.getVariables()` returns `{title:"Pro", ...declaredDefaults}`. Two hosts pointing at the same source with different overrides → each script sees its own values.
- [x] Documentation updated
  - `docs/concepts/compositions.mdx` — switched the sub-comp example from `JSON.parse(host.dataset.variableValues)` to `__hyperframes.getVariables()`; added declared-defaults pattern and per-instance scoping note.
  - `docs/concepts/data-attributes.mdx` — clarified per-instance behavior on `data-variable-values`.

## Backwards compatibility

Fully backwards compatible. Compositions that read `host.dataset.variableValues` directly keep working — the host attribute is still set as before. The new path is a strict addition.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
jrusso1020 added a commit that referenced this pull request May 4, 2026
…stack

End-to-end Docker regression test that exercises the full variables
chain: meta.json renderConfig.variables → harness → createRenderJob →
RenderConfig → CaptureOptions → engine evaluateOnNewDocument →
window.__hfVariables → getVariables() → DOM text → rendered pixels.

Fixture (packages/producer/tests/variables-prod/):
- src/index.html: composition with three declared variables (title,
  subtitle, bgColor) read via window.__hyperframes.getVariables() and
  rendered as positioned text on a colored background. No animation —
  keeps the regression frame-stable so it isolates "did the variables
  flow through?" from motion concerns.
- meta.json: tags ["variables", "composition"] (runs in the existing
  fast shard's tag filter), renderConfig.variables provides override
  values the baseline reflects ("Override Title", "Override subtitle",
  #0a3d62). Defaults would produce a visibly different frame, so a
  failing baseline that reflects defaults means the variables didn't
  propagate.
- output/output.mp4: Docker-generated baseline per the project's
  CLAUDE.md golden-baseline rule.

Harness change (packages/producer/src/regression-harness.ts):
- TestMetadata.renderConfig gains an optional variables field,
  validated as a JSON object in the meta.json validator.
- The createRenderJob call site forwards renderConfig.variables to
  RenderConfig.variables, which the engine already consumes via
  evaluateOnNewDocument (PR #600).

Verified: docker:test variables-prod passes 100/100 visual checkpoints
and audio correlation 1.000 against the committed baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jrusso1020 added a commit that referenced this pull request May 4, 2026
## What

Adds two lint rules + render-time validation for the variable system shipped in PR #600 + PR #601. Authors get fast feedback when JSON is malformed, declarations are missing required fields, or `--variables` values don't match the declared schema.

This is **PR 3 of a 4-PR stack** — based on `feat/get-variables-subcomp` (PR #601). Will retarget to `main` after PRs 1 + 2 merge.

## Why

The runtime today silently masks several classes of mistake:
- A typo in `data-variable-values` JSON makes the parser return `{}` and the script reads stale declared defaults, with no visible signal.
- A typo in a `data-composition-variables` declaration (missing `id`, wrong `type`) gets silently filtered out.
- `--variables '{"titel":"x"}'` instead of `"title"` renders fine and produces the wrong output.

These are exactly the cases lint + schema validation are good at catching.

## How

**Lint rules** (`packages/core/src/lint/rules/composition.ts`):
- `invalid_variable_values_json` — host's `data-variable-values` must parse as a JSON object.
- `invalid_composition_variables_declaration` — root `<html>`'s `data-composition-variables` must parse as an array of objects with `id`, `type` (one of string/number/color/boolean/enum), `label`, and `default`. Per-entry findings report which fields are missing or invalid.

Both rules read via a new `readJsonAttr` helper in `lint/utils.ts`. The existing `readAttr`'s regex `["']([^"']+)["']` truncates JSON-in-attribute values at the first internal quote — `data-variable-values='{"x":"y"}'` would capture only `{`. The new helper alternates double-vs-single-quoted branches with quote-specific char classes. A second helper `findHtmlTag` returns the `<html>` open tag (where `data-composition-variables` lives), distinct from `findRootTag` which returns the first in-body composition element.

**Render-time validation** (`packages/core/src/runtime/validateVariables.ts`):
- `validateVariables(values, declarations)` returns a structured array of `VariableValidationIssue`s — `undeclared`, `type-mismatch`, or `enum-out-of-range`. Pure / sync; works in any environment.
- `formatVariableValidationIssue` renders one-line user-facing strings for CLI output.
- Both exported from `@hyperframes/core`.

**CLI integration** (`packages/cli/src/commands/render.ts`):
- New `--strict-variables` flag. Default: print warnings, continue. Strict: print warnings, exit 1.
- New `validateVariablesAgainstProject(indexPath, values)` helper reads the project's `index.html`, pulls the declared schema via `extractCompositionMetadata`, validates the CLI payload. Uses the existing `ensureDOMParser` polyfill (same pattern as `compositions.ts`).

## Test plan

- [x] Unit tests added/updated
  - **11 `validateVariables` unit tests** — happy path, undeclared keys, type mismatches (every type), enum range, multi-issue aggregation, formatter output.
  - **11 `composition.test.ts` cases** — both lint rules: parse errors, shape errors, per-entry validation, unknown types, positive cases for valid declarations.
  - **5 `render.test.ts` cases** — `validateVariablesAgainstProject`: no-declarations, happy path, undeclared, type-mismatch, missing-file.
  - All existing tests green: core 646, cli 213.
- [x] Manual flow walkthrough
  - `--variables '{"title":"x"}'` against an index that declares `title` as string → no warnings.
  - `--variables '{"count":"three"}'` against `count: number` → warning printed, render continues.
  - Same with `--strict-variables` → exit 1 before render starts.
- [x] Documentation updated
  - `docs/packages/cli.mdx` — added `--strict-variables` flag row.

## Backwards compatibility

Fully additive. Existing compositions emit zero new lint findings (the rules only fire on malformed JSON or invalid declarations, which the runtime would have silently dropped anyway). Existing `hyperframes render` invocations behave identically without the new flag.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
jrusso1020 added a commit that referenced this pull request May 4, 2026
…stack

End-to-end Docker regression test that exercises the full variables
chain: meta.json renderConfig.variables → harness → createRenderJob →
RenderConfig → CaptureOptions → engine evaluateOnNewDocument →
window.__hfVariables → getVariables() → DOM text → rendered pixels.

Fixture (packages/producer/tests/variables-prod/):
- src/index.html: composition with three declared variables (title,
  subtitle, bgColor) read via window.__hyperframes.getVariables() and
  rendered as positioned text on a colored background. No animation —
  keeps the regression frame-stable so it isolates "did the variables
  flow through?" from motion concerns.
- meta.json: tags ["variables", "composition"] (runs in the existing
  fast shard's tag filter), renderConfig.variables provides override
  values the baseline reflects ("Override Title", "Override subtitle",
  #0a3d62). Defaults would produce a visibly different frame, so a
  failing baseline that reflects defaults means the variables didn't
  propagate.
- output/output.mp4: Docker-generated baseline per the project's
  CLAUDE.md golden-baseline rule.

Harness change (packages/producer/src/regression-harness.ts):
- TestMetadata.renderConfig gains an optional variables field,
  validated as a JSON object in the meta.json validator.
- The createRenderJob call site forwards renderConfig.variables to
  RenderConfig.variables, which the engine already consumes via
  evaluateOnNewDocument (PR #600).

Verified: docker:test variables-prod passes 100/100 visual checkpoints
and audio correlation 1.000 against the committed baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jrusso1020 added a commit that referenced this pull request May 4, 2026
…stack

End-to-end Docker regression test that exercises the full variables
chain: meta.json renderConfig.variables → harness → createRenderJob →
RenderConfig → CaptureOptions → engine evaluateOnNewDocument →
window.__hfVariables → getVariables() → DOM text → rendered pixels.

Fixture (packages/producer/tests/variables-prod/):
- src/index.html: composition with three declared variables (title,
  subtitle, bgColor) read via window.__hyperframes.getVariables() and
  rendered as positioned text on a colored background. No animation —
  keeps the regression frame-stable so it isolates "did the variables
  flow through?" from motion concerns.
- meta.json: tags ["variables", "composition"] (runs in the existing
  fast shard's tag filter), renderConfig.variables provides override
  values the baseline reflects ("Override Title", "Override subtitle",
  #0a3d62). Defaults would produce a visibly different frame, so a
  failing baseline that reflects defaults means the variables didn't
  propagate.
- output/output.mp4: Docker-generated baseline per the project's
  CLAUDE.md golden-baseline rule.

Harness change (packages/producer/src/regression-harness.ts):
- TestMetadata.renderConfig gains an optional variables field,
  validated as a JSON object in the meta.json validator.
- The createRenderJob call site forwards renderConfig.variables to
  RenderConfig.variables, which the engine already consumes via
  evaluateOnNewDocument (PR #600).

Verified: docker:test variables-prod passes 100/100 visual checkpoints
and audio correlation 1.000 against the committed baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jrusso1020 added a commit that referenced this pull request May 4, 2026
… (PR 5/5) (#604)

## What

End-to-end Docker regression test that exercises the **full variables chain** shipped in PRs #600-#603. Closes the loop between unit-tested seams and the actual rendered output.

This is **PR 5 of 5**, the regression cap. Stacked on `feat/get-variables-skills` (PR #603).

## Why

PRs 1-4 ship unit tests that cover the seams independently:
- Engine: \`evaluateOnNewDocument\` injection (mocked Puppeteer)
- Helper: \`getVariables()\` merge (jsdom)
- CLI: \`parseVariablesArg\` validation (pure function)
- Loader: \`__hfVariablesByComp\` population (jsdom)
- Validator: \`validateVariables\` type-checking (pure)

But none of those check that the chain actually works front-to-back inside the production Chrome+ffmpeg+harness combo. A type signature change on \`CaptureOptions.variables\`, a regression in \`evaluateOnNewDocument\` ordering, a bug in the runtime helper's attribute parsing — any of those could pass unit tests and silently render the wrong text. This regression catches it.

## How

**Fixture** (\`packages/producer/tests/variables-prod/\`):
- \`src/index.html\` — composition with three declared variables (\`title\`, \`subtitle\`, \`bgColor\`) read via \`window.__hyperframes.getVariables()\` and rendered as positioned text on a colored background. **No animation** — keeps the regression frame-stable so it isolates "did the variables flow through?" from motion concerns.
- \`meta.json\` — tags \`[\"variables\", \"composition\"]\` (runs in the existing fast shard's tag filter, no workflow YAML changes needed). \`renderConfig.variables\` provides override values that the baseline reflects (\"Override Title\", \"Override subtitle\", \`#0a3d62\`). **If variables don't propagate**, the rendered frame shows declared defaults (\"Default Title\", black) — visibly different from the baseline, so PSNR fails on dozens of frames.
- \`output/output.mp4\` — Docker-generated baseline per the project's CLAUDE.md golden-baseline rule. Host renders drift across Chrome/font versions and would fail PSNR even on green code.
- \`src/silence.wav\` — copied from \`missing-host-comp-id\`'s silence track to satisfy the audio-correlation check.

**Harness change** (\`packages/producer/src/regression-harness.ts\`):
- \`TestMetadata.renderConfig\` gains an optional \`variables: Record<string, unknown>\` field, validated as a JSON object (not array, not null) in the \`meta.json\` validator.
- The \`createRenderJob\` call site forwards \`renderConfig.variables\` to \`RenderConfig.variables\`, which the engine already consumes via \`evaluateOnNewDocument\` (PR #600).

## Test plan

- [x] **\`docker:test variables-prod\` PASSED** — 100/100 visual checkpoints, audio correlation 1.000.
- [x] **Defeated the bug it's meant to catch** — generated a baseline against an old image (without the harness change) and confirmed it shows defaults. After the harness change + image rebuild, the baseline correctly shows overrides.
- [x] **CI auto-includes** — fast shard's \`--exclude-tags slow,render-compat,hdr\` lets \`[variables, composition]\` through. No \`.github/workflows/regression.yml\` edits needed.

## Backwards compatibility

Additive. Existing fixtures don't set \`renderConfig.variables\` and behave identically. The harness validator only fires on the new field if it's present.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants