Skip to content

feat(core): declarative variable bindings — data-var-src, data-var-text, css custom props#2054

Open
jrusso1020 wants to merge 1 commit into
07-07-fix_studio_code-review_and_live-test_fixes_for_the_variables_stackfrom
07-07-feat_core_declarative_variable_bindings_data-var-src_data-var-text_css_custom_props
Open

feat(core): declarative variable bindings — data-var-src, data-var-text, css custom props#2054
jrusso1020 wants to merge 1 commit into
07-07-fix_studio_code-review_and_live-test_fixes_for_the_variables_stackfrom
07-07-feat_core_declarative_variable_bindings_data-var-src_data-var-text_css_custom_props

Conversation

@jrusso1020

@jrusso1020 jrusso1020 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

What

Eighth PR of the template-variables stack: declarative bindings — the no-script consumption channel, so connecting a variable to an element needs zero composition code.

The runtime resolves these at init and re-applies after the composition loader inlines external/template sub-compositions (their DOM and per-instance scoped values don't exist at init; the pass is idempotent):

  • data-var-src="id" — sets the element's src from the variable value (URL string or image {url}); the authored src stays as the fallback
  • data-var-text="id" — sets the element's own text; element children (nested clips, animated spans) are preserved via setOwnText semantics — a text binding never deletes animation targets
  • Every scalar variable (and a font value's family name) is applied as a --{id} CSS custom property on its composition root — the root is resolved with the same chain the SDK's findRoot uses, so a persisted inline default can never shadow a runtime override

Also in this PR (from the post-feature review round):

  • runtime/variableScope.ts — the element→values scope resolver, shared with the color-grading runtime (was duplicated)
  • Per-run scope memoization (N bound elements pay one resolution per scope)
  • lint learns the contract: new unknown_variable_binding warning (binding id not declared — a typo'd binding silently keeps the fallback), and media_missing_src downgrades to media_variable_src_no_fallback (warning) for variable-bound media, explaining the audio-extraction caveat
  • SDK usage scan counts data-var-* bindings; shared isScalarVariableValue guard so SDK/runtime/Studio can't disagree on what "scalar" means
  • Contract surfaces updated everywhere agents read them: docs/concepts/variables.mdx, docs/reference/html-schema.mdx (which previously claimed values "must be read and applied manually"), docs/concepts/data-attributes.mdx, and the hyperframes-core skill references

Why

Variables were consumable only via getVariables() script calls — "make this image replaceable" required glue code. This is the foundation for the Studio's select-an-element → make-it-dynamic gesture (#2055), and the right altitude: agents hand-authoring compositions get the same one-attribute binding.

Known follow-ups (deliberately not in this PR)

  • The bundler doesn't rebase relative URLs inside variable defaults when inlining sub-compositions (authored src attrs are rebased; a bound default like img.png from a sub-comp can 404 after bundling)
  • The render audio pipeline discovers tracks from the authored src, not the variable value — binding media with audio can produce an A/V content mismatch; the new lint warning calls this out

Test plan

  • 9 jsdom runtime tests (string/{url} sources, fallback preservation, own-text child preservation, idempotent re-apply, scalar + font-name CSS props, sub-composition scoping)
  • Lint rule tests for both new codes; SDK usage-scan binding test
  • core 1127 / lint 335 / sdk 436 / parsers 802 / studio 1392 / studio-server 260
  • Live-verified end-to-end via feat(studio): bind selected element properties to variables #2055's browser session

🤖 Generated with Claude Code

…xt, css custom props

Eighth PR of the template-variables stack: the no-script consumption
channel, so binding a variable to an element needs zero composition code.

- runtime resolves at init (values are fixed per page load — seek-safe,
  deterministic, identical in preview and render):
  - data-var-src="id" → sets the element's src from the variable value
    (URL string or image {url}); authored src stays as the fallback
  - data-var-text="id" → sets text content from a scalar value
  - every scalar variable (and a font value's family name) is applied as
    a --{id} CSS custom property on its composition root, so plain
    var(--id) CSS bindings respond to render/preview overrides instead of
    only the persisted default
- bindings resolve against the element's owning composition (same scope
  chain as the color-grading runtime: __hfVariablesByComp for inlined
  sub-comps, then the top-level merge)
- sdk: getVariableUsage counts data-var-* bindings as usage
- docs: "Declarative Bindings (No Script Required)" section in
  concepts/variables.mdx

This is the foundation for the Studio "select an element → make this
property dynamic" gesture (next PR).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jrusso1020 jrusso1020 force-pushed the 07-07-fix_studio_code-review_and_live-test_fixes_for_the_variables_stack branch from 5751d71 to e8b75b7 Compare July 8, 2026 08:00
@jrusso1020 jrusso1020 force-pushed the 07-07-feat_core_declarative_variable_bindings_data-var-src_data-var-text_css_custom_props branch from 605efea to a6b9835 Compare July 8, 2026 08:00
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.

1 participant