Skip to content

fix: isolate duplicate sub-composition instances#561

Merged
miguel-heygen merged 1 commit intomainfrom
fix/nested-composition-instance-scope
Apr 29, 2026
Merged

fix: isolate duplicate sub-composition instances#561
miguel-heygen merged 1 commit intomainfrom
fix/nested-composition-instance-scope

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

Problem

Closes #556.

Embedding the same external sub-composition twice with the same data-composition-id left both sibling instances sharing the author-facing selector scope. CSS rules and classic scripts that targeted [data-composition-id="scene"] could resolve against the wrong sibling or both siblings after bundling.

What this fixes

  • Assigns stable runtime composition ids (<id>__hf<n>) when a bundled document contains duplicate external sub-composition hosts for the same id.
  • Preserves the author-facing id in data-hf-original-composition-id for debugging.
  • Rewrites hoisted CSS and scoped script selector normalization to the per-instance runtime selector.
  • Proxies window.__timelines inside scoped sub-composition scripts so window.__timelines["scene"] = tl registers against the runtime instance id.
  • Adds a bundler regression test for two sibling instances of the same external sub-composition.

Root cause

The bundler scoped CSS and script queries to the composition id string itself. When two hosts used the same id, both instances still shared [data-composition-id="scene"]; the script wrapper also looked up the first matching root and timeline registration reused the same key.

Verification

Local checks

  • bun run --filter @hyperframes/core test src/compiler/htmlBundler.test.ts
  • bun run build:hyperframes-runtime
  • bun run --filter @hyperframes/core typecheck
  • bunx oxlint packages/core/src/compiler/compositionScoping.ts packages/core/src/compiler/htmlBundler.ts packages/core/src/compiler/htmlBundler.test.ts
  • bunx oxfmt --check packages/core/src/compiler/compositionScoping.ts packages/core/src/compiler/htmlBundler.ts packages/core/src/compiler/htmlBundler.test.ts

Browser verification

  • Generated a bundled repro with two sibling scene instances from /tmp/hf-instance-scope-qa.
  • Served the bundled HTML at http://127.0.0.1:8806/instance-scope-bundled.html.
  • Verified via agent-browser eval that the DOM contains scene__hf1 and scene__hf2, the original [data-composition-id="scene"] .title selector is gone, and timeline keys are main, scene__hf1, and scene__hf2.
  • Screenshot: qa-artifacts/issue-556/instance-scope-browser.png
  • Recording: qa-artifacts/issue-556/instance-scope-browser.webm

Notes

The QA artifacts are local only and not included in the PR.

Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — sophisticated but correct fix for issue #557.

Approach: pre-count [data-composition-src] hosts by composition ID; for any duplicates, mint runtime-unique ids (scene__hf1, scene__hf2) on the host elements (preserving the original via data-hf-original-composition-id). CSS scoping then matches the docs pattern [data-composition-id="<original>"] and rewrites it to the unique runtime scope. The script wrapper gets a __hfTimelineRegistryProxy that aliases window.__timelines["scene"] reads/writes to window.__timelines["scene__hf1"] so authored code keeps using the original ID transparently.

Symmetric proof: reverted only compositionScoping.ts + htmlBundler.ts while keeping the new test → "isolates sibling instances of the same external sub-composition" fails red. Restoring the fix → 562/562 pass.

One observation, non-blocking: the pre-pass only counts hosts with data-composition-src. If a project mixes a data-composition-src-loaded instance with an inline <template> instance sharing the same composition id (unusual authoring shape), the inline one wouldn't be deduplicated. Same for two [data-composition-id="x"] divs without sub-composition mounts. Probably fine to leave for now since both are odd patterns the new lint rule from #562 can warn against.

— Review by Rames Jusso

@miguel-heygen miguel-heygen merged commit c196b76 into main Apr 29, 2026
41 checks passed
@miguel-heygen miguel-heygen deleted the fix/nested-composition-instance-scope branch April 29, 2026 15:53
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.

Nested compositions: sibling instances share CSS scope under attribute selectors

2 participants