fix(core): a preview that is never seeked shows only the clips of its current time (#4422)

The visibility pass ran only on seek, pause, play and during playback, and at init for video
and img alone, so a paused page published __renderReady with every caption and composition host
visible until someone seeked. Readiness now runs that pass for non-media timed elements; media
keeps its init pass and seeks, which own color grading and audio scheduling.
This commit is contained in:
Miguel Ángel
2026-09-24 14:18:53 -04:00
committed by GitHub
parent 69fea290a9
commit 861fcc4dd0
2 changed files with 61 additions and 2 deletions
+55 -2
View File
@@ -2559,8 +2559,8 @@ describe("initSandboxRuntimeModular", () => {
expect(getContextSpy).toHaveBeenCalledTimes(1);
expect(document.getElementById("first")?.style.visibility).toBe("visible");
expect(document.getElementById("second")?.style.visibility).toBe("hidden");
expect(futureComposition.style.visibility).toBe("");
expect(futureComposition.style.display).toBe("");
expect(futureComposition.style.visibility).toBe("hidden");
expect(futureComposition.style.display).toBe("none");
window.__player?.seek(3);
@@ -2805,6 +2805,59 @@ describe("initSandboxRuntimeModular", () => {
expect(window.__player).toBeDefined();
});
function mountRootWithClip(start: string): HTMLElement {
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);
return appendClip(root, start);
}
function appendClip(parent: Element, start: string): HTMLElement {
const clip = document.createElement("div");
clip.className = "clip";
clip.setAttribute("data-start", start);
clip.setAttribute("data-duration", "2");
clip.setAttribute("data-track-index", "1");
parent.appendChild(clip);
return clip;
}
it("publishes render readiness with out-of-window clips already hidden, no seek needed", () => {
const caption = mountRootWithClip("5");
window.__timelines = { main: createMockTimeline(10) };
initSandboxRuntimeModular();
expect(window.__renderReady).toBe(true);
expect(caption.style.visibility).toBe("hidden");
});
it("paints clips mounted before readiness at the time sought before readiness", () => {
mountRootWithClip("0");
const root = document.querySelector("[data-composition-id='main']")!;
window.__timelines = { main: createMockTimeline(10) };
window.__hfTimelinesBuilding = true;
initSandboxRuntimeModular();
expect(window.__renderReady).toBe(false);
window.__player?.seek(6);
const inWindow = appendClip(root, "5");
const outOfWindow = appendClip(root, "0");
window.__hfTimelinesBuilding = false;
window.dispatchEvent(new CustomEvent("hf-timelines-built"));
expect(window.__renderReady).toBe(true);
expect(window.__player?.getTime()).toBe(6);
expect([inWindow.style.visibility, outOfWindow.style.visibility]).toEqual([
"visible",
"hidden",
]);
});
it("waits for GSAP batching to finish before publishing render readiness", () => {
const root = document.createElement("div");
root.setAttribute("data-composition-id", "main");
+6
View File
@@ -3341,6 +3341,12 @@ export function initSandboxRuntimeModular(): void {
);
}
}
// Nothing else hides out-of-window clips on a paused page until someone seeks, so readiness does.
// Media is left to init's media pass and to seeks: they own color grading and audio scheduling.
syncTimedElementVisibility(
state.currentTime,
Array.from(document.querySelectorAll("[data-start]:not(video, audio, img)")),
);
// __renderReady = timeline binding attempted, safe for deterministic seeking.
// Set after any GSAP batching has completed. renderSeek works with or
// without a GSAP timeline (CSS/WAAPI/Lottie compositions use adapters only).