feat(core): add data-props interpolation for parameterized sub-compositions#210
Closed
miguel-heygen wants to merge 3 commits intomainfrom
Closed
feat(core): add data-props interpolation for parameterized sub-compositions#210miguel-heygen wants to merge 3 commits intomainfrom
miguel-heygen wants to merge 3 commits intomainfrom
Conversation
…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>
e3e814c to
3a0a0a2
Compare
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>
08fa61d to
628b99d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds parameterized sub-compositions to HyperFrames via
data-propsand{{key:default}}interpolation.A composition file can now contain mustache-style placeholders. A parent passes values via
data-propsJSON; the placeholders resolve at build time, render time, and in the live preview. Placeholders with defaults ({{key:default}}) resolve standalone — no parent required.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-propscloses that gap without adding React, TypeScript, node_modules, or a build step. It fits the existingdata-*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:
htmlBundler.ts— build-time (npx hyperframes rendervia bundler)producer/htmlCompiler.ts— render-time (before PostCSS scoping)compositionLoader.ts— browser runtime (preview + studio)Also:
data-variable-values→data-props(the old attribute was never shipped publicly)htmlParser.tsandgenerators/hyperframes.tsfor the renameTest plan
data-props, linted (0 errors), rendered to MP4 (12s, 1920x1080, 49KB)<script>alert(1)</script>in props is escaped to<script>in output{{key:default}}renders without parentdata-props