fix(parsers): moving a clip on the timeline moves its inner animations too - #4761
Merged
Merged
Conversation
…e aliases in place
…e no animation change
5 tasks done
miguel-heygen
marked this pull request as ready for review
September 30, 2026 19:30
somanshreddy
approved these changes
Sep 30, 2026
miguel-heygen
marked this pull request as draft
September 30, 2026 19:58
miguel-heygen
marked this pull request as ready for review
September 30, 2026 19:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Moving or stretching a clip on the Studio timeline now carries every animation aimed at things inside that clip, not only the ones aimed at the clip itself. Animations on elements outside the clip are never rewritten.
Why
The timeline's move and retime write-back (
shift-positions/scale-positions) matched tweens by an exact string compare against the clip's#id. A scene that animates its own children ("#scene h1","#child", a class used only inside the scene, a list of targets) kept those animations at their old times, so after a drag the scene appeared with its title already landed and its boxes already moved.Related work
Standalone fix; not part of the placement-model work.
How
@hyperframes/parsers(clipTweenMatcher): a tween moves with the clip when its selector is the clip's own selector, or when its full target set is the clip or sits inside it, with no nearer timed (data-start) clip of its own. The acorn writer, the recast writer and the SDK'ssetTimingall use it, so there is one owner.clipQueryRoot(same file) owns where the clips are looked up: the<template>that holds the GSAP script, else the document, because linkedom keeps a template's children under the template where document queries do not reach them. The studio-server route and the SDK both use it. The SDK passes the exact selectors it matched before (#id,[data-hf-id]in both quote styles) as the clip's own selectors, so nothing it moved before is lost.tl.from(b, …)with no position) follows the tween before it.hasExplicitTime(same file) decides this for all four writer loops and for the SDK: a move leaves such a tween alone, and a stretch scales its duration but never pins a position on it.["#scene h1", window.logo]) withhasPartialSelector, and the matcher refuses it. The tween stays visible in the timeline and the property panel. This check runs before the own-selector match, so["#scene", window.logo]does not move the logo either.data-startis not moved either. A group move of a parent and its nested clip therefore shifts each tween once.shift-positions/scale-positionsfor that edit. The committed result now carries the bytes before and after, andsdkTimingGsapSyncreports a sync only when the timeline script in those bytes actually changed (it walks the parsed bytes with the same composition walker the SDK uses, so a script inside a<template>counts); otherwise the server pass runs. The single-clip move and resize and the group move and resize all read it.findTimelineScript(in the acorn parser) is now the one rule for which script holds a file's timeline, used by the SDK and the server, so agsap.configscript placed first no longer makes the SDK sync a different script than the server. With no SDK session (the master view) or a declined SDK write, the server pass stays the one sync. Before this, a +2s move in such a composition landed the clip's tweens at +4s, and the following stretch scaled from the wrong start.Known limits
scene.querySelectorAll(".x")) reads as the global.xand stays put when.xalso exists outside.data-startattribute does not carry its descendants on its first move (the move writes the attribute)."<","+=1","intro") are skipped, for descendants as for the clip's own tweens.Test plan
gsapWriter.parity.test.ts, both writers, shift and scale on one composition: descendant selector, child id, list target and inside-only class move; an outside element, a class spanning inside and outside, a tween inside a nested timed clip, a chained tween (keeps no position; its duration scales) and an array with an unresolvable part stay.files.test.ts:shift-positionsthenscale-positionson a template-wrapped composition move#card h1and#line, and leave#sideand#dot, which sits in a nested timed clip.mutate.gsap.test.ts:setTimingon a scene carries#scene h1, and leaves a chained tween, a partly known array and an outside element alone, and leaves an outside tween chained after the scene's content unwritten (the known limit). A second test moves a clip in a<template>-wrapped composition and carries its[data-hf-id]tween and#card h1.["#scene", window.logo](stays), and a clip withoutdata-startwhose tween targets it through a class (moves).<template>file with agsap.configscript before the timeline script: an SDK move then stretch lands each tween once (Studio test), and SDKsetTimingsyncs the timeline script there (SDK test). Before, the SDK moved no tween and the stretch then scaled from the new start.useTimelineEditing.test.tsx: a move, a resize and a group move in a sub-composition, each through the SDK and through the server (no SDK session), read the final file bytes from an in-memory project whose GSAP route runs the real server writer. Before the Studio fix the three SDK cases fail with doubled times (#sceneat 5 instead of 3,#scene h1at 5.5, durations 4 instead of 2); the server cases pass before and after. The three older tests that expected a server post after an SDK commit are replaced by these.<template>) fails the template config-script Studio test.Before
Main, just after dragging the scene 2s later, at 3.5s: the scene has just appeared, but its title has already landed and the gold box has already moved.
Main after the stretch, at 4s: same, the inner animations still sit at their original times.
After
This branch, same drag, at 3.5s: the title is sliding in and the gold box waits for its cue. After stretching the scene to 6s and reloading, at 4s and 5.5s, the inner animations play at their scaled times.
gsap.configscript first; the single-file master view, plain and config first), each start re-checked in real GSAP from the saved file: after the +2s move, the scene's timed tweens sit at 3 and the outside#ctastill plays at 5.5 and the chained outside#sidegoes from 3 to 5. After the +3s stretch, the chained outside#sideand#ctamove to 6.5 and 7, as the known limit says. Each edit wrote the animations once, and nothing outside the scene got a written position.