Skip to content

fix(studio): a layer with a min(), max() or clamp() translate moves from where it really is - #4832

Merged
miguel-heygen merged 3 commits into
mainfrom
fix/studio-translate-math-arithmetic
Oct 1, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
fix/studio-translate-math-arithmetic

Conversation

@miguel-heygen

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

Copy link
Copy Markdown
Collaborator

What changes

A layer without GSAP whose CSS translate uses min(), max() or clamp() now moves from where it really is. That includes a layer that has a transform transition.

To read such a translate, main wrote a temporary inline transform onto the layer and read it back. If the layer has transition: transform ..., the browser reports the transition's starting value instead, so main read the layer's offset as 0,0. The drag then started off by the layer's own translate: 46.65 px for translate: clamp(0px, 10%, 30px) min(40px, 25%) on a 240x160 box. The drop landed that far off, and the file got the wrong translate. Without a transition, main's temporary write is removed in the same step of code and no frame shows it. Measured in the built Studio, both main and this branch then have a press jump of 0 px.

How

readTranslatePx (plainTranslate.ts) now evaluates the computed translate itself: px, %, calc(), min(), max(), clamp(), parentheses and + - * /. It reads the computed value first, where Chrome has already resolved em, vw, vh, var() and simplifiable functions such as abs(-10%), so only % is left to work out. % resolves against the box transform-box names: the content box for content-box and fill-box, otherwise the border box. The temporary-transform reader is deleted.

Anything outside that grammar, such as abs(10% - 50px) or round(10%, 7px), which Chrome keeps unresolved, evaluates to NaN rather than to a guess. Studio then refuses to move the layer, says why, and leaves the layer as it was:

  • A drag or arrow-key nudge shows "Studio can't read this layer's translate. Move it in the Code tab." Before, a drag showed a generic toast and a nudge did nothing.
  • The Properties panel disables X and Y and commits nothing.

Checked in headless Chrome 152 against the layer's real on-screen movement, including padding and border, for eleven values:

  • plain px and %;
  • the calc, min, max and clamp forms;
  • em, vw and vh inside them;
  • calc(50% / 3);
  • abs(-10%).

All eleven match to 0.0001 px.

Tests

  • plainTranslate.test.ts:
    • "works out %s by arithmetic": the min, max, clamp and nested calc forms.
    • "reads %s as NaN, never as a guess".
    • "resolves a percent against the content box under transform-box: content-box / fill-box". Both fail if % always resolves against the border box.
    • "writes nothing to the element while it reads min(10px, 5%) clamp(0px, 10%, 30px)": a MutationObserver on the layer during the read.
    • With main's plainTranslate.ts in place, 9 of the original 18 fail, the MutationObserver one included.
  • manualOffsetDrag.test.ts: "refuses a translate it can't read instead of guessing". It fails with the guard removed.
  • previewReadOnly.overlay.test.tsx: "refuses a drag and an arrow nudge out loud, and commits nothing". It fails if the reason isn't passed on.
  • propertyPanelTransformCommit.test.ts: the X/Y fields "are disabled" and "commit nothing and say why". The latter fails with the panel guard removed.

Before

Main, a layer without GSAP with translate: clamp(0px, 10%, 30px) min(40px, 25%) and transition: transform 0.4s, dragged +90/+60. The layer is off by 46.65 px (its own translate) from the first move to the drop, and the file saves translate: 90px 60px instead of 114px 100px. The dashed outline and cross mark where it was released.

Before: the layer drops 46.65 px from where it was released

After

Same layer and gesture on this branch. It is 0 px off at every step and at the drop, and the file saves translate: 114px 100px.

After: the layer drops where it was released

@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 changed the title fix(studio): a translate written with min(), max() or clamp() moves without a jump at press fix(studio): a layer with a min(), max() or clamp() translate moves from where it really is Oct 1, 2026
@miguel-heygen
miguel-heygen marked this pull request as ready for review October 1, 2026 06:36
@miguel-heygen
miguel-heygen merged commit 7880d41 into main Oct 1, 2026
82 checks passed
@miguel-heygen
miguel-heygen deleted the fix/studio-translate-math-arithmetic branch October 1, 2026 06:38
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