fix: isolate duplicate sub-composition instances#561
Conversation
jrusso1020
left a comment
There was a problem hiding this comment.
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
Problem
Closes #556.
Embedding the same external sub-composition twice with the same
data-composition-idleft 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
<id>__hf<n>) when a bundled document contains duplicate external sub-composition hosts for the same id.data-hf-original-composition-idfor debugging.window.__timelinesinside scoped sub-composition scripts sowindow.__timelines["scene"] = tlregisters against the runtime instance id.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.tsbun run build:hyperframes-runtimebun run --filter @hyperframes/core typecheckbunx oxlint packages/core/src/compiler/compositionScoping.ts packages/core/src/compiler/htmlBundler.ts packages/core/src/compiler/htmlBundler.test.tsbunx oxfmt --check packages/core/src/compiler/compositionScoping.ts packages/core/src/compiler/htmlBundler.ts packages/core/src/compiler/htmlBundler.test.tsBrowser verification
sceneinstances from/tmp/hf-instance-scope-qa.http://127.0.0.1:8806/instance-scope-bundled.html.agent-browser evalthat the DOM containsscene__hf1andscene__hf2, the original[data-composition-id="scene"] .titleselector is gone, and timeline keys aremain,scene__hf1, andscene__hf2.qa-artifacts/issue-556/instance-scope-browser.pngqa-artifacts/issue-556/instance-scope-browser.webmNotes
The QA artifacts are local only and not included in the PR.