refactor(dashboard): one live reader of prefers-reduced-motion - #10461
Conversation
Four inline matchMedia('(prefers-reduced-motion: reduce)') + change-listener
blocks re-implemented the read that useReducedMotion (added in kirodotdev#10108) already
provides. Route ChatSidebar, VoiceDictationPanel, ThemeExperienceLayer and the
design-critique app's useReduceMotion through the shared hook so the dashboard
has one live reader instead of four spellings that drift.
Pure refactor: the reduced/reduceMotion values and all downstream use are
unchanged; the hook preserves the live 'change' subscription each copy had.
Fixes kirodotdev#10304
UX Review (Fable 5, fork) — ✅ PASSUX-level review of This PR is a pure refactor with no user-visible surface: four inline UX-Verdict: PASS Pure internal refactor — no user-visible control, string, or state changes; the shared hook preserves the live subscription behavior each inline copy had. [UX-REVIEWED] b769597 |
Design Review (Fable 5, fork) — ✅ PASSDesign-level review of The shared hook's semantics match each removed inline copy (SSR/no-matchMedia guard, mount-time read, live Design-Verdict: PASS Four drifting inline copies collapse onto the already-established shared hook with matching semantics — the right shape, nothing new invented. [DESIGN-REVIEWED] b769597 |
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. The only candidate — the dropped [OPUS-REVIEWED] b769597 |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of All four base sites match the diff, the shared hook exists on base with the same live-subscription behavior, and the design-critique alias has exactly one real consumer. Emitting the review. First-Principles-Verdict: CONCERNS The kept Not justified as shipped
What this change shipsInventory (4 items) — 3 justifiedIntent: collapse four inline live readers of
WatchThe kept alias means two names for one capability in a repo that also imports framer-motion's same-named Subtractions
[FIRST-PRINCIPLES-REVIEWED] b769597 |
iamwhatever
left a comment
There was a problem hiding this comment.
Tier 1 auto-approve: refactor (4 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: code-move only -- four local prefers-reduced-motion readers (design-critique/hooks.ts, ThemeExperienceLayer, VoiceDictationPanel, ChatSidebar) collapsed onto the shared useReducedMotion live reader, each call site keeping its existing name and semantics, no behaviour change. CodeQL is not applicable on this fork PR (default-setup emits no check-run); SAST coverage is Semgrep only, latest run success with 0 annotations.
Problem / Motivation
Four sites in the dashboard re-implement the same
matchMedia('(prefers-reduced-motion: reduce)')+change-listener block thatwebsite/src/hooks/useReducedMotion.ts(added in #10108) already provides:components/ChatSidebar.tsx(~4110)components/VoiceDictationPanel.tsx(~145)components/ThemeExperienceLayer.tsx(~525, plus a localreadReducedMotionhelper)apps/design-critique/hooks.ts::useReduceMotionThree spellings of "read the motion preference live" drift exactly the way
ChatSidebar's comment already had. This is the deferred cleanup from the FirstPrinciples review on #10108, which hoisted the hook out of
PackAvatarand theIssue Radar
PipelineViewand counted these remaining inline subscriptions.Why it matters
A single reader is one place to fix if the media-query handling ever needs to
change (the JS players in #10108 rely on it, since the stylesheet's global
reduced-motion rule cannot reach JS-driven animation). Four copies are four
places to keep in step, and one comment had already drifted.
What changed (motivation → approach → change)
their own copy of the same subscribe-to-
changelogic.useReducedMotionat each site rather than inventinganything new, matching the pattern feat(crews): play Lottie and sprite packs on crew avatars #10108 established. The design-critique
app keeps its local
useReduceMotionname (a thin alias over the shared hook)so its own call sites stay unchanged.
useState+useEffectblocks (andThemeExperienceLayer'sreadReducedMotionhelper) with the shared hook. Thereduced/reduceMotionvalues and every downstream use are untouched, andthe hook preserves the live
changesubscription each copy had. Net −47 lines.Tests
No new tests: this is a pure refactor and the four sites' existing suites cover
the behaviour. Verified locally:
vitest run(full suite) → 2053 test files pass, includingChatSidebar.*,VoiceDictationPanel.test.tsx(16), andThemeExperienceLayer.test.tsx(30) which exercise the reduced-motion branch.tsc --noEmitclean.git grep -n "prefers-reduced-motion" website/src --and --not -e test: thefour named live readers no longer appear as inline subscriptions; the shared
hook, the stylesheet rules, and one-shot
.matchesreads (out of this issue'sscope) remain.
Manual verification
A throwaway component-level test (not committed) rendered the shared hook
against a live mock
MediaQueryList: it reads the initial preference in bothstates and re-renders when the media query flips at runtime, confirming the live
subscription the inline copies had is preserved end-to-end.
Scope note
This collapses the four live readers the issue named. The repo also has
several one-shot
.matchesreads (e.g.FlyingQuote,disintegrate,ChatSidebar's row-cap read) that do not subscribe to changes; those are adifferent shape and out of this issue's scope, so the acceptance grep still
shows them. Happy to widen if a maintainer prefers.
Related Issues
Fixes #10304
Follows up on #10108