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
@@ -12,6 +12,7 @@ import {
extendRootDurationIfNeeded,
formatTimelineAttributeNumber,
patchIframeDomTiming,
playbackStartAttributeForElement,
persistTimelineBatchEdit,
type PersistTimelineBatchChange,
type RecordEditInput,
@@ -242,9 +243,10 @@ export function useTimelineGroupEditing({
// duration change), so nothing timing-related changed — the batch only
// rewrites data-track-index, which the renderer never reads (documented
// in core runtime/timeline.ts; track is a studio lane concept). The live
// DOM patch above + the gesture owner's optimistic store update fully
// cover the UI, so after the persist there is nothing to GSAP-shift and
// nothing for the preview to recompute: skip the fallback below entirely.
// DOM patch above + the gesture owner's optimistic store update cover the
// in-flight UI; after the complete lane + z transaction, that owner
// refreshes the preview so its runtime manifest converges to disk. There
// is still nothing to GSAP-shift here, so skip this fallback entirely.
// Running it anyway is what made the mirrored z-order lane move blink —
// a zero-delta batch yields no scriptText, and finishGroupTimingGsapFallback
// used to full-reload the iframe when there was no script to soft-swap
@@ -348,10 +350,7 @@ export function useTimelineGroupEditing({
["data-duration", formatTimelineAttributeNumber(change.duration)],
];
if (change.playbackStart != null) {
const liveAttr =
change.element.playbackStartAttr === "playback-start"
? "data-playback-start"
: "data-media-start";
const liveAttr = playbackStartAttributeForElement(change.element);
liveAttrs.push([liveAttr, formatTimelineAttributeNumber(change.playbackStart)]);
}
patchIframeDomTiming(previewIframeRef.current, change.element, liveAttrs, activeCompPath);