Skip to content

feat(lint): add gsap_css_transform_conflict - #106

Merged
miguel-heygen merged 1 commit into
mainfrom
feat/lint-gsap-css-transform-conflict
Mar 28, 2026
Merged

feat(lint): add gsap_css_transform_conflict#106
miguel-heygen merged 1 commit into
mainfrom
feat/lint-gsap-css-transform-conflict

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Mar 27, 2026

Copy link
Copy Markdown
Collaborator

Changes

Added a new lint rule gsap_css_transform_conflict that detects when GSAP animations will silently overwrite CSS transforms.

gsap_css_transform_conflict (error) — fires when an element has transform: translateX(-50%) or transform: scale() in CSS and a GSAP tl.to/from tween animates x, y, xPercent, yPercent, or scale. GSAP silently overwrites the full CSS transform, discarding centering tricks like translateX(-50%). Fix hint guides authors to the safe fromTo + xPercent pattern.

Root cause

This bug surfaced while building compositions where title reveals were placed off-center because tl.to("#title", { x: 0 }) stripped the translateX(-50%) centering from CSS.

Test coverage

  • gsap_css_transform_conflicttl.to with x on CSS translateX element → error
  • gsap_css_transform_conflicttl.to with scale on CSS scale() element → error
  • gsap_css_transform_conflicttl.fromTo without CSS transform → no finding

@miguel-heygen
miguel-heygen force-pushed the feat/lint-gsap-css-transform-conflict branch from f798ace to ced72b7 Compare March 28, 2026 00:00
@miguel-heygen miguel-heygen changed the title feat(lint): add gsap_css_transform_conflict and sequential_clips_different_tracks rules feat(lint): add gsap_css_transform_conflict Mar 28, 2026

@vanceingalls vanceingalls 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.

Good rule — this is a real footgun in GSAP compositions and worth catching at lint time. A few issues to address before merging:

1. The fromTo exemption doesn't actually exist

The PR description and fix hint both say the safe pattern is tl.fromTo(...) with explicit xPercent — but the rule code never checks which tween method was used. GsapWindow doesn't have a method field (extractGsapWindows drops it), so tl.fromTo("#title", { xPercent: -50 }, { xPercent: -50, x: 0 }) on an element with transform: translateX(-50%) in CSS will still fire because x and xPercent are in the properties list and the CSS selector matches.

The negative test ("does NOT report error when tl.to animates x on element WITHOUT CSS transform") passes for the wrong reason — it passes because #card has no CSS transform, not because it uses fromTo. Rename the test or add the actual fromTo-on-CSS-transform case.

Two options:

  • (a) Add a method field to GsapWindow so the rule can skip fromTo calls (if the intent is "fromTo means the author is managing transforms explicitly").
  • (b) Keep firing on fromTo too — but then update the fix hint, since it currently recommends something the rule would also flag.

I'd lean toward (a). The whole point of fromTo is that the author sets both ends explicitly, so GSAP overwriting CSS is intentional.

2. Negative test is misleadingly named

it("does NOT report error when tl.to animates x on element WITHOUT CSS transform")

The test body uses tl.fromTo, not tl.to. And it tests absence of CSS transform rather than the fromTo exemption. Either fix the name to match the body, or split into two tests:

  • fromTo on an element WITH CSS transform → no finding (once the exemption exists)
  • tl.to on an element WITHOUT CSS transform → no finding

3. Inline style transforms are invisible to this rule

The rule only scans <style> blocks. An element like:

<div id="title" style="transform: translateX(-50%)"></div>

with no corresponding <style> rule would silently pass. This is common in AI-generated compositions. Not blocking, but worth a follow-up or a comment in the code acknowledging the gap.

4. CSS selector regex only handles bare #id / .class

The regex ([#.][a-zA-Z0-9_-]+)\s*\{([^}]+)\} won't match compound selectors like #root .title, grouped selectors like #a, #b, or attribute selectors. Compositions typically use flat ID selectors so this is low risk — but add a code comment noting the limitation so future maintainers don't assume full CSS parsing.

5. Dual findings for combined transforms

An element with transform: translateX(-50%) scale(0.8) will appear in both cssTranslateSelectors and cssScaleSelectors. If the tween animates both x and scale, two separate findings fire for the same element. Consider deduplicating into a single finding listing all conflicting properties.

@miguel-heygen
miguel-heygen force-pushed the feat/lint-gsap-css-transform-conflict branch 2 times, most recently from 05dd365 to c4ce52e Compare March 28, 2026 00:20
@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

All 5 points addressed — force-pushed to the same commit.

1. fromTo exemption is now real
Added a method field to GsapWindow and populate it in extractGsapWindows from match[1]. The rule skips any window where win.method === "fromTo". Went with option (a).

2. Tests renamed and split

  • "warns when tl.to animates x on an element with CSS translateX" — positive case, tl.to
  • "warns when tl.to animates scale on an element with CSS scale transform" — positive case, tl.to
  • "does NOT warn when tl.to targets element without CSS transform" — now correctly uses tl.to (was accidentally tl.fromTo before)
  • "does NOT warn when tl.fromTo targets element WITH CSS transform (author owns both ends)" — new, exercises the actual exemption
  • "emits one warning when a combined CSS transform conflicts with multiple GSAP properties" — new, covers the dedup case

3. Inline style gap documented
Added a comment in the rule block: "Only scans <style> blocks. Inline style="transform:..." on elements is not detected. This is common in AI-generated compositions and may cause false negatives."

4. CSS selector regex limitation documented
Added a comment: "CSS selector regex matches bare #id and .class only. Compound selectors (#root .title), grouped selectors, and attribute selectors are not matched. Compositions typically use flat IDs so risk is low."

5. Deduplication
Conflicts are now collected into a Map<selector, { cssTransform, props: Set, raw }> across all windows before emitting, so a single tween with both x and scale on a combined translateX(-50%) scale(0.8) transform produces one finding listing all conflicting properties.

Detects elements whose CSS <style> block sets `transform: translate*` or
`transform: scale*` that are also targeted by a GSAP tl.to/tl.from tween
animating x, y, xPercent, yPercent, or scale. GSAP's transform properties
overwrite the *entire* CSS transform, silently discarding translateX(-50%)
centering and similar positioning tricks.

tl.fromTo is exempt: when the author provides explicit from/to states they
own both ends of the transform, so overwriting CSS is intentional.

Combined transforms (translateX(-50%) scale(0.8)) that conflict with
multiple tween properties produce a single deduplicated finding.

Adds a method field to GsapWindow so the rule can distinguish tl.to/from
(conflict) from tl.fromTo (exempt).

Known limitations noted in comments: inline style transforms are not
detected; CSS selector regex handles bare #id/.class only.

Tests: tl.to on CSS translateX → warn; tl.to on CSS scale → warn;
tl.fromTo on CSS translateX → no finding (exempt); tl.to without CSS
transform → no finding; combined transform → single finding.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@miguel-heygen
miguel-heygen force-pushed the feat/lint-gsap-css-transform-conflict branch from c4ce52e to 59dddd9 Compare March 28, 2026 00:44
@miguel-heygen
miguel-heygen merged commit d781398 into main Mar 28, 2026
20 checks passed
dahans-msft2 pushed a commit to dahans-msft2/hyperframes that referenced this pull request Aug 6, 2026
## Changes

Added a new lint rule `gsap_css_transform_conflict` that detects when GSAP animations will silently overwrite CSS transforms.

**`gsap_css_transform_conflict` (error)** — fires when an element has `transform: translateX(-50%)` or `transform: scale()` in CSS and a GSAP `tl.to/from` tween animates `x`, `y`, `xPercent`, `yPercent`, or `scale`. GSAP silently overwrites the full CSS transform, discarding centering tricks like `translateX(-50%)`. Fix hint guides authors to the safe `fromTo` + `xPercent` pattern.

## Root cause

This bug surfaced while building compositions where title reveals were placed off-center because `tl.to("#title", { x: 0 })` stripped the `translateX(-50%)` centering from CSS.

## Test coverage

- [x] `gsap_css_transform_conflict` — `tl.to` with `x` on CSS `translateX` element → error
- [x] `gsap_css_transform_conflict` — `tl.to` with `scale` on CSS `scale()` element → error  
- [x] `gsap_css_transform_conflict` — `tl.fromTo` without CSS transform → no finding
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.

2 participants