Skip to content

fix(core): align sub-composition scoping across runtime and bundler#897

Open
func25 wants to merge 1 commit into
heygen-com:mainfrom
func25:subcomp-scoping-parity
Open

fix(core): align sub-composition scoping across runtime and bundler#897
func25 wants to merge 1 commit into
heygen-com:mainfrom
func25:subcomp-scoping-parity

Conversation

@func25
Copy link
Copy Markdown
Contributor

@func25 func25 commented May 16, 2026

What

Align sub-composition scoping and identity handling across runtime and bundler

Why

Runtime and bundled output could diverge for duplicate sub-composition instances. That affected scoped roots, per-instance variables, repeat loads, and inline template mounts.

How

  • Preserve the authored inner root wrapper without keeping duplicate DOM ids.
  • Use per-instance composition ids where needed for duplicate mounts.
  • Keep scoped root aliasing working through data-hf-authored-id.
  • Align runtime and bundled sub-compositions on per-instance variable lookup with window.__hfVariablesByComp.
  • Add cleanup for stale scoped variable entries during repeat loads and removed-host cases.

Test plan

How was this tested?

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)

@func25 func25 force-pushed the subcomp-scoping-parity branch from b07bfee to 97dee97 Compare May 16, 2026 12:19
@func25 func25 force-pushed the subcomp-scoping-parity branch from 97dee97 to f51bdd1 Compare May 16, 2026 13:21
@func25 func25 marked this pull request as ready for review May 16, 2026 13:21
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

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

Review: fix(core): align sub-composition scoping across runtime and bundler

Verdict: Approve — this is a real issue with a well-structured fix.

The problem is real

When the same sub-composition mounts multiple times, the previous code had several divergence bugs between runtime and bundled output:

  • Duplicate DOM id attributesgetElementById returns the wrong element for the second instance
  • CSS scoping collision — both instances shared the same [data-composition-id] scope
  • Variable collision__hfVariablesByComp["scene"] can only hold one set of values
  • Timeline collision__timelines["scene"] overwrites the first instance

The fix

The data-hf-authored-id approach is elegant: strip the literal id (no more DOM duplicates), preserve it as a data attribute for selector compatibility, and uniquify data-composition-id per instance (scene__hf1, scene__hf2). The CSS/JS scoping rewrites #scene-root[data-hf-authored-id="scene-root"] at both compile time and runtime.

What's good

  • ~30 new tests covering bundler, runtime loader, and scoping — including repeat loads, host removal, mixed inline+external duplicates, variable cleanup, and edge cases like attribute values inside CSS selectors
  • prepareFlattenedInnerRoot correctly strips timing/composition attrs from the inner wrapper
  • replaceAuthoredRootIdSelectors parser handles edge cases well: quoted strings in attribute selectors, bracket depth tracking, CSS escape sequences
  • Stale variable cleanup when hosts are removed or re-numbered is thorough
  • The getVariables() fix to use __hfTimelineCompId instead of __hfCompId is the key per-instance variable isolation

Minor observations (non-blocking)

  1. Duplicated constantsFLATTENED_INNER_ROOT_STRIP_ATTRS is defined identically in both htmlBundler.ts and compositionLoader.ts. Consider extracting to a shared module if they drift apart. Low risk since the list is stable.

  2. Duplicated selector parserreplaceAuthoredRootIdSelectors exists as TypeScript (compile-time) and as an embedded JS string (runtime IIFE). This is inherent to the architecture, but bugs would need fixing in two places.

  3. gsap.utils.selector() behavioral change — The override now uses window.document.querySelectorAll + root.contains() filter instead of root.querySelectorAll. This is necessary (authored root IDs aren't real id attrs), but queries the full document. Fine for composition-sized DOMs; worth noting if compositions grow very large.

  4. Silent catchtry { root.querySelector(...) } catch {} in the getElementById override swallows all errors. Fine for a fallback path, but a debug-level log could help future troubleshooting.

All CI checks pass (including all 8 regression shards, perf suite, Windows, preview parity). The test coverage is thorough enough that I'm confident in the correctness.

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.

2 participants