Add a "Reduce motion" accessibility setting to Appearance#51
Open
DragonnZhang wants to merge 2 commits into
Open
Add a "Reduce motion" accessibility setting to Appearance#51DragonnZhang wants to merge 2 commits into
DragonnZhang wants to merge 2 commits into
Conversation
Add a renderer-only "Reduce motion" preference to Settings → Appearance → Interface. When enabled the app renders inside <MotionConfig reducedMotion= "always"> so every motion/react component short-circuits its animations, sets data-reduce-motion="true" on <html> for a global CSS guard that collapses plain CSS animations/transitions, and persists the choice in localStorage. When disabled it uses reducedMotion="user", still honoring the OS prefers-reduced-motion setting. - New ReduceMotionProvider wired at the renderer root (main.tsx). - Appearance toggle reuses SettingsToggle (gains an optional testId prop). - data-testid on settings-navigator items for e2e navigation. - Two new i18n keys across all 7 locales. - CDP e2e assertion: e2e/assertions/reduce-motion.assert.ts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #50
What & why
OpenWork animates heavily — the transcript, session list, dropdowns, toasts, the
pet, and the empty-composer entrance all use
motion/react(framer-motion)motion/AnimatePresencetransitions — and there was no way to turn themdown. The app never consulted the OS
prefers-reduced-motionpreference andoffered no in-app control, which is a real accessibility gap (vestibular
sensitivity, photosensitivity) and a common desktop expectation: Claude's desktop
app, macOS, and Windows all expose a Reduce motion control.
This adds a "Reduce motion" toggle to Settings → Appearance → Interface
(next to "Rich tool descriptions"). When enabled it:
<MotionConfig reducedMotion="always">so everymotion/reactcomponent short-circuits its animations app-wide (MotionConfigpropagates through React context to all
motioncomponents in the tree,including those in
packages/ui);data-reduce-motion="true"on<html>, and a global CSS guard collapsesplain CSS animations/transitions (
animation-duration/transition-duration→ ~0ms) so non-
motioneffects are covered too;localStorage(the samelib/local-storage.tsmechanism as other renderer UI prefs), surviving restarts.When disabled it uses
reducedMotion="user", which still honors the OSprefers-reduced-motionsetting — a strictly better default than today'salways-animate behavior.
Frontend-only. No backend / qwen-code change:
motionis already a dependencyin both
apps/electronandpackages/ui, persistence reuses the existinglocal-storagehelper (no IPC, noconfig.json), and the toggle reuses theexisting
SettingsToggle+ Appearance page structure.Changes
context/ReduceMotionContext.tsx(new) —ReduceMotionProvider(owns thepref, applies the
<html>attribute, wraps children inMotionConfig) +useReduceMotion()hook.main.tsx— mountReduceMotionProviderat the renderer root (insideThemeProvider, wrappingApp/Toaster/ pet), so the whole app inherits it.lib/local-storage.ts— newreduceMotionkey (craft-reduce-motion).index.css— global[data-reduce-motion='true']CSS guard.pages/settings/AppearanceSettingsPage.tsx— the toggle in the Interfacesection.
components/settings/SettingsToggle.tsx— optionaltestIdprop forwardedto the underlying switch (for e2e; backward-compatible).
pages/settings/SettingsNavigator.tsx—data-testid={settings-nav-${id}}on each nav item so the assertion can open the Appearance page (per the harness
README's "add a
data-testidin the same PR" guidance).packages/shared/src/i18n/locales/*.json—settings.appearance.reduceMotion/
...Descin all 7 locales (reuses the existingsettings.appearance.interfacesection heading).
e2e/assertions/reduce-motion.assert.ts(new) — CDP assertion (below).Verification (DoD)
bun run typecheck:all— introduces no new errors. Delta vsmainiszero: the only errors are the 11 pre-existing ones in
apps/electron(
auto-update.tsowner/repo, asettings-default-thinkingtest tuple, twotest files importing
vitest) — none in the files this PR touches.packages/{core,shared,server-core,server,session-tools-core,ui}all pass clean.bun test— the failing set is byte-for-byte identical tomain,verified by diffing the sorted unique failure lists from a clean-
mainrun andthis branch in the same environment: 56 pre-existing failures on both
(
BrowserCDP, RPC handler profiles,RoutedClient,startWebuiHttpServer,i18n sorted checks,
resource-bundle, …). This change adds zero newfailures. (The raw
N failtotal is order-dependent when the whole suite runsunisolated — the deterministic signal is the failing-test set, which is
unchanged.)
bun run lint:i18n:parity— OK (1544 keys per locale). The two new keys areadded to every locale.
bun run build:renderer, where this change lives) — ✅builds cleanly.
bun build e2e/assertions/reduce-motion.assert.ts)— ✅.
CDP e2e assertion (
reduce-motion.assert.ts)Drives the real built app over CDP entirely in the draft/no-session state — no
seeded conversation and no backend connection (it only touches Settings):
initially off (
aria-checked="false"), with nodata-reduce-motionattribute on
<html>.aria-checked="true",<html>gainsdata-reduce-motion="true", andlocalStorage["craft-reduce-motion"]is"true"— proving it applies and persists, not merely renders.aria-checked="false", the attribute is removed, and thepersisted value is
"false".Part of the autonomous desktop-feature loop (
loop-bot).🤖 Generated with Claude Code
Generated by Claude Code