Skip to content

fix: honor prefers-reduced-motion for page transitions (MON-194) - #297

Merged
Walid-peach merged 2 commits into
masterfrom
feature/mon-194-animations-ignore-prefers-reduced-motion-rgaa-138139-wcag
Aug 1, 2026
Merged

fix: honor prefers-reduced-motion for page transitions (MON-194)#297
Walid-peach merged 2 commits into
masterfrom
feature/mon-194-animations-ignore-prefers-reduced-motion-rgaa-138139-wcag

Conversation

@Walid-peach

Copy link
Copy Markdown
Owner

What

Closes the remaining gap in prefers-reduced-motion support flagged by the RGAA self-audit (MON-194, RGAA 13.8/13.9 / WCAG 2.3.3), and removes the now-fixed item from the /accessibilite declaration.

Why

The audit found prefers-reduced-motion unreferenced by literal grep, but the landing page's cinematic scroll hero (AssemblyScrollExperience.tsx) already gates on framer-motion's useReducedMotion() and falls back to StaticExperience — that's why the string search came up empty even though the behavior existed. Auditing the actual remaining continuous/large-motion effects turned up two real gaps: PageTransition.tsx, an unguarded fade + 6px translate that runs on every route change site-wide, and no CSS-level prefers-reduced-motion fallback in globals.css for anything not already gated in script (e.g. the nudge scroll-hint keyframe).

Changes

  • frontend/src/components/PageTransition.tsx: read useReducedMotion() and skip the fade/translate animation when set, showing content immediately instead.
  • frontend/src/app/globals.css: add a global @media (prefers-reduced-motion: reduce) override that collapses animation/transition durations and disables smooth scrolling, as a CSS-level fallback in addition to the existing JS gates (AssemblyScrollExperience, LiveAssemblyPulse, QuizDeck, and now PageTransition).
  • frontend/src/app/accessibilite/page.tsx: remove the "Animations non désactivables" non-conformity item now that it's fixed, following the same pattern as the MON-192/193/195 fixes.

Testing

  • npm run lint — clean.
  • npm test — 25 suites / 197 tests pass.
  • npm run build — compiles and type-checks cleanly; the /votes prerender step fails locally with a 500 from the API, but that's a pre-existing local-environment gap (no local API/DB running against which to statically generate that page) — confirmed the change only touches PageTransition.tsx, globals.css, and the static /accessibilite page, none of which touch /votes data fetching.
  • Not manually tested in a real browser with OS-level "reduce motion" toggled (no interactive browser session in this environment) — reasoned through the code path instead: useReducedMotion() from framer-motion reflects matchMedia('(prefers-reduced-motion: reduce)') synchronously on first client render, so AssemblyScrollExperience renders StaticExperience and PageTransition skips its transition, for a user with the OS preference set.

Risks / Notes

  • No schema, deploy config, or CI workflow changes.
  • The global CSS override in globals.css is a broad * selector; it only fires under prefers-reduced-motion: reduce and only clamps animation/transition duration, so it shouldn't visually break anything — it just makes the very short hover micro-transitions (e.g. HemicycleChart seat dots) effectively instant too, which is expected and desired.

Breaking Changes

None.

The landing page's cinematic scroll hero already gated on
useReducedMotion(). The remaining gap was PageTransition.tsx, which
runs an unguarded fade/translate on every route change, and the
globals.css nudge keyframe having no native CSS fallback.

Guard the page-transition animation behind useReducedMotion() and add
a global @media (prefers-reduced-motion: reduce) override in
globals.css as a CSS-level fallback for anything not already gated in
script. Remove the now-fixed item from the /accessibilite declaration.
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mon-elu Ready Ready Preview Aug 1, 2026 10:37pm

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

🧪 dbt data-health check — ✅ passed

22:31:22  52 of 62 PASS source_not_null_raw_votes_vote_id ................................ [PASS in 0.91s]
22:31:22  56 of 62 START test source_unique_raw_deputies_deputy_id ....................... [RUN]
22:31:23  53 of 62 PASS source_not_null_raw_votes_voted_at ............................... [PASS in 0.92s]
22:31:23  57 of 62 START test source_unique_raw_votes_vote_id ............................ [RUN]
22:31:23  56 of 62 PASS source_unique_raw_deputies_deputy_id ............................. [PASS in 0.89s]
22:31:23  58 of 62 START test unique_mart_deputy_scorecard_deputy_id ..................... [RUN]
22:31:23  54 of 62 PASS source_relationships_raw_vote_positions_deputy_id__deputy_id__source_raw_deputies_  [PASS in 1.02s]
22:31:23  59 of 62 START test unique_mart_party_alignment_deputy_id ...................... [RUN]
22:31:23  55 of 62 PASS source_relationships_raw_vote_positions_vote_id__vote_id__source_raw_votes_  [PASS in 1.12s]
22:31:23  60 of 62 START test unique_mart_vote_summary_vote_id ........................... [RUN]
22:31:24  57 of 62 PASS source_unique_raw_votes_vote_id .................................. [PASS in 0.88s]
22:31:24  61 of 62 START test unique_stg_deputies_deputy_id .............................. [RUN]
22:31:24  58 of 62 PASS unique_mart_deputy_scorecard_deputy_id ........................... [PASS in 0.96s]
22:31:24  62 of 62 START test unique_stg_votes_vote_id ................................... [RUN]
22:31:24  59 of 62 PASS unique_mart_party_alignment_deputy_id ............................ [PASS in 0.98s]
22:31:24  60 of 62 PASS unique_mart_vote_summary_vote_id ................................. [PASS in 0.98s]
22:31:25  61 of 62 PASS unique_stg_deputies_deputy_id .................................... [PASS in 1.00s]
22:31:25  62 of 62 PASS unique_stg_votes_vote_id ......................................... [PASS in 0.89s]
22:31:26  
22:31:26  Finished running 62 data tests in 0 hours 0 minutes and 20.56 seconds (20.56s).
22:31:26  
22:31:26  Completed successfully
22:31:26  
22:31:26  Done. PASS=62 WARN=0 ERROR=0 SKIP=0 TOTAL=62

Validates prod data health. A failure means current prod data is stale/broken, not necessarily that this PR is wrong.

@Walid-peach Walid-peach left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Attention Score

100/100 - SAFE TO SKIM

  • Base 100
  • No Must Fix findings
  • No Should Fix findings
  • Verdict is not "Needs changes" or "Scope should be split"
  • No high-risk paths touched (no migrations, workflows, railway.json, api/main.py CORS/rate-limit, requirements*.txt)
  • Diff is small: 21 additions / 6 deletions across 3 files (well under 400 lines / 15 files)
  • No new api/, rag/, or scripts/ code, so the "no test changes" penalty doesn't apply
  • Testing / Validation Gaps section is non-empty (see below) but doesn't cross a threshold worth penalizing given how mechanical the change is and how well-established the pattern already is elsewhere in the codebase
  • Nothing the agent couldn't verify from the diff/CI: build, lint, and full Jest suite already pass in CI
    Reason you can skim: three small, mechanical, well-precedented changes with all CI green and no risky surface touched.

Summary

This PR closes the RGAA 13.8/13.9 / WCAG 2.3.3 gap for prefers-reduced-motion. It correctly identifies that the audit's headline claim was stale — AssemblyScrollExperience.tsx already gates its cinematic scroll hero behind useReducedMotion() — and instead fixes the two real remaining gaps: PageTransition.tsx (an unguarded fade+translate on every route change) and a missing CSS-level @media (prefers-reduced-motion: reduce) fallback in globals.css. It also removes the now-resolved item from /accessibilite, following the exact pattern of the three prior RGAA-fix PRs (MON-192/193/195).

Must Fix

None.

Should Fix

None.

Nice to Have

  • PageTransition.tsx has no test coverage for the reduceMotion branch. QuizClient.test.tsx already shows the pattern for mocking useReducedMotion from framer-motion in this codebase, so a similar test here (asserting opacity/transform are set immediately and the requestAnimationFrame path is skipped) would be cheap to add and would guard the behavior long-term. Not blocking given how small and mechanical the change is.
  • The global CSS override in globals.css uses a broad *, *::before, *::after selector. This is the standard, well-known idiom for this problem (and intentionally uses 0.001ms rather than 0s so transitionend listeners still fire), so it's not a concern — just worth a reviewer's awareness that it also collapses the very short hover micro-transitions in HemicycleChart.tsx (0.1–0.15s) under reduced motion, which is expected/desired but slightly broader than "large motion" framing in the issue title.

Testing / Validation Gaps

  • No new automated test was added for the PageTransition reduced-motion branch (see Nice to Have above).
  • No manual browser verification with OS-level "reduced motion" toggled was performed (per the PR's own Testing section) — verified by code reading only. Low risk given the identical pattern already ships and works for AssemblyScrollExperience, LiveAssemblyPulse, and QuizDeck.

Documentation / Reviewer Notes

  • /accessibilite update correctly follows the established pattern (MON-192/193/195) for removing a fixed non-conformity item — no further doc changes needed.
  • Reviewers should double check in a real browser with "reduce motion" enabled that page navigation no longer shows the fade/translate, since that's the one path not verified end-to-end here.

Verdict

Ready to merge

@Walid-peach Walid-peach added the safe-to-skim PR review score above threshold - agent review likely sufficient label Aug 1, 2026
MON-196 (deputy photo alt text) merged in parallel and also removed a
non-conformity item from the same /accessibilite list. With both items
now fixed, rewrite the "Résultats des tests" and "Contenus non
accessibles" sections to state no known non-conformities remain,
instead of leaving an orphaned empty list.
@Walid-peach
Walid-peach merged commit 9c1005c into master Aug 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe-to-skim PR review score above threshold - agent review likely sufficient

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant