fix(motion): render final state immediately under prefers-reduced-motion - #924
fix(motion): render final state immediately under prefers-reduced-motion#924njrini99-code wants to merge 1 commit into
Conversation
Fixes #906. With macOS Reduce Motion on, several reveal/draw animations across the golf auth family, the landing Hero email-capture confirmation, the HoleShotPath shot-path draw, and the AnimatedNumber mount-roll stayed stuck at their `initial` hidden state (opacity 0 / undrawn pathLength / held-at-0) instead of rendering the final visible content immediately. Root cause (the class, not one instance): these components gated only the framer-motion `transition` duration on `useReducedMotion()` (dropping it to 0), but left `initial={{ opacity: 0, ... }}` unconditional. That still depends on the animation engine actually running the enter tween — any delay in `LazyMotion` feature-chunk loading, hydration timing, or a missed `onAnimationComplete` leaves the element parked at its `initial` value with no fallback. The already-correct primitives in this repo (`src/components/ui/reveal.tsx`, `src/components/baseball/living-annual/ Reveal.tsx`, `Sparkline.tsx`, `Dial.tsx`, `RadialGauge.tsx`) all avoid this by setting `initial={reduced ? false : {...}}` — `initial={false}` makes framer-motion apply the `animate` target directly as the mount style, no engine dependency, no race. Fix — apply that same `initial={reduced ? false : {...}}` idiom to every un-gated instance found by auditing the auth/demo/landing surfaces named in the issue: - src/app/golf/(auth)/{login,demo,signup,forgot-password,reset-password}/ page.tsx — 26 `m.div` reveal instances across the shared golf-auth pattern (brand mark, form card, footer, inline success/error banners). - src/components/landing/Hero.tsx:41 — EmailCapture's "you're on the list" confirmation had NO reduced-motion gating at all. - src/components/golf/coachhelm/v3/HoleShotPath/index.tsx — the hole-shot-path segment draw (pathLength) and shot-dot pop-in, used on the round-detail/round-review shot visualization. - src/components/ui/animated-number.tsx — the NumberFlow mount-roll wrapper artificially held `displayValue` at 0 for the stagger-delay window regardless of motion preference (NumberFlow's own `respectMotionPreference` disables ITS spin, but our own charade above it was still the thing freezing Command Center stat numerals at 0). Now snaps straight to the final value under reduced motion, no timer. Added src/components/ui/__tests__/animated-number.test.tsx (mocks `framer-motion`'s `useReducedMotion` per repo convention, matching Reveal.test.tsx) locking in: normal mount still rolls 0 → value; reduced motion renders the final value synchronously with no 0 flash and ignores staggerIndex; prefix/suffix and subsequent updates still work. Gates: tsc --noEmit clean; eslint clean on all changed files; vitest 956/956 passed across src/components/golf, src/components/landing, src/components/ui, src/components/baseball/living-annual, and the golf (auth) route group (0 regressions), plus the new 6-case suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Warning Review limit reached
Next review available in: 59 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
🤖 Mission Control — PR summary What it changes: Under Risk / areas: shared motion primitives spanning golf + baseball + landing; potential animation/visual regressions. Watch: the normal-motion path is unchanged (no flash / double-render); SSR→hydration timing; parity with the already-correct CI: ✅ green so far — 34 checks passing, 4 pending, 0 failing; mergeable state BLOCKED on required review + in-flight checks (no CI failure). Awaiting review. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
Problem
Fixes #906. With macOS Reduce Motion on, several reveal/draw-style animations across the codebase stayed stuck at their
initialhidden state instead of rendering the final visible content — verified live:/golf/loginform invisible,/golf/demoform invisible; CI Playwright logs showeduseReducedMotion()firing onsrc/components/landing/Hero.tsx:99,src/app/golf/(auth)/*, andsrc/components/baseball/living-annual/Reveal.tsx:50.Root cause — the class, not one instance
The affected components gated only the framer-motion
transitionduration onuseReducedMotion()(dropping it to0) but leftinitial={{ opacity: 0, ... }}unconditional:That still depends on the animation engine actually running the enter tween (LazyMotion feature-chunk load, hydration timing,
onAnimationComplete, etc.) to reach theanimatetarget. Any hiccup there leaves the element parked atinitialwith no fallback.The already-correct primitives in this repo —
src/components/ui/reveal.tsx,src/components/baseball/living-annual/Reveal.tsx,Sparkline.tsx,Dial.tsx,RadialGauge.tsx— all avoid this by settinginitial={reduced ? false : {...}}.initial={false}makes framer-motion apply theanimatetarget directly as the mount style, synchronously, with zero dependency on the animation engine ever running.Fix
Applied that same
initial={reduced ? false : {...}}idiom everywhere it was missing, found by auditing every auth/demo/landing/draw surface named in the issue:src/app/golf/(auth)/{login,demo,signup,forgot-password,reset-password}/page.tsx— 26m.divreveal instances across the shared golf-auth pattern (brand mark, the actual form card, footer, inline success/error banners). These are the pages verified live as invisible.src/components/landing/Hero.tsx:41—EmailCapture's "you're on the list" confirmation had no reduced-motion gating at all (the file the issue names at line 99, which is whereuseReducedMotion()is called).src/components/golf/coachhelm/v3/HoleShotPath/index.tsx— the hole-shot-path segment draw (pathLength) and shot-dot pop-in, used in round-detail/round-review shot visualization — matches the reported "score to par line freezes mid-draw."src/components/ui/animated-number.tsx— the NumberFlow mount-roll wrapper artificially helddisplayValueat0for the stagger-delay window regardless of motion preference. NumberFlow's ownrespectMotionPreferencedisables its internal spin, but our own wrapper charade above it was the thing actually freezing Command Center stat numerals at 0. Now snaps straight to the final value under reduced motion with no timer at all.src/components/baseball/living-annual/Reveal.tsxandsrc/components/ui/reveal.tsxwere already correct (confirmed by reading + their existing passing tests) — not touched.Tests
Added
src/components/ui/__tests__/animated-number.test.tsx(mocksframer-motion'suseReducedMotionper repo convention, same shape asReveal.test.tsx), 6 cases:0 → value0ever shownstaggerIndex(no held-at-0 delay window)Gates (run in the worktree)
npx tsc --noEmit -p tsconfig.json— cleannpx eslint <9 changed files>— cleannpx vitest run src/components/golf src/components/landing src/components/ui src/components/baseball/living-annual "src/app/golf/(auth)"— 956/956 passed, 4 skipped, 0 regressionsCaveats
forgot-password/reset-password(infinite-repeatx/y/scaleloops, noinitialprop, not "starts hidden") were left untouched — out of scope for a reveal fix.HoleShotPath's hover-tooltipm.div(only mounts on user hover/focus, not on page load) was left as-is — not a "content invisible at rest" case.vercel.jsoncrons, or attempt to merge/deploy per workflow rules.🤖 Generated with Claude Code