fix(producer): bound nested media by a host's data-duration when it has no data-end - #3975
miga-heygen wants to merge 1 commit into
Conversation
…as no data-end The runtime hides a slot's descendants at data-start + data-duration, and the compiler only stamps data-end on media tags, so a composition host authored with data-duration was unbounded for the media planner: audio and video nested in a shortened scene kept playing over the next scene. The template-wrapped compile test asserted that unbounded end (a 4s clip in a 2s slot ending at 6); it now expects the slot end. The host end now comes from the runtime's own resolveAuthoredTimingWindow rather than a local re-implementation, so precedence matches the runtime exactly: a positive data-duration wins over a conflicting data-end, and a zero or negative data-duration is treated as absent (falling back to data-end, or unbounded) instead of collapsing the host window to its start and dropping every nested clip. Tests cover the conflicting-attribute case and the non-positive-duration fallback. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
terencecho
left a comment
There was a problem hiding this comment.
APPROVE at a1f32d44 — planner's host-window resolver now delegates to the runtime's own; divergence removed by construction
Clean fix. 3 files, +96/-14, one commit co-authored by valeriangalliat + miguel-heygen (bot-carrier repackage of #3965 with signed commits to satisfy the repo's signed-commit ruleset).
Head verified: a1f32d4414d2e340b5683359d87aa3f99db12b26.
The change is the right shape
resolveHostEnd in packages/producer/src/services/renderMediaCollector.ts:44-56 now returns resolveAuthoredTimingWindow({start, duration, end})?.end ?? null, imported from @hyperframes/core. That is the same resolver the runtime uses for resolveDurationForElement — so the planner's host-close window and the runtime's descendant-hide window cannot drift by construction. The previous local parseNumeric (Number.parseFloat) is deleted alongside the divergent read-order (data-end first) and the data-duration="0" mishandling (which collapsed the window to hostStart and dropped every nested clip).
The docstring update on resolveHostWindow (:72-81) correctly cites the shared resolver as the new source-of-truth for the host end, and the doc block on resolveHostEnd names all three fold rules: positive-duration wins over conflicting end, non-positive-duration falls back to end, and end-before-start is ignored.
Test coverage matches the fix rules exactly
Four new tests in renderMediaCollector.test.ts:20-90 pin every rule the fix relies on:
closes a host authored with data-duration but no data-end— the primary scenario (#3964): audio in a 2s-slot-over-4s-scene closes at 2s not 4s; a video whose own window is entirely after 2s is dropped from the plan; an id-refdata-start="hook"chains correctly.prefers data-duration over a conflicting data-end, like the runtime— precedence test:data-start=2 data-duration=2 data-end=6→ close at 4 (duration wins).it.each(["0", "-1"])— non-positive-duration fallback: unbounded when nodata-end, else fall back todata-end; nested clips are NOT dropped.ignores a data-end at or before the host start— end-before-start is treated as absent; window stays unbounded rather than collapsing.
The amended existing test in the same file (preserves an explicitly marked legacy-global media window) correctly reflects the new behavior: open-ended audio tracks close with the host (start 2 + duration 6 → end 8) rather than staying unbounded. The amended htmlCompiler.test.ts:1229-1238 template-wrapped sub-composition test correctly expects the 4s clip to close at end: 4 with its 2s host (was end: 6, which was the unbounded-scene-file bug).
Mutation checks (per PR body, verified by test shape)
- Reverting to end-first precedence → the
prefers data-duration over conflicting data-endtest fails (assertsend: 4, mutation would give6). - Removing the non-positive-duration guard → both
it.eachcases fail (open-clip would collapse tohostStartinstead of staying unbounded atend: 4; capped-clip would collapse instead of falling back toend: 3). - Reading
data-endraw without the start-past-end guard → theignores a data-end at or before the host starttest fails (window would collapse from unbounded to 1, dropping the clip that ends at 5).
Each mutation → distinct red test. Coverage is honest.
CI
At head: 43/43 checks green, zero pending, zero failing. reviewDecision: REVIEW_REQUIRED reflects that this stamp is the one that clears the gate.
Substance is Val's fix (valeriangalliat) tightened for runtime-precedence parity and non-positive-duration handling, co-authored with Miguel. Miga carries it as a signed-commit repackage of #3965. Ship it.
— Review by tai (pr-review)
What
The render-media planner now closes a composition host (
data-composition-fileelement) at the same window the runtime uses to hide its descendants.resolveHostEndinpackages/producer/src/services/renderMediaCollector.tsdelegates to the runtime's ownresolveAuthoredTimingWindowfrom@hyperframes/core: a positivedata-durationcloses the host atstart + duration;data-endapplies only whendata-durationis absent or non-positive, and only when it lies past the host start.Why
The compiler only stamps
data-endon media tags, so a host authored with justdata-durationwas unbounded for the media planner. Audio and video nested in a shortened scene kept playing over the next scene (#3964).The first cut of this fix read
data-endfirst anddata-durationsecond, which is the reverse of the runtime's precedence (resolveDurationForElementinpackages/core/src/runtime/startResolver.ts). It also treateddata-duration="0"as a real duration, which collapsed the host window to its start and dropped every nested clip. Sharing the runtime's resolver instead of re-implementing the rule removes both divergences and the localparseNumericcopy (parseFloatvs the runtime'sNumberparsing).How
resolveHostEndreturnsresolveAuthoredTimingWindow({ start, duration, end })?.end ?? null.resolveHostWindowdoc block rewritten to describe the shared rule.renderMediaCollector.test.ts:data-durationand nodata-endbounds nested media (including an id-refdata-start);data-start=2 data-duration=2 data-end=6bounds nested media at 4 (duration wins);data-duration="0"and"-1"are treated as absent: unbounded when there is nodata-end, otherwise fall back todata-end; nested clips are not dropped;data-endat or before the host start is ignored rather than collapsing the window.htmlCompiler.test.ts: the template-wrapped sub-composition test now expects the 4s clip to close with its 2s host (end: 4, was6).Verification
bun test packages/producer/src/services/renderMediaCollector.test.ts— 7 pass.bun test packages/producer/src/services/htmlCompiler.test.ts— 135 pass.bun run --cwd packages/producer typecheck— clean;oxlintandoxfmt --checkclean on the touched files.it.eachcases; readingdata-endraw fails the end-before-start test.Closes #3964
Supersedes #3965 — carries valeriangalliat's fix verbatim with signed commits so it can merge under the repo's signed-commit ruleset; adds duration-first precedence to match the runtime and a non-positive-duration guard, with tests.
— Miga