diff --git a/packages/cli/src/commands/snapshot.ts b/packages/cli/src/commands/snapshot.ts index f9158f20a..a05292339 100644 --- a/packages/cli/src/commands/snapshot.ts +++ b/packages/cli/src/commands/snapshot.ts @@ -276,17 +276,14 @@ async function captureSnapshots( for (let i = 0; i < positions.length; i++) { const time = positions[i]!; - // 30 = runtime's default canonicalFps (not exposed on PlayerAPI) await page.evaluate((t: number) => { const player = (window as any).__player; if (!player) return; const safe = Math.max(0, Number(t) || 0); - const frame = Math.floor(safe * 30 + 1e-9); - const quantized = frame / 30; if (typeof player.renderSeek === "function") { - player.renderSeek(quantized); + player.renderSeek(safe); } else if (typeof player.seek === "function") { - player.seek(quantized); + player.seek(safe); } if ((window as any).gsap?.ticker?.tick) { (window as any).gsap.ticker.tick(); diff --git a/packages/core/src/runtime/init.test.ts b/packages/core/src/runtime/init.test.ts index 6fd967495..629bf107c 100644 --- a/packages/core/src/runtime/init.test.ts +++ b/packages/core/src/runtime/init.test.ts @@ -576,4 +576,52 @@ describe("initSandboxRuntimeModular", () => { expect(player?.getTime()).toBeCloseTo(1, 1); expect(childTimeline.time()).toBeCloseTo(1, 1); }); + + it("sets __renderReady only after timeline is bound, not at __playerReady time", async () => { + const root = document.createElement("div"); + root.setAttribute("data-composition-id", "main"); + root.setAttribute("data-root", "true"); + root.setAttribute("data-start", "0"); + root.setAttribute("data-width", "1920"); + root.setAttribute("data-height", "1080"); + document.body.appendChild(root); + + (window as Window & { __timelines?: Record }).__timelines = { + main: createMockTimeline(10), + }; + + initSandboxRuntimeModular(); + + const win = window as Window & { + __playerReady?: boolean; + __renderReady?: boolean; + __player?: { _timeline: RuntimeTimelineLike | null }; + }; + + expect(win.__playerReady).toBe(true); + expect(win.__renderReady).toBe(true); + expect(win.__player?._timeline).not.toBeNull(); + }); + + it("does not set __renderReady when no timeline is available", () => { + const root = document.createElement("div"); + root.setAttribute("data-composition-id", "main"); + root.setAttribute("data-root", "true"); + root.setAttribute("data-start", "0"); + root.setAttribute("data-width", "1920"); + root.setAttribute("data-height", "1080"); + document.body.appendChild(root); + + (window as Window & { __timelines?: Record }).__timelines = {}; + + initSandboxRuntimeModular(); + + const win = window as Window & { + __playerReady?: boolean; + __renderReady?: boolean; + }; + + expect(win.__playerReady).toBe(true); + expect(win.__renderReady).toBeUndefined(); + }); }); diff --git a/packages/core/src/runtime/init.ts b/packages/core/src/runtime/init.ts index 11b84dc22..2f1ab2266 100644 --- a/packages/core/src/runtime/init.ts +++ b/packages/core/src/runtime/init.ts @@ -1463,7 +1463,9 @@ export function initSandboxRuntimeModular(): void { .finally(() => { externalCompositionsReady = true; bindRootTimelineIfAvailable(); - (window as Window & { __renderReady?: boolean }).__renderReady = true; + if (state.capturedTimeline) { + (window as Window & { __renderReady?: boolean }).__renderReady = true; + } runAdapters("discover", state.currentTime); bindMediaMetadataListeners(); installAssetFailureDiagnostics(); @@ -1650,9 +1652,10 @@ export function initSandboxRuntimeModular(): void { if (bindRootTimelineIfAvailable() && state.capturedTimeline !== prevTimeline) { player._timeline = state.capturedTimeline; } - // Re-run adapters to discover new elements runAdapters("discover", state.currentTime); - (window as Window & { __renderReady?: boolean }).__renderReady = true; + if (state.capturedTimeline) { + (window as Window & { __renderReady?: boolean }).__renderReady = true; + } postTimeline(); postState(true); }, 0);