Skip to content

feat(core): add data-props interpolation for parameterized sub-compositions#210

Closed
miguel-heygen wants to merge 3 commits intomainfrom
feat/data-props-interpolation
Closed

feat(core): add data-props interpolation for parameterized sub-compositions#210
miguel-heygen wants to merge 3 commits intomainfrom
feat/data-props-interpolation

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen commented Apr 3, 2026

What

Adds parameterized sub-compositions to HyperFrames via data-props and {{key:default}} interpolation.

A composition file can now contain mustache-style placeholders. A parent passes values via data-props JSON; the placeholders resolve at build time, render time, and in the live preview. Placeholders with defaults ({{key:default}}) resolve standalone — no parent required.

<!-- Parent: same component, 3 different instances -->
<div class="clip"
  data-composition-src="compositions/card.html"
  data-props='{"title":"Pro","price":"$29","bgColor":"#ec4899"}'
  data-start="0" data-duration="5" data-track-index="0">
</div>
<!-- compositions/card.html: renders standalone with defaults, or with parent props -->
<style>.card { background: {{bgColor:#6366f1}}; }</style>
<h2>{{title:Card Title}}</h2>
<p>{{price:$0}}/mo</p>

Why

Before this PR, every repeated element in a HyperFrames composition was hand-coded. 3 pricing cards = 3x copy-pasted HTML. 10 countdown numbers = 10 nearly-identical blocks. 4 team members = 4 duplicated structures. There was no way to write a component once and reuse it with different data.

This was the #1 pain point across 40 stress-test projects built by AI agents comparing HyperFrames to Remotion. Every agent independently flagged it. Remotion solves this naturally with React components and props — HyperFrames had no equivalent.

data-props closes that gap without adding React, TypeScript, node_modules, or a build step. It fits the existing data-* attribute model and works with the linter, renderer, and studio as-is.

The {{key:default}} syntax solves a follow-on problem: if a composition contains {{bgColor}} and is opened standalone (preview, lint, render without a parent), the CSS breaks. Inline defaults make every composition self-contained.

How

New file: interpolateProps.ts — shared interpolation engine with three context-aware functions:

  • interpolateProps() — HTML-escaped for content (XSS-safe)
  • interpolateCssProps() — raw values for CSS (entity escaping breaks CSS)
  • interpolateScriptProps() — JS-escaped for scripts (prevents string breakout)

Wired into three pipelines:

  1. htmlBundler.ts — build-time (npx hyperframes render via bundler)
  2. producer/htmlCompiler.ts — render-time (before PostCSS scoping)
  3. compositionLoader.ts — browser runtime (preview + studio)

Also:

  • Renamed data-variable-valuesdata-props (the old attribute was never shipped publicly)
  • Updated htmlParser.ts and generators/hyperframes.ts for the rename
  • Updated skill docs (SKILL.md, patterns.md) and CLI docs (compositions.md)

Test plan

  • 465/465 core tests pass (40 unit + 4 integration — 21 new)
  • Build succeeds across all packages (core, cli, producer, studio)
  • Pre-commit hooks pass (oxlint, oxfmt, typecheck, commitlint)
  • E2E: created a project with 3 parameterized pricing cards using data-props, linted (0 errors), rendered to MP4 (12s, 1920x1080, 49KB)
  • Verified CSS interpolation works with PostCSS scoping (producer pipeline)
  • Verified XSS: <script>alert(1)</script> in props is escaped to &lt;script&gt; in output
  • Verified standalone rendering: composition with {{key:default}} renders without parent data-props
  • Verified unmatched placeholders without defaults are preserved as-is

…itions

Add {{key}} mustache-style interpolation to sub-compositions, enabling
component reuse with different data. Pass a JSON object via data-props on
the host element; placeholders in the sub-composition's HTML, CSS, and
scripts are replaced at build time, render time, and in the live preview.

- New interpolateProps.ts utility with HTML-escaping (XSS-safe)
- Wired into htmlBundler (build-time), compositionLoader (runtime),
  and producer htmlCompiler (render-time)
- Renamed data-variable-values → data-props (shorter, saves tokens)
- 22 new tests (19 unit + 3 integration), 444/444 total passing
- Updated skill docs, CLI docs, and patterns with usage examples

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dering

Extend mustache interpolation to support inline defaults: {{key:fallback}}.
When a composition is rendered standalone (no parent data-props), defaults
are used so CSS stays valid, text renders, and lint/preview/render all work.

- Updated regex to capture optional :default group
- All three interpolation functions (HTML, CSS, script) support defaults
- interpolateProps/interpolateCssProps/interpolateScriptProps accept
  optional null values (standalone mode)
- 21 new tests for default behavior, 465/465 total passing
- Updated skill docs, patterns, and CLI docs with {{key:default}} examples

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@miguel-heygen miguel-heygen force-pushed the feat/data-props-interpolation branch from e3e814c to 3a0a0a2 Compare April 3, 2026 16:45
Three new lint rules for parameterized compositions:

1. invalid_data_props_json (error): Catches malformed JSON in data-props
   attributes — typos, arrays instead of objects, missing quotes.

2. mustache_placeholder_without_default (warning): Warns when sub-composition
   files use {{key}} without a default ({{key:default}}). Compositions without
   defaults break when rendered standalone (invalid CSS, raw mustache text).
   Only fires for files in compositions/ directory.

3. unused_data_props_key (warning): Detects data-props keys that don't match
   any {{placeholder}} in the referenced inline template. Catches typos like
   {"titl":"Pro"} when the template has {{title}}. Shows available placeholders
   in the fix hint. Only works for inline templates (same-file); cross-file
   validation for data-composition-src requires a multi-file lint pass.

Also adds rawSource to LintContext for rules that need the original HTML
before template unwrapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@miguel-heygen miguel-heygen force-pushed the feat/data-props-interpolation branch from 08fa61d to 628b99d Compare April 3, 2026 17:05
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