Skip to content

fix(studio): a move keeps the route it chose at press through drop and save - #4834

Open
miguel-heygen wants to merge 3 commits into
mainfrom
fix/studio-move-route-once
Open

miguel-heygen wants to merge 3 commits into
mainfrom
fix/studio-move-route-once

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #4830.

What changes

A move decides whether it moves the layer by its own CSS translate or through GSAP once, when the gesture starts. The drop and the save then use that decision instead of deciding again.

Before, a drag decided the route three times: at press (createManualOffsetDragMember), when the commit arrived (handleGsapAwarePathOffsetCommit and the group preflight), and in the stager (stageElementOffset). If GSAP took the layer over between those points, the press could draft one way and the save write the other.

If GSAP has folded the layer's translate into its own transform since a CSS-route press, the save is refused, because a CSS write at that point would apply the move twice. The user sees "The animation took over this layer's position during the move, so it was not saved." and nothing is written. The refusal also resets GSAP's x/y/xPercent/yPercent to 0, which on the CSS route only ever hold the folded draft, and the gesture's own rollback puts the CSS translate back, so the layer returns to where it started. A real rotation or an authored inline transform such as rotate(45deg) scale(1.5) is kept. Checked in Chrome with GSAP 3.15: the box is back at its start, and still there after a seek.

A fold is "GSAP's cached x/y/xPercent/yPercent are non-zero", not merely "GSAP has parsed the transform": a parse with nothing to fold leaves them at 0, and that move saves normally. A group runs the same check in its preflight, so a group with one taken-over member writes no member at all. Since #4830 nothing on the drag path asks GSAP about a layer it doesn't position, so this is a guard, not an expected path.

How

  • Drag and nudge carry the press's decision (member.plainTranslate) on the commit: single moves through MoveCommitOptions.plainTranslate, group moves on each DomEditGroupPathOffsetCommit.
  • handleGsapAwarePathOffsetCommit and the group commit use the carried route. They decide with gsapWritesPosition only when nothing was carried, which is the panel's and webmcp moveTo's case.
  • stageElementOffset takes the route as a required argument and no longer decides on its own.
  • The panel's X/Y commit decides once, reads the current position for that route (readMoveOffset(el, plainTranslate)) and passes the route to the commit.
  • Unchanged: a group member that isn't the selection keeps today's rule, decided at press without a fetch. The anchor move of a GSAP resize decides at its own entry, as it does today.

Open item

Resize and rotate still read GSAP's x/y/rotation off an element GSAP doesn't position: the resize anchor base in startGesture, the resize anchor member, and the GSAP resize settle. #4805 (resize) and #4802 (rotate) rewrite exactly those lines and are still open. Once they merge, those reads move under this one decision, with a test that resizing a fade-only box leaves its next move on the CSS route.

Tests

Each one fails with its piece of the fix reverted:

  • domEditOverlayStartGesture.test.ts: "drops on the route it chose at press, even if GSAP takes the element over mid-drag".
  • useGsapAwareEditing.test.tsx: "keeps the route a gesture chose at press", for the CSS route on a layer GSAP has since taken over and the GSAP route on a layer that now looks GSAP-free, single and group.
  • useDomGeometryCommit.test.tsx:
    • "refuses a CSS move GSAP has folded into its x/y, writes nothing, and hands the translate back", and its other side, "saves a CSS move GSAP has only parsed, with nothing folded into its x/y".
    • "leaves a later move's translate alone when an earlier move's save fails": the stager's rollback only puts its own value back.
  • useGsapAwareEditing.test.tsx: "writes no member when one CSS-route member has been folded since the press".
  • domEditOverlayStartGesture.test.ts: "drops every member on the route it chose at press" (group).
  • useDomEditNudge.test.tsx: "commits an element without GSAP on the CSS route".
  • propertyPanelTransformCommit.test.ts: the panel reads and moves a GSAP-free layer by its translate and passes the route it chose.

Before

Main with #4830, a box with translate: 40px 30px and a GSAP tween that only fades it, dragged +90/+60 with GSAP made to fold the box's translate into its own x/y partway through (gsap.getProperty(box, "x")). From the fold on, the box is 114.86 px off the pointer, and the save writes gsap.set("#target", { x: 224, y: 156 }), counting the translate twice, so the box ends 273 px from where it was released. White dotted: where it started. Pink dashed and cross: where it was released.

Before: the box lands 273 px past the release and the file gains gsap.set

After

Same fixture and gesture on this PR's head. Studio shows "The animation took over this layer's position during the move, so it was not saved.", the file is not changed, and the box goes back to where it started (0 px from its start). White dotted: where it started. Pink dashed and cross: where it was released.

After: the save is refused, the file is unchanged and the box is back at its start

Base automatically changed from fix/studio-move-route-position-channels to main October 1, 2026 05:52
@miguel-heygen
miguel-heygen force-pushed the fix/studio-move-route-once branch from 0ad9b0f to ecc429a Compare October 1, 2026 06:12
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Edit accuracy: 494 passing here, 494 on the base branch

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

@miguel-heygen
miguel-heygen force-pushed the fix/studio-move-route-once branch from 35f995c to 0da8e10 Compare October 1, 2026 06:50
@miguel-heygen
miguel-heygen marked this pull request as ready for review October 1, 2026 07:26

This branch has not been deployed

No deployments
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.

1 participant