Repository navigation
Description
Describe the bug
When a sub-composition's data-duration ends before its host composition's data-duration ends, the sub-composition's slot blanks for the remaining time — even though
data-composition-src is present on the child div.
This is adjacent to but distinct from #911. #911's symptom was the same (slot goes black), but the root cause was the producer's htmlCompiler stripping
data-composition-src during inlining. Commit 2b46565c fixed that by making the runtime accept both data-composition-src and data-composition-file, and by having the
producer set data-composition-file. That fix is shipped in 0.6.110 — I verified the runtime bundle (dist/hyperframe.runtime.iife.js) contains
d.hasAttribute("data-composition-src")||d.hasAttribute("data-composition-file").
In my reproducer the producer is not involved — I edit index.html by hand, data-composition-src is present on the child throughout, and the slot still blanks. The trigger
is the duration mismatch itself, not a missing attribute.
Link to reproduction
Self-contained minimal reproducer below. Built on top of the unmodified npx hyperframes init decision-tree scaffold; the only edits are to index.html.
Steps to reproduce
npx [email protected] init(pickdecision-tree).- Edit
index.html— extend the root'sdata-durationfrom15to331.224, but leave the child at15.
<div id="root" data-composition-id="main" data-start="0"
data-duration="331.224" data-width="1920" data-height="1080">
<div id="decision-tree-comp"
data-composition-id="decision-tree"
data-composition-src="compositions/decision_tree.html"
data-start="0"
data-duration="15"
data-track-index="0"
data-width="1920" data-height="1080"></div>
</div>npx hyperframes render --fps 8 --quality draft --output broken.mp4- Sample frames:
for t in 7 14 20 100 200 300; do
ffmpeg -y -loglevel quiet -ss $t -i broken.mp4 -frames:v 1 -update 1 f_${t}.png
done
md5sum f_*.pngObserved (0.6.110)
| Frame at | md5 | size | content |
|---|---|---|---|
| t=7s, 14s | fc3407717aa9c33b4ded9dcbcd97a561 |
20,302 B | dotted grid (child rendering) |
| t=20s, 100s, 200s, 300s | 9c95fca010bc4b6747f415d2c1efad74 |
8,680 B | pure blank |
All four "blank" frames are byte-identical for 316 seconds. The slot goes black the instant the child's data-duration elapses and stays black until the host's ends.
Expected
Per the framing in #911 ("regardless of host data-duration"), the slot should hold its last frame for as long as the host is still alive. That's what happens when I rewrite the
child's data-duration to also be 331.224 — t=20s/100s/300s become identical to t=14s (the dotted grid stays visible), matching what I'd expect for an external sub-composition
whose timeline has settled.
Environment
[email protected](npm; tarball verified against thev0.6.110GitHub tag)- Node 22.x
- Linux (Debian-based container, headless Chromium bundled by hyperframes)
- Render mode (not preview)
Context
I caught this while empirically validating an npm pin bump in helmdeck — we wrap hyperframes in a render pack and were investigating
a related operator-visible bug where LLM-generated compositions had mismatched root/child durations. Our workaround on our side (PR
#546) rewrites the child's data-duration to match the root's whenever they started equal, which sidesteps this entirely — but I
wanted to surface the upstream behavior because it's a clean repro and the symptom matches #911's framing.
Happy to test a candidate fix against the same reproducer.
Two heads-ups when pasting:
- The triple-backticks for the inner html and bash blocks I escaped with a zero-width space (you'll see them as
instead of). Strip those zero-width chars before
pasting, OR retype the inner fences. (Or grab it from /tmp/upstream-issue-body.md` directly — that one has no escaping.) - Title: Sub-composition slot still blanks at child data-duration when host continues (after Sub-composition slot goes black after GSAP timeline ends, regardless of host data-duration #911 fix in 0.6.110)
Activity
chore(sidecar-hyperframes): bump pin 0.6.97 → 0.6.110 + blog (#547) (#…
miguel-heygen commentedon Jun 17, 2026
Thanks for the unusually thorough writeup — the repro and frame table made this quick to confirm.
I reproduced the behavior (visible at t=14s, blank at t=20s+ with a child
data-duration="15"under a 331s host), but after tracing it I don't think this is a runtime bug — it's working as designed, and it's a different case from #911.The distinction from #911:
data-duration=3). The slot outlived the animation and went black. fix: hold external sub-compositions through host duration #917 fixed that by holding the child's final frame through its owndata-duration. ✅data-duration(15) is shorter than the host (331). Here there's nothing to "hold through" — the clip's window is simply over at 15s.The rule you quoted — "
data-durationtakes precedence" — is exactly what's producing this: a clip withdata-duration="15"is a 15-second window on the timeline and ends at 15s. Holding it to the host's 331s would require ignoringdata-duration, i.e. the opposite of that rule, and would re-break the #917 contract (a correctly-sized slot must end on time even when its GSAP timeline is shorter). Your helmdeck workaround — rewriting the childdata-durationto match the root — is actually the correct authoring fix: if you want a sub-composition to fill the whole host, itsdata-durationshould span the host window.That said, you're right that it fails silently, and a single full-bleed sub-composition shorter than its host is almost always a mistake. So rather than change the runtime, #1542 surfaces it at author time:
subcomposition_blanks_before_host, that warns when a sole/dominant sub-composition mount starting at ~0 ends before the host window (with a fixHint suggesting either extendingdata-durationor covering the tail). It's deliberately narrow so it stays quiet on intentional short clips like a 15s intro followed by another clip.PR: #1542. Closing this as working-as-designed once that lands, but happy to keep iterating if the lint heuristic misses your real-world cases.
feat(lint): warn when a sub-composition slot blanks before the host
feat(lint): warn when a sub-composition slot blanks before the host
feat(lint): warn when a sub-composition slot blanks before the host (#…
feat(lint): warn when a sub-composition slot blanks before the host (h…