render: a composition slot's data-duration does not bound the media nested inside it (audio plays past the slot)

Author: valeriangalliatCreated Sep 15, 2026Updated Sep 15, 2026

Describe the bug

When a nested composition slot is authored with data-start and data-duration but no data-end, render plans the audio and video nested inside that slot to their own authored end, not to the slot's end. The runtime already hides the slot's timed descendants at start + duration (since #1662), so the picture switches to the next scene while the previous scene's sound keeps playing.

Link to reproduction

https://github.com/ArcadeHQ/hyperframes-repros/tree/patch/host-duration-bound

Steps to reproduce

  1. git checkout patch/host-duration-bound
  2. npx [email protected] check (clean)
  3. npm run render -- --debug -o out/repro.mp4 --quality draft
  4. ffmpeg -ss 2 -t 2 -i out/repro.mp4 -vn -af volumedetect -f null - and read mean_volume

The project: root 4s. Slot scene-a is data-start="0" data-duration="2" over a scene whose content is 4s and which holds <audio data-start="0" data-duration="4"> (silent until 1.5s, then a 440 Hz tone). Slot scene-b follows at 2–4s.

Expected behavior

Scene B (2–4s) is silent. The slot ended at 2s, and the runtime already treats it as closed for visibility (scene-a's data-start="2.5" element never paints).

Actual behavior

About −21 dB mean volume in 2–4s: the tone plays over scene B. compiled/summary.json in the producer .debug directory lists scene-a-sound as start 0, end 4. The frame at 3s is correctly scene B, so pixels and audio disagree about where the slot ends.

Where: packages/producer/src/services/renderMediaCollector.tsresolveHostWindow bounds a host by data-end only (the comment notes a host with just data-duration was unbounded in the older file-tree walk too). compileTimingAttrs stamps data-end on <video>/<audio> tags but not on composition hosts, so a slot authored with data-duration never gets one. Runtime visibility and the runtime media sync both honour data-duration on the host, so preview and render disagree.

Proposed fix: when a host has no data-end, bound it at resolved data-start + data-duration. PR to follow.

Environment

  • hyperframes 0.8.40 (latest)
  • Node.js v24.19.0, macOS (darwin arm64)
  • FFmpeg / FFprobe 8.1.2
  • Chrome headless shell 152.0.7977.54 (puppeteer cache)
  • npx hyperframes doctor from the repro checkout: all required checks pass; optional whisper-cpp, Kokoro, MusicGen and Docker not installed.

Additional context

With the proposed fix applied, the same render gives −91 dB (digital silence) in 2–4s and is otherwise identical.