Building on PR 1's getVariables() helper, this PR routes per-instance
values into the correct sub-composition. Same composition source can
now be embedded N times with different content via data-variable-values
on each host element.
How it works:
- compositionLoader, before injecting wrapped scripts, layers the host
element's data-variable-values JSON over the sub-comp's declared
defaults (its own data-composition-variables) and writes the merged
object to window.__hfVariablesByComp[compositionId]. Skipped when
both sides are empty so the table only grows for instances that
actually carry values.
- compositionScoping's wrapper IIFE now takes a fourth parameter
__hyperframes alongside the existing scoped document/gsap/window.
The scoped __hyperframes shadows getVariables() to read from
__hfVariablesByComp[__hfCompId], returning a fresh object each call
so script mutations don't leak into the shared table.
- Top-level scripts (not wrapped by compositionScoping) keep using the
unscoped window.__hyperframes.getVariables(), which reads
data-composition-variables defaults plus the CLI override
(window.__hfVariables) — same path as PR 1.
- readDeclaredDefaults is exported from getVariables.ts so the loader
reuses the exact same defaults-extraction logic the helper uses for
the top-level path.
Inline templates (no separate <html> document root) get host overrides
only — no declared defaults — since there's no separate <html> to read
data-composition-variables from. External sub-comps fetched via
data-composition-src get the full declared defaults + host overrides
merge.
Tests: 3 new compositionScoping tests covering scoped getVariables
invocation, missing-entry fallback, and mutation isolation. 5 new
compositionLoader tests covering merge order, declared-only path,
empty-skip, invalid-host-JSON resilience, and per-instance scoping
across two hosts sharing a source. 3 new getVariables tests covering
the newly-public readDeclaredDefaults. All 622 core tests green.
Docs: docs/concepts/compositions.mdx switched its sub-comp example from
hand-rolled JSON.parse(host.dataset.variableValues) to the new
__hyperframes.getVariables() pattern. data-attributes.mdx clarifies
per-instance scoping behavior.
This is PR 2 of a 4-PR stack. PR 3 adds schema validation + lint;
PR 4 ships skill / scaffold updates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
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>