Skip to content

fix: hold external sub-compositions through host duration#917

Merged
miguel-heygen merged 1 commit into
heygen-com:mainfrom
puneetdixit200:fix/subcomposition-duration-hold
May 17, 2026
Merged

fix: hold external sub-compositions through host duration#917
miguel-heygen merged 1 commit into
heygen-com:mainfrom
puneetdixit200:fix/subcomposition-duration-hold

Conversation

@puneetdixit200

Copy link
Copy Markdown
Contributor

Summary

  • Keep data-composition-src hosts visible for their authored data-duration even when the child GSAP timeline is
    shorter.
  • Preserve legacy live-duration clamping for non-external child compositions.
  • Add a regression test covering a 3s external host slot with a 1s child timeline.

Verification

  • bun run --filter @hyperframes/core test:hyperframe-runtime-ci
  • bun run --filter @hyperframes/core test
  • bun run --filter @hyperframes/core typecheck
  • bunx oxfmt --check packages/core/src/runtime/init.ts packages/core/src/runtime/init.test.ts
  • `bunx oxlint packages/core/src/runtime/init.ts packages/core/src/runtime

Copilot AI review requested due to automatic review settings May 17, 2026 09:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates runtime visibility duration clamping so external composition hosts remain visible for their authored slot duration even when their child timeline is shorter, and adds a regression test for that behavior.

Changes:

  • Adjust visibility window calculation to skip Math.min(authoredDuration, liveTimelineDuration) for data-composition-src hosts.
  • Refresh related inline comments around stripping timing attrs.
  • Add a unit test covering external composition host visibility through authored duration.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/core/src/runtime/init.ts Updates visibility-duration reconciliation for external composition hosts.
packages/core/src/runtime/init.test.ts Adds test ensuring external composition hosts stay visible through authored duration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1338 to +1349
const usesExternalCompositionSlot = rawNode.hasAttribute("data-composition-src");

// Generic child compositions retain legacy behavior and respect both
// the authored parent clip window and the live child timeline duration.
// External composition hosts render into an authored slot, so a shorter
// child timeline should hold its final state through that slot.
if (
duration != null &&
duration > 0 &&
liveDuration != null &&
!usesExternalCompositionSlot
) {
Comment on lines +198 to +205
initSandboxRuntimeModular();
await new Promise<void>((resolve) => window.setTimeout(resolve, 0));

const player = (
window as Window & {
__player?: { renderSeek: (timeSeconds: number) => void };
}
).__player;

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Real bug, clean fix. APPROVE.

The problem: When a sub-composition is loaded via data-composition-src with a host data-duration="3" but its child GSAP timeline is only 1s long, the visibility system clamps to Math.min(3, 1) = 1 — the sub-composition vanishes 2s early. The parent authored a 3s window; the child's final frame should hold through it.

Why the fix is correct: The duration derivation path is:

  1. Stripping phase preserves data-duration as private data-hf-authored-duration before removing it
  2. resolveDurationForElement() reads the private attr (defaults to includeAuthoredTimingAttrs: true) → returns the authored 3s
  3. The new !usesExternalCompositionSlot guard skips the Math.min(duration, liveDuration) clamp for external hosts
  4. data-composition-src is never stripped, so rawNode.hasAttribute("data-composition-src") is reliable at this point in the lifecycle
  5. GSAP { paused: true } timelines hold their final state when seeked past their end — so the visual hold is correct

Legacy behavior preserved: Inline child compositions (without data-composition-src) still clamp via Math.min — the !usesExternalCompositionSlot condition is false for them.

Test coverage: The regression test sets up the exact scenario (3s host, 1s child, seek to t=2) and asserts visibility. Good.

@miguel-heygen miguel-heygen merged commit 551607e into heygen-com:main May 17, 2026
43 checks passed
miguel-heygen added a commit that referenced this pull request May 17, 2026
PR #917 fixed visibility clamping for external sub-compositions in
preview mode by checking data-composition-src. However, the producer's
htmlCompiler strips that attribute during inlining without setting the
data-composition-file marker that the core bundler sets. This caused
the runtime to still clamp duration to Math.min(authored, live) in
rendered output.

Two fixes:
- Runtime: also check data-composition-file (set by the core bundler
  after inlining)
- Producer: set data-composition-file before removing
  data-composition-src, matching the core bundler's behavior

Closes #911

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
miguel-heygen added a commit that referenced this pull request Jun 17, 2026
A sub-composition mount whose data-duration ends before the host
composition's window leaves its slot blank for the remainder. The
runtime behavior is correct (data-duration is the slot's visible window
and takes precedence), but a full-bleed sub-composition shorter than the
composition is almost always an authoring mistake that fails silently
(issue #1540).

Add the subcomposition_blanks_before_host rule, scoped narrowly to the
high-signal shape — a sole/dominant external mount starting at ~0 whose
window ends before the host's — so it stays silent on intentional short
clips. Document the slot-window semantics in the sub-compositions
reference, distinguishing the hold-through-slot case (#911/#917) from
the blank-when-shorter-than-host case.
miguel-heygen added a commit that referenced this pull request Jun 17, 2026
A sub-composition mount whose data-duration ends before the host
composition's window leaves its slot blank for the remainder. The
runtime behavior is correct (data-duration is the slot's visible window
and takes precedence), but a full-bleed sub-composition shorter than the
composition is almost always an authoring mistake that fails silently
(issue #1540).

Add the subcomposition_blanks_before_host rule, scoped narrowly to the
high-signal shape — a sole/dominant external mount starting at ~0 whose
window ends before the host's — so it stays silent on intentional short
clips. Document the slot-window semantics in the sub-compositions
reference, distinguishing the hold-through-slot case (#911/#917) from
the blank-when-shorter-than-host case.
miguel-heygen added a commit that referenced this pull request Jun 17, 2026
…1542)

A sub-composition mount whose data-duration ends before the host
composition's window leaves its slot blank for the remainder. The
runtime behavior is correct (data-duration is the slot's visible window
and takes precedence), but a full-bleed sub-composition shorter than the
composition is almost always an authoring mistake that fails silently
(issue #1540).

Add the subcomposition_blanks_before_host rule, scoped narrowly to the
high-signal shape — a sole/dominant external mount starting at ~0 whose
window ends before the host's — so it stays silent on intentional short
clips. Document the slot-window semantics in the sub-compositions
reference, distinguishing the hold-through-slot case (#911/#917) from
the blank-when-shorter-than-host case.
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.

3 participants