Describe the bug
A root timeline whose own duration is 0 — e.g. one that only holds tl.set(...) at position 0, which is exactly what Studio writes for a static resize — never renders its sets in the preview. After resizing an element in Studio the file is correct, but the element snaps back to its authored size in the preview (keeping the drag offset), so it looks like the resize was lost.
Cause: in packages/core/src/runtime/init.ts, an unusable (zero) root duration takes the createDurationFloorTimeline fallback: the root timeline is added to a new paused wrapper padded to the composition duration. The author's root was created gsap.timeline({ paused: true }) and stays paused, and a paused child is never rendered when its parent is seeked. The nested-composition path already unpauses held children (ensureChildCandidatesActive); the floor wrapper does not.
Minimal reproduction
<div id="root" data-composition-id="main" data-start="0" data-duration="2" data-width="1920" data-height="1080">
<div id="box" class="clip" data-start="0" data-duration="2" data-track-index="1"
style="position:absolute; left:100px; top:100px; width:400px; height:300px; background:tomato"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
tl.set("#box", { width: 200, height: 150 }, 0);
window.__timelines["main"] = tl;
</script>
Steps to reproduce
npx hyperframes preview on the composition above (or produce it by resizing an element in Studio in a composition without tweens).
- Look at
#box at any playhead position.
Expected behavior
#box renders at 200×150.
Actual behavior
#box stays at 400×300. In the preview iframe, __timelines.main.paused() is true, its children are never initialised, and __player.seek(t) has no effect on it. After __timelines.main.paused(false), __player.seek(0.5) renders 200×150. A single tween in the timeline (non-zero duration) avoids the fallback and the bug. hyperframes snapshot --at 0.5 of the same file renders 200×150 correctly, so only the preview runtime is affected.
Environment
hyperframes CLI 0.8.77 (also on main @ 0985b45), GSAP 3.12.5 and 3.15.0, macOS, Chrome (headed and headless).
Additional context
Fix PR to follow: unpause the root timeline once the floor wrapper actually holds it, mirroring ensureChildCandidatesActive.
Describe the bug
A root timeline whose own duration is 0 — e.g. one that only holds
tl.set(...)at position 0, which is exactly what Studio writes for a static resize — never renders its sets in the preview. After resizing an element in Studio the file is correct, but the element snaps back to its authored size in the preview (keeping the drag offset), so it looks like the resize was lost.Cause: in
packages/core/src/runtime/init.ts, an unusable (zero) root duration takes thecreateDurationFloorTimelinefallback: the root timeline is added to a new paused wrapper padded to the composition duration. The author's root was createdgsap.timeline({ paused: true })and stays paused, and a paused child is never rendered when its parent is seeked. The nested-composition path already unpauses held children (ensureChildCandidatesActive); the floor wrapper does not.Minimal reproduction
Steps to reproduce
npx hyperframes previewon the composition above (or produce it by resizing an element in Studio in a composition without tweens).#boxat any playhead position.Expected behavior
#boxrenders at 200×150.Actual behavior
#boxstays at 400×300. In the preview iframe,__timelines.main.paused()istrue, its children are never initialised, and__player.seek(t)has no effect on it. After__timelines.main.paused(false),__player.seek(0.5)renders 200×150. A single tween in the timeline (non-zero duration) avoids the fallback and the bug.hyperframes snapshot --at 0.5of the same file renders 200×150 correctly, so only the preview runtime is affected.Environment
hyperframes CLI 0.8.77 (also on
main@ 0985b45), GSAP 3.12.5 and 3.15.0, macOS, Chrome (headed and headless).Additional context
Fix PR to follow: unpause the root timeline once the floor wrapper actually holds it, mirroring
ensureChildCandidatesActive.