diff --git a/packages/core/src/runtime/init.test.ts b/packages/core/src/runtime/init.test.ts
index 1644832ede..3e5b6c44f0 100644
--- a/packages/core/src/runtime/init.test.ts
+++ b/packages/core/src/runtime/init.test.ts
@@ -612,6 +612,25 @@ describe("initSandboxRuntimeModular", () => {
}
});
+ it("leaves hidden clips alone on every jump when the page has no timeline", () => {
+ document.body.innerHTML =
+ `
` +
+ `` +
+ `` +
+ `
`;
+ window.__timelines = {};
+ initSandboxRuntimeModular();
+ const query = vi.spyOn(document, "querySelectorAll");
+ const looked = () =>
+ query.mock.calls.filter(([selector]) => selector === "[data-hf-clip-hidden]").length;
+ try {
+ for (const t of [0.5, 1, 1.5, 3, 6]) window.__player?.seek(t);
+ expect(looked()).toBe(0);
+ } finally {
+ query.mockRestore();
+ }
+ });
+
it("styles a hidden clip visible once while playing toward its start, far scene included", () => {
document.body.innerHTML =
`` +
diff --git a/packages/core/src/runtime/init.ts b/packages/core/src/runtime/init.ts
index e2d997848b..036e980784 100644
--- a/packages/core/src/runtime/init.ts
+++ b/packages/core/src/runtime/init.ts
@@ -5163,6 +5163,16 @@ export function initSandboxRuntimeModular(): void {
return false;
}
};
+ // Registered timelines other than the root composition's: each scene's own clock.
+ const sceneTimelines = () => {
+ const timelines = (window.__timelines ?? {}) as Record;
+ const rootCompositionId =
+ resolveRootCompositionElement()?.getAttribute("data-composition-id") ?? null;
+ return Object.entries(timelines).filter(
+ (entry): entry is [string, RuntimeTimelineLike] =>
+ !!entry[1] && typeof entry[1] === "object" && entry[0] !== rootCompositionId,
+ );
+ };
const seekStandaloneRegisteredTimelines = (
timeSeconds: number,
revision: number,
@@ -5172,12 +5182,7 @@ export function initSandboxRuntimeModular(): void {
sceneClocks = new WeakMap();
sceneClocksRevision = revision;
}
- const timelines = (window.__timelines ?? {}) as Record;
- const rootCompositionId =
- resolveRootCompositionElement()?.getAttribute("data-composition-id") ?? null;
- for (const [compositionId, timeline] of Object.entries(timelines)) {
- if (!timeline || typeof timeline !== "object" || compositionId === rootCompositionId)
- continue;
+ for (const [compositionId, timeline] of sceneTimelines()) {
const scene = sceneClockOf(compositionId, timeline);
if (!scene) continue;
const sourceTime =
@@ -5291,9 +5296,10 @@ export function initSandboxRuntimeModular(): void {
// Only clips holding a tween that renders for the first time on this seek need their authored style read.
const firstRendered =
firstRenders && tl ? clipsFirstRenderedIn(tl, tweensRenderedThrough.time, t) : null;
- // An empty set means no tween draws for the first time: nothing to show, so no page-wide query.
+ // An empty set means no tween draws for the first time, and a page with no timeline has no tween:
+ // nothing to show, so no page-wide query.
const hideClipsAgain =
- firstRenders && firstRendered?.size !== 0
+ firstRenders && firstRendered?.size !== 0 && (tl || sceneTimelines().length > 0)
? showHiddenClips(firstRendered ? (el) => firstRendered.has(el) : undefined)
: null;
// Scenes first, each paused on its own clock, so the root's pass skips them and its later