Skip to content

refactor(dashboard): one live reader of prefers-reduced-motion - #10461

Merged
iamwhatever merged 1 commit into
kirodotdev:mainfrom
chuqijiang2026:fix/reduced-motion-single-reader-10304
Sep 13, 2026
Merged

refactor(dashboard): one live reader of prefers-reduced-motion#10461
iamwhatever merged 1 commit into
kirodotdev:mainfrom
chuqijiang2026:fix/reduced-motion-single-reader-10304

Conversation

@chuqijiang2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

Four sites in the dashboard re-implement the same
matchMedia('(prefers-reduced-motion: reduce)') + change-listener block that
website/src/hooks/useReducedMotion.ts (added in #10108) already provides:

  • components/ChatSidebar.tsx (~4110)
  • components/VoiceDictationPanel.tsx (~145)
  • components/ThemeExperienceLayer.tsx (~525, plus a local readReducedMotion helper)
  • apps/design-critique/hooks.ts::useReduceMotion

Three spellings of "read the motion preference live" drift exactly the way
ChatSidebar's comment already had. This is the deferred cleanup from the First
Principles review on #10108, which hoisted the hook out of PackAvatar and the
Issue Radar PipelineView and 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)

  • Root cause: the shared hook existed but four call sites still carried
    their own copy of the same subscribe-to-change logic.
  • Approach: reuse useReducedMotion at each site rather than inventing
    anything new, matching the pattern feat(crews): play Lottie and sprite packs on crew avatars #10108 established. The design-critique
    app keeps its local useReduceMotion name (a thin alias over the shared hook)
    so its own call sites stay unchanged.
  • Change: replace the inline useState+useEffect blocks (and
    ThemeExperienceLayer's readReducedMotion helper) with the shared hook. The
    reduced / reduceMotion values and every downstream use are untouched, and
    the hook preserves the live change subscription 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, including
    ChatSidebar.*, VoiceDictationPanel.test.tsx (16), and
    ThemeExperienceLayer.test.tsx (30) which exercise the reduced-motion branch.
  • tsc --noEmit clean.
  • git grep -n "prefers-reduced-motion" website/src --and --not -e test: the
    four named live readers no longer appear as inline subscriptions; the shared
    hook, the stylesheet rules, and one-shot .matches reads (out of this issue's
    scope) 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 both
states 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 .matches reads (e.g. FlyingQuote, disintegrate,
ChatSidebar's row-cap read) that do not subscribe to changes; those are a
different 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

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
@chuqijiang2026
chuqijiang2026 requested a review from a team September 13, 2026 02:51
@chuqijiang2026
chuqijiang2026 requested a review from a team as a code owner September 13, 2026 02:51
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

UX Review (Fable 5, fork) — ✅ PASS

UX-level review of b7695973c5f898147f85736277b4644cfac513e0 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

This PR is a pure refactor with no user-visible surface: four inline prefers-reduced-motion readers are replaced by the shared useReducedMotion hook, which preserves both the lazy initial read and the live change subscription each copy had. No strings, controls, states, or element placements change, so no screenshot or recording evidence is required, and the empty attachment lists are consistent with that.

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

@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — ✅ PASS

Design-level review of b7695973c5f898147f85736277b4644cfac513e0 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The shared hook's semantics match each removed inline copy (SSR/no-matchMedia guard, mount-time read, live change subscription), and the diff is exactly the four consolidations the description names. This is a clean, proportionate DRY refactor onto an existing abstraction, with the one debatable choice (keeping the useReduceMotion alias in the design-critique app) explicitly justified. No design-level concerns.

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

@github-actions

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

Reviewed b7695973c5f898147f85736277b4644cfac513e0 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] b769597

@github-actions

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed b7695973c5f898147f85736277b4644cfac513e0 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.

The only candidate — the dropped addListener fallback in design-critique — requires a browser that exposes MediaQueryList.addListener but not addEventListener (Safari < 14). Whether that engine is in the project's support scope is unestablished (the candidate itself concedes this), so (a) "a concrete input that occurs in practice" cannot be re-derived. Even granting it, the initial read is preserved via the one-shot onChange(); only a mid-session preference flip is missed on that engine — a graceful degradation, not a crash/security/data-loss defect, and one that aligns design-critique with the three other call sites that already lacked the fallback. Below the 80 bar; dropped.

[OPUS-REVIEWED] b769597

@github-actions

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5, fork) — 🟡 CONCERNS

Premise-level review of b7695973c5f898147f85736277b4644cfac513e0 via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

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 useReduceMotion alias is a second spelling of the shared hook with exactly one consumer — delete it, don't wrap it.

Not justified as shipped

  1. design-critique keeps its local useReduceMotion name as a wrapper — duplicate of website/src/hooks/useReducedMotion.ts: the stated reason, "so this app's call sites stay unchanged", protects exactly one call site (grep useReduceMotion( → 1 consumer, DesignCritiquePage.tsx:102; the Motion.tsx:9 hit is a comment), so the second name costs more than the one-line import edit it avoids.

What this change ships

Inventory (4 items) — 3 justified

Intent: collapse four inline live readers of prefers-reduced-motion onto the existing shared hook — a FIX (deferred cleanup, linked issue #10304).

  1. design-critique's motion read now delegates to the shared hook under the old local name — duplicate of website/src/hooks/useReducedMotion.ts (second spelling, 1 consumer)
  2. ThemeExperienceLayer's local readReducedMotion helper and change-listener effect replaced by the shared hook — justified
  3. VoiceDictationPanel's inline state+effect replaced by the shared hook — justified
  4. ChatSidebar's inline state+effect replaced by the shared hook; the comment the description calls drifted is rewritten, preserving its not-framer rationale — justified

Watch

The kept alias means two names for one capability in a repo that also imports framer-motion's same-named useReducedMotion in 25+ files; every future reader must learn which of three spellings is which.
Clears when: the alias in apps/design-critique/hooks.ts is deleted and DesignCritiquePage.tsx:102 imports the shared hook.

Subtractions

  • Delete useReduceMotion in website/src/apps/design-critique/hooks.ts and import useReducedMotion from ../../hooks/useReducedMotion at its single call site, DesignCritiquePage.tsx:102 (1 consumer counted; pattern useReduceMotion(). Net: 6 more lines gone.

[FIRST-PRINCIPLES-REVIEWED] b769597

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 13, 2026
@iamwhatever
iamwhatever enabled auto-merge (squash) September 13, 2026 04:06

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

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.

@iamwhatever
iamwhatever merged commit f1c365d into kirodotdev:main Sep 13, 2026
81 of 82 checks passed
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dashboard: one live reader of prefers-reduced-motion (four inline copies remain)

2 participants