Problem or Motivation
OpenWork animates heavily — the transcript, session list, dropdowns, toasts, the
pet, and the empty-composer entrance all use motion/react (framer-motion)
motion / AnimatePresence transitions. There is currently no way to turn
these down. Users who are sensitive to motion (vestibular disorders,
photosensitivity) or who simply prefer a calmer, snappier UI have no control:
the app never consults the OS "reduce motion" preference and offers no in-app
toggle.
Comparable desktop apps ship exactly this. Claude's desktop app, macOS, and
Windows all expose a Reduce motion / reduce animations accessibility control,
and the web platform standardizes it as prefers-reduced-motion. OpenWork's
Appearance settings already cover theme mode, color theme, font, language, pet,
and "rich tool descriptions" — an accessibility motion control is the conspicuous
gap.
Proposed Solution
Add a "Reduce motion" toggle to Settings → Appearance → Interface (next to
"Rich tool descriptions"). When enabled it:
- Forces
motion/react to short-circuit all motion component animations
app-wide by rendering the app inside <MotionConfig reducedMotion="always">
(when off, reducedMotion="user" so the OS prefers-reduced-motion setting is
still respected — a strictly better default than today's "always animate").
- Sets
data-reduce-motion="true" on <html>, and a global CSS rule collapses
CSS animations/transitions (animation-duration/transition-duration →
~0ms) so non-motion CSS effects are covered too.
- Persists the choice in the renderer's
localStorage (the same
lib/local-storage.ts mechanism used by other renderer UI prefs like
showConnectionIcons), so it survives restarts.
Feasibility
Classification: pure frontend — candidate. No qwen-code backend involvement.
motion is already a dependency (apps/electron + packages/ui); MotionConfig
propagates through React context to every motion component in the tree,
including those in packages/ui, so wrapping once at the renderer root
(main.tsx's Root) covers the whole app.
- Persistence reuses the existing
lib/local-storage.ts KEYS/get/set helper
— no new IPC, no config.json, no main-process change beyond the renderer.
- The toggle reuses the existing
SettingsToggle component and Appearance page
structure. Reuses settings.appearance.interface section heading; two new i18n
keys (settings.appearance.reduceMotion / ...Desc) added to all 7 locales.
Alternatives Considered
- Respect only the OS
prefers-reduced-motion (no in-app toggle): better than
nothing, but users often want reduced motion in one app without changing an
OS-wide setting. The proposed default already honors the OS value and adds an
explicit override on top.
- A per-animation opt-out: far more invasive and easy to miss spots; a single
root-level MotionConfig + CSS guard is the standard, comprehensive approach.
Additional Context
Acceptance criteria (CDP e2e assertion)
A new e2e/assertions/reduce-motion.assert.ts drives the real built app over CDP
entirely in the draft/no-session state (no seeded conversation, no backend
connection needed — it only touches Settings):
- Open Settings → Appearance from the sidebar; the Reduce motion toggle
renders and is initially off (aria-checked="false"), and <html> has no
data-reduce-motion attribute.
- Click the toggle → it flips to
aria-checked="true", <html> gains
data-reduce-motion="true", and the localStorage key
(craft-reduce-motion) is persisted as true — proving the toggle actually
applies and persists the preference, not merely renders.
- Click again → the toggle returns to
aria-checked="false", the <html>
attribute is removed, and the persisted value is false.
Part of the autonomous desktop-feature loop (loop-bot).
Problem or Motivation
OpenWork animates heavily — the transcript, session list, dropdowns, toasts, the
pet, and the empty-composer entrance all use
motion/react(framer-motion)motion/AnimatePresencetransitions. There is currently no way to turnthese down. Users who are sensitive to motion (vestibular disorders,
photosensitivity) or who simply prefer a calmer, snappier UI have no control:
the app never consults the OS "reduce motion" preference and offers no in-app
toggle.
Comparable desktop apps ship exactly this. Claude's desktop app, macOS, and
Windows all expose a Reduce motion / reduce animations accessibility control,
and the web platform standardizes it as
prefers-reduced-motion. OpenWork'sAppearance settings already cover theme mode, color theme, font, language, pet,
and "rich tool descriptions" — an accessibility motion control is the conspicuous
gap.
Proposed Solution
Add a "Reduce motion" toggle to Settings → Appearance → Interface (next to
"Rich tool descriptions"). When enabled it:
motion/reactto short-circuit allmotioncomponent animationsapp-wide by rendering the app inside
<MotionConfig reducedMotion="always">(when off,
reducedMotion="user"so the OSprefers-reduced-motionsetting isstill respected — a strictly better default than today's "always animate").
data-reduce-motion="true"on<html>, and a global CSS rule collapsesCSS animations/transitions (
animation-duration/transition-duration→~0ms) so non-
motionCSS effects are covered too.localStorage(the samelib/local-storage.tsmechanism used by other renderer UI prefs likeshowConnectionIcons), so it survives restarts.Feasibility
Classification: pure frontend — candidate. No qwen-code backend involvement.
motionis already a dependency (apps/electron+packages/ui);MotionConfigpropagates through React context to every
motioncomponent in the tree,including those in
packages/ui, so wrapping once at the renderer root(
main.tsx'sRoot) covers the whole app.lib/local-storage.tsKEYS/get/sethelper— no new IPC, no
config.json, no main-process change beyond the renderer.SettingsTogglecomponent and Appearance pagestructure. Reuses
settings.appearance.interfacesection heading; two new i18nkeys (
settings.appearance.reduceMotion/...Desc) added to all 7 locales.Alternatives Considered
prefers-reduced-motion(no in-app toggle): better thannothing, but users often want reduced motion in one app without changing an
OS-wide setting. The proposed default already honors the OS value and adds an
explicit override on top.
root-level
MotionConfig+ CSS guard is the standard, comprehensive approach.Additional Context
Acceptance criteria (CDP e2e assertion)
A new
e2e/assertions/reduce-motion.assert.tsdrives the real built app over CDPentirely in the draft/no-session state (no seeded conversation, no backend
connection needed — it only touches Settings):
renders and is initially off (
aria-checked="false"), and<html>has nodata-reduce-motionattribute.aria-checked="true",<html>gainsdata-reduce-motion="true", and thelocalStoragekey(
craft-reduce-motion) is persisted astrue— proving the toggle actuallyapplies and persists the preference, not merely renders.
aria-checked="false", the<html>attribute is removed, and the persisted value is
false.Part of the autonomous desktop-feature loop (
loop-bot).