Skip to content

fix(studio): one GSAP-writes check that sees every keyframe form - #4781

Merged
miguel-heygen merged 3 commits into
mainfrom
fix/studio-gsap-owns-keyframe-arrays
Oct 2, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
fix/studio-gsap-owns-keyframe-arrays

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

What

Studio had two copies of the check "does GSAP write this channel on this element?". The copy used by the seek reapply, the drag reapply and the crop "GSAP owns clip-path" rule (gsapAnimatesProperty) read only top-level tween vars, so it missed object-of-arrays keyframes (keyframes: { x: [0, 200] }). A layer moved that way that still carried an old Studio offset got that offset put back after every seek, on top of GSAP's position: it showed up 300 px away from where its animation put it. A crop on a layer whose clip-path GSAP tweens that way was treated as Studio's to rewrite.

How

  • gsapWritesChannels in gsapRuntimeKeyframes.ts already answers the question for every keyframe form (top-level, percent, array, object-of-arrays) through keyframeVarsCarryChannel. It is now exported.
  • The three callers use it: seek reapply and drag reapply with ["x", "y"], the crop rule with ["clipPath"].
  • gsapAnimatesProperty is deleted. Its file keeps only gsapRendersTransform, which is a different question.
  • No new authoring format and no behaviour change for top-level vars.

Before

A layer with an old Studio offset of 300 px and a GSAP tween keyframes: { x: [0, 200] } (the dashed box is where the tween starts it), after seeking back to 0 s on main. Studio puts the offset back, so the layer sits 300 px right of its animated start.

Before: the layer sits 300 px right of its animated start

After

The same fixture and seek at this head. GSAP owns x, so Studio leaves the position alone and the layer sits on the dashed box.

After: the layer sits at its animated start

Tests

  • reapplyPositionEditsAfterSeek.test.ts: an element with an old offset and keyframes: { x: [0, 200] } keeps its offset off, while its untweened neighbour gets its offset back.
  • cropResize.test.ts: a crop on an element whose clip-path GSAP tweens by object-of-arrays keyframes is left alone.
  • Removing the object-of-arrays branch from keyframeVarsCarryChannel fails both new tests (and one existing crop test). The Studio unit suite passes.

Base automatically changed from fix/studio-resize-scales-crop to main September 30, 2026 18:56
@miguel-heygen
miguel-heygen force-pushed the fix/studio-gsap-owns-keyframe-arrays branch from 00bcdd3 to 505d0eb Compare October 2, 2026 19:26
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Edit accuracy: accurate 1216 (base branch 1216), smooth 1049 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (1)

@miguel-heygen
miguel-heygen marked this pull request as ready for review October 2, 2026 20:14

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at c4dd776c60965fea4b2a7fc7d8f5d01c6444723e on code merits. The unified GSAP channel check covers the keyframe forms exercised by the changed callers; it prevents Studio's saved offset from being reapplied over an x/y tween and preserves GSAP ownership of a tweened crop clip path. I compared the effective diff with merge base aca4bc2f492c2b038d87c875cd7d53a80dfb4bb1 and ran four focused suites in an isolated checkout (38/38 passed).

The seek guard still handles only x/y, not xPercent/translateX, but that limit predates this PR. The new classifier also does not catch exceptions from a malformed timeline implementation where its predecessor did; I found no normal reload/teardown path that produces such a throwing timeline, so I am not treating that semantic difference as a demonstrated merge blocker. The edit-accuracy CI shards were pending during this review; this approval is a code verdict, not a claim that all CI completed.

— Review by tai (pr-review)

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at c4dd776c.

Does the shared check still catch what the deleted one did? Yes. I loaded gsapAnimatesProperty from main next to gsapWritesChannels and ran both over 16 vars shapes × {["x","y"], ["clipPath"]} × {same element, id-only match}. Wherever the old check said true, the new one does too. It newly says true in three cases, all object-of-arrays: { x: [0, 200] }, { clipPath: [...] }, and { x: [...], ease, easeEach }. Top-level, percent, step-array and x: undefined give the same answers as before, as do null and string keyframes.

The PR body's test claims hold:

  • On main's sources, both new tests fail and the other 15 pass.
  • Removing the object-of-arrays branch from keyframeVarsCarryChannel turns 3 tests red: both new tests, plus the existing "follows only the axes GSAP does not tween". That's what the body says.
  • Turning off the seek-reapply guard fails the new reapply test.

Suite: studio 637 files / 6972 tests pass, and tsc --noEmit is clean.

Non-blocking, for the record:

  • The old function wrapped its scan in try/catch and returned false if getChildren or targets() threw. gsapWritesChannels lets the error through. Real GSAP doesn't throw there. The existing gsapWritesPosition, gsapWritesRotation and gsapWritesBox callers already run unguarded. The useDomSelection call sites still sit in try blocks, while usePreviewPersistence and useDomEditPreviewSync don't. So I see no practical risk.
  • The drag path (applyStudioPathOffsetViaGsap) now also sends object-of-arrays x/y elements through gsap.set, which matches how percent keyframes already worked. Nothing pins that call site, but its behaviour is now the same as the seek-reapply path's.

— Rames

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 00b61b8 Oct 2, 2026
137 checks passed
@miguel-heygen
miguel-heygen deleted the fix/studio-gsap-owns-keyframe-arrays branch October 2, 2026 21:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants