fix(studio): harden composition timeline reliability (#2615)

* fix(studio): preserve composition playback continuity

* feat(studio): drag compositions into the timeline

* fix(studio): collapse expanded composition move aliases

* fix(studio): make timeline cuts atomic

* fix(studio): group inspector gesture history

* test(studio): cover masked text selection

* fix(studio): harden composition timeline reliability

* fix(studio): satisfy CI source gates

* fix(studio): harden composition mutation requests
This commit is contained in:
Miguel Ángel
2026-07-17 14:15:30 -04:00
committed by GitHub
parent 2be8a62c00
commit 2b65b4efce
93 changed files with 3925 additions and 758 deletions
+15 -17
View File
@@ -20,6 +20,8 @@ import {
import { forceDispatchSeekEvent } from "./adapters/seek-dispatch";
import { createWaapiAdapter } from "./adapters/waapi";
import {
readElementPlaybackRate,
readElementPlaybackStart,
refreshRuntimeMediaCache,
resolveRuntimeMediaClipDuration,
syncRuntimeMedia,
@@ -2656,17 +2658,15 @@ export function initSandboxRuntimeModular(): void {
if (!node) continue;
const start = resolveStartForElement(node, 0);
if (!Number.isFinite(start)) continue;
const authoredDuration = resolveDurationForElement(node, {
includeAuthoredTimingAttrs: true,
});
const timelineDuration = getTimelineDurationSeconds(timeline);
const duration =
authoredDuration != null && authoredDuration > 0 ? authoredDuration : timelineDuration;
const sourceTime =
readElementPlaybackStart(node) +
Math.max(0, timeSeconds - start) * readElementPlaybackRate(node);
const localTime = Math.max(
0,
duration != null && duration > 0
? Math.min(duration, timeSeconds - start)
: timeSeconds - start,
timelineDuration != null && timelineDuration > 0
? Math.min(timelineDuration, sourceTime)
: sourceTime,
);
seekRuntimeTimeline(timeline, localTime, "runtime.init.transport.childTimeline", options);
}
@@ -2809,15 +2809,13 @@ export function initSandboxRuntimeModular(): void {
} catch (err) {
swallow("runtime.init.transport.seek", err);
}
// Sibling timelines (registered in __timelines but not nested under
// the root) are paused alongside the master. We do NOT seek them to
// absolute position `t` here — child timelines nested under the root
// are already propagated via tl.totalTime(), and seeking them again
// at absolute `t` would clobber their offset-relative position.
// Play/pause propagation for siblings happens in the player.play()
// and player.pause() overrides via the adapter layer.
} else {
seekStandaloneRegisteredTimelines(t, opts);
// Root propagation cannot represent an authored child source offset or
// playback rate. Re-seek registered children below with their host's
// explicit source-time contract.
}
seekStandaloneRegisteredTimelines(t, opts);
if (tl && opts?.activateChildren) {
activateSiblingTimelines(tl);
}
for (const adapter of state.deterministicAdapters) {
if (adapter.name === "gsap" && tl) continue;