From c1e7614e9202bf7dd505de2b425da0615b134180 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 13:34:39 +0000 Subject: [PATCH 1/2] Add an "Increase contrast" accessibility setting to Appearance --- .../renderer/context/HighContrastContext.tsx | 79 ++++++++++++ apps/electron/src/renderer/index.css | 32 +++++ .../src/renderer/lib/local-storage.ts | 1 + apps/electron/src/renderer/main.tsx | 9 +- .../pages/settings/AppearanceSettingsPage.tsx | 11 ++ docs/loop/feature-ledger.md | 3 +- e2e/assertions/high-contrast.assert.ts | 117 ++++++++++++++++++ packages/shared/src/i18n/locales/de.json | 2 + packages/shared/src/i18n/locales/en.json | 2 + packages/shared/src/i18n/locales/es.json | 2 + packages/shared/src/i18n/locales/hu.json | 2 + packages/shared/src/i18n/locales/ja.json | 2 + packages/shared/src/i18n/locales/pl.json | 2 + packages/shared/src/i18n/locales/zh-Hans.json | 2 + 14 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 apps/electron/src/renderer/context/HighContrastContext.tsx create mode 100644 e2e/assertions/high-contrast.assert.ts diff --git a/apps/electron/src/renderer/context/HighContrastContext.tsx b/apps/electron/src/renderer/context/HighContrastContext.tsx new file mode 100644 index 000000000..c2e45ac34 --- /dev/null +++ b/apps/electron/src/renderer/context/HighContrastContext.tsx @@ -0,0 +1,79 @@ +/** + * HighContrastContext + * + * App-wide "Increase contrast" accessibility preference — the natural companion + * to `ReduceMotionContext`, living beside it in Settings → Appearance → Interface. + * + * When enabled it sets `data-high-contrast="true"` on ``. The global CSS + * block in `index.css` gated on `:root[data-high-contrast='true']` then raises + * the contrast of the theme tokens — strengthening `--border`, `--input`, + * `--muted-foreground`, `--foreground-dimmed` and the focus `--ring`, and adding + * a visible focus outline. Because every theme derives those tokens from + * `--foreground` (which flips per light/dark), the alpha-based overrides work in + * both light and dark and across preset themes. The theme system injects its + * colors as a `:root { … }` stylesheet rule, so the higher-specificity + * `:root[data-high-contrast='true']` selector wins without `!important`. + * + * The preference is persisted in `localStorage` (renderer-only, no backend), + * mirroring the other lightweight UI prefs in `lib/local-storage.ts`. + */ + +import { + createContext, + useContext, + useState, + useEffect, + useCallback, + type ReactNode, +} from 'react' +import * as storage from '@/lib/local-storage' + +interface HighContrastContextType { + highContrast: boolean + setHighContrast: (value: boolean) => void +} + +const HighContrastContext = createContext(null) + +const HIGH_CONTRAST_ATTR = 'data-high-contrast' + +/** Reflect the preference onto so the global CSS overrides can react. */ +function applyHighContrastAttribute(enabled: boolean): void { + const root = document.documentElement + if (enabled) { + root.setAttribute(HIGH_CONTRAST_ATTR, 'true') + } else { + root.removeAttribute(HIGH_CONTRAST_ATTR) + } +} + +export function HighContrastProvider({ children }: { children: ReactNode }) { + const [highContrast, setHighContrastState] = useState(() => + storage.get(storage.KEYS.highContrast, false), + ) + + // Keep the DOM attribute in sync (also covers the initial value on mount). + useEffect(() => { + applyHighContrastAttribute(highContrast) + }, [highContrast]) + + const setHighContrast = useCallback((value: boolean) => { + setHighContrastState(value) + storage.set(storage.KEYS.highContrast, value) + applyHighContrastAttribute(value) + }, []) + + return ( + + {children} + + ) +} + +export function useHighContrast(): HighContrastContextType { + const ctx = useContext(HighContrastContext) + if (!ctx) { + throw new Error('useHighContrast must be used within a HighContrastProvider') + } + return ctx +} diff --git a/apps/electron/src/renderer/index.css b/apps/electron/src/renderer/index.css index 5efb958b0..7d92b0e53 100644 --- a/apps/electron/src/renderer/index.css +++ b/apps/electron/src/renderer/index.css @@ -1386,3 +1386,35 @@ html.dark[data-scenic] .fullscreen-overlay-background { transition-delay: 0ms !important; scroll-behavior: auto !important; } + +/* Increase contrast: when the user enables "Increase contrast" in Appearance + settings, raise the contrast of the low-alpha theme tokens app-wide. Every + theme derives these from `--foreground` (which flips per light/dark), so the + alpha-based overrides work in both modes and across preset themes. The theme + system injects its colors as a plain `:root { … }` rule, so this + higher-specificity `:root[data-high-contrast='true']` selector wins without + `!important`. `--hc-enabled` is a marker the e2e assertion reads to confirm + the block actually applied. */ +:root[data-high-contrast='true'] { + --hc-enabled: 1; + + /* Stronger borders, dividers and input outlines. */ + --border: oklch(from var(--foreground) l c h / 0.28); + --input: oklch(from var(--foreground) l c h / 0.38); + + /* De-dim secondary/"muted" and unfocused text so it stays readable. */ + --muted-foreground: var(--foreground-80); + --foreground-dimmed: var(--foreground-90); + --md-bullets: var(--foreground-80); + --md-counters: var(--foreground-80); + + /* More visible focus ring. */ + --ring: oklch(from var(--foreground) l c h / 0.6); + --ring-width: 2px; +} + +/* A clearly visible keyboard-focus outline for interactive elements. */ +:root[data-high-contrast='true'] :focus-visible { + outline: 2px solid var(--ring); + outline-offset: 1px; +} diff --git a/apps/electron/src/renderer/lib/local-storage.ts b/apps/electron/src/renderer/lib/local-storage.ts index 4d6446afd..50d3d74ed 100644 --- a/apps/electron/src/renderer/lib/local-storage.ts +++ b/apps/electron/src/renderer/lib/local-storage.ts @@ -53,6 +53,7 @@ export const KEYS = { // Appearance showConnectionIcons: 'show-connection-icons', reduceMotion: 'reduce-motion', // Minimize animations/transitions app-wide + highContrast: 'high-contrast', // Raise contrast of borders, dividers, and muted text app-wide // What's New whatsNewLastSeenVersion: 'whats-new-last-seen-version', diff --git a/apps/electron/src/renderer/main.tsx b/apps/electron/src/renderer/main.tsx index 14729366d..94bc4ad72 100644 --- a/apps/electron/src/renderer/main.tsx +++ b/apps/electron/src/renderer/main.tsx @@ -7,6 +7,7 @@ import { Provider as JotaiProvider, useAtomValue } from 'jotai' import App from './App' import { ThemeProvider } from './context/ThemeContext' import { ReduceMotionProvider } from './context/ReduceMotionContext' +import { HighContrastProvider } from './context/HighContrastContext' import { windowWorkspaceIdAtom } from './atoms/sessions' import { Toaster } from '@/components/ui/sonner' import { PetWindowController } from '@/components/pet/PetWindowController' @@ -108,9 +109,11 @@ function Root() { return ( - - - + + + + + ) diff --git a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx index 4da9ba24c..89b16ed14 100644 --- a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx +++ b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx @@ -15,6 +15,7 @@ import { HeaderMenu } from '@/components/ui/HeaderMenu' import { EditPopover, EditButton, getEditConfig } from '@/components/ui/EditPopover' import { useTheme } from '@/context/ThemeContext' import { useReduceMotion } from '@/context/ReduceMotionContext' +import { useHighContrast } from '@/context/HighContrastContext' import { useAppShellContext } from '@/context/AppShellContext' import { routes } from '@/lib/navigate' import { FolderOpen, Monitor, RefreshCw, Sun, Moon } from 'lucide-react' @@ -143,6 +144,9 @@ export default function AppearanceSettingsPage() { // Reduce motion toggle (renderer-only preference, persisted in localStorage) const { reduceMotion, setReduceMotion } = useReduceMotion() + // Increase contrast toggle (renderer-only preference, persisted in localStorage) + const { highContrast, setHighContrast } = useHighContrast() + // Pet companion settings + custom pets (synced via shared Jotai atoms) const { pets, @@ -387,6 +391,13 @@ export default function AppearanceSettingsPage() { onCheckedChange={setReduceMotion} testId="reduce-motion-toggle" /> + diff --git a/docs/loop/feature-ledger.md b/docs/loop/feature-ledger.md index 51235562a..5636dbb13 100644 --- a/docs/loop/feature-ledger.md +++ b/docs/loop/feature-ledger.md @@ -32,7 +32,8 @@ log, not the system of record. | slug | title | source | feasibility | status | issue | pr | branch | updated | notes | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| reduce-motion | "Reduce motion" accessibility setting in Appearance | Claude desktop / macOS / Windows reduce-motion + `prefers-reduced-motion` | frontend-only | pr-open | [#50](https://github.com/modelstudioai/openwork/issues/50) | [#51](https://github.com/modelstudioai/openwork/pull/51) | loop/reduce-motion | 2026-07-03 | Renderer-only pref (localStorage) applied app-wide via `` + `data-reduce-motion` on `` + global CSS guard. Off ⇒ `reducedMotion="user"` (still honors OS). New `ReduceMotionProvider` in `main.tsx`; toggle in Appearance→Interface; 2 new i18n keys ×7 locales. typecheck/`bun test` zero-delta vs main (56-failure set byte-identical); renderer build ✅; i18n parity ✅. CDP assertion included; **could not run locally** (egress 403s Electron binary download). | +| high-contrast | "Increase contrast" accessibility setting in Appearance | macOS Accessibility "Increase contrast" / Windows contrast themes / VS Code High Contrast | frontend-only | in-progress | [#66](https://github.com/modelstudioai/openwork/issues/66) | — | loop/high-contrast | 2026-07-07 | Companion to merged reduce-motion. Renderer-only pref (`craft-high-contrast` localStorage) → `HighContrastProvider` context → `data-high-contrast` on `` → CSS overrides of theme tokens (`--border`/`--input`/`--muted-foreground`/`--ring`) gated on `:root[data-high-contrast='true']` (higher specificity beats theme `:root` injection). Toggle in Appearance→Interface below Reduce motion; 2 i18n keys ×7 locales. | +| reduce-motion | "Reduce motion" accessibility setting in Appearance | Claude desktop / macOS / Windows reduce-motion + `prefers-reduced-motion` | frontend-only | merged | [#50](https://github.com/modelstudioai/openwork/issues/50) | [#51](https://github.com/modelstudioai/openwork/pull/51) | loop/reduce-motion | 2026-07-07 | **Merged** into `main` (2026-07-06). Renderer-only pref (localStorage) applied app-wide via `` + `data-reduce-motion` on `` + global CSS guard. Off ⇒ `reducedMotion="user"` (still honors OS). | | composer-expand | Expand / collapse (maximize) toggle for the chat composer | Claude/ChatGPT/Codex desktop composer maximize | frontend-only | pr-open | [#48](https://github.com/modelstudioai/openwork/issues/48) | [#49](https://github.com/modelstudioai/openwork/pull/49) | loop/composer-expand | 2026-07-03 | Opened by a prior run. Adds `isComposerExpanded` toggle in `FreeFormInput`; 2 new i18n keys. Awaiting review. | | scroll-to-bottom | "Jump to latest" (scroll-to-bottom) button in the chat transcript | Claude Code / ChatGPT / Codex desktop | frontend-only | pr-open | [#46](https://github.com/modelstudioai/openwork/issues/46) | [#47](https://github.com/modelstudioai/openwork/pull/47) | loop/scroll-to-bottom | 2026-07-02 | Opened by a prior run. Floating jump button in `ChatDisplay` + `seed()` harness hook. Awaiting review. | | thinking-level-picker | Thinking-level (reasoning effort) picker in the chat composer | Claude Code Desktop effort menu (⌘⇧E) + OpenWork's own model picker | frontend-only | merged | [#44](https://github.com/modelstudioai/openwork/issues/44) | [#45](https://github.com/modelstudioai/openwork/pull/45) | loop/thinking-level-picker | 2026-07-03 | **Merged** into `main` (2026-07-02). `thinkingLevel`/`onThinkingLevelChange` already plumbed to `FreeFormInput`; only the UI trigger was missing. Reuses `thinking.*` + `settings.ai.thinking` i18n keys (zero new keys). | diff --git a/e2e/assertions/high-contrast.assert.ts b/e2e/assertions/high-contrast.assert.ts new file mode 100644 index 000000000..2f0175be2 --- /dev/null +++ b/e2e/assertions/high-contrast.assert.ts @@ -0,0 +1,117 @@ +/** + * Feature assertion: the "Increase contrast" toggle in Settings → Appearance + * actually applies and persists an app-wide high-contrast preference. + * + * Drives the real UI over CDP entirely in the draft/no-session state (no seeded + * conversation, no backend connection): opens Settings → Appearance, flips the + * toggle, and asserts the observable effects — the switch state, the + * `data-high-contrast` attribute on , the persisted localStorage value, + * AND that the high-contrast CSS actually applies (the `--hc-enabled` custom + * property computes to `1` on :root only while enabled). Toggling twice proves + * it both applies and reverts, not merely renders. + */ + +import type { Assertion } from '../runner'; + +const SETTINGS_NAV = '[data-testid="nav:settings"]'; +const APPEARANCE_NAV = '[data-testid="settings-nav-appearance"]'; +const TOGGLE = '[data-testid="high-contrast-toggle"]'; +const STORAGE_KEY = 'craft-high-contrast'; + +/** Read the toggle's aria-checked ("true" | "false" | null). */ +function ariaCheckedExpr(): string { + return `(() => { + const el = document.querySelector(${JSON.stringify(TOGGLE)}); + return el ? el.getAttribute('aria-checked') : null; + })()`; +} + +/** True when carries the high-contrast marker attribute. */ +function htmlMarkedExpr(): string { + return `document.documentElement.getAttribute('data-high-contrast') === 'true'`; +} + +/** True when the high-contrast CSS block actually matched (marker custom prop). */ +function cssAppliedExpr(): string { + return `getComputedStyle(document.documentElement).getPropertyValue('--hc-enabled').trim() === '1'`; +} + +/** The persisted localStorage value for the preference. */ +function storedValueExpr(): string { + return `window.localStorage.getItem(${JSON.stringify(STORAGE_KEY)})`; +} + +const assertion: Assertion = { + name: 'increase-contrast toggle applies and persists an app-wide preference', + async run(app) { + const { session } = app; + + // App fully mounted. + await session.waitForFunction( + '!document.getElementById("_loader") && (document.getElementById("root")?.childElementCount ?? 0) > 0', + { timeoutMs: 30000, message: 'app did not mount' }, + ); + + // Open Settings → Appearance (real user path). + await session.click(SETTINGS_NAV, { timeoutMs: 15000 }); + await session.click(APPEARANCE_NAV, { timeoutMs: 15000 }); + + // The toggle is the feature under test — its presence is the first signal. + await session.waitForSelector(TOGGLE, { + timeoutMs: 15000, + message: 'increase-contrast toggle did not render', + }); + + // Initial state: off, no marker on , CSS not applied, not persisted true. + const initialChecked = await session.evaluate(ariaCheckedExpr()); + if (initialChecked !== 'false') { + throw new Error(`expected toggle off initially, saw aria-checked=${initialChecked}`); + } + if (await session.evaluate(htmlMarkedExpr())) { + throw new Error('expected no data-high-contrast attribute before enabling'); + } + if (await session.evaluate(cssAppliedExpr())) { + throw new Error('expected --hc-enabled to be unset before enabling'); + } + + // Enable → toggle on, marked, CSS applied, persisted true. + await session.click(TOGGLE); + await session.waitForFunction( + `${ariaCheckedExpr()} === 'true'`, + { timeoutMs: 5000, message: 'toggle did not switch on' }, + ); + await session.waitForFunction(htmlMarkedExpr(), { + timeoutMs: 5000, + message: 'data-high-contrast was not applied to when enabled', + }); + await session.waitForFunction(cssAppliedExpr(), { + timeoutMs: 5000, + message: 'high-contrast CSS did not apply (--hc-enabled !== 1) when enabled', + }); + const storedOn = await session.evaluate(storedValueExpr()); + if (storedOn !== 'true') { + throw new Error(`expected persisted "true" after enabling, saw ${JSON.stringify(storedOn)}`); + } + + // Disable → toggle off, marker removed, CSS reverted, persisted false. + await session.click(TOGGLE); + await session.waitForFunction( + `${ariaCheckedExpr()} === 'false'`, + { timeoutMs: 5000, message: 'toggle did not switch off' }, + ); + await session.waitForFunction(`!(${htmlMarkedExpr()})`, { + timeoutMs: 5000, + message: 'data-high-contrast was not removed from when disabled', + }); + await session.waitForFunction(`!(${cssAppliedExpr()})`, { + timeoutMs: 5000, + message: 'high-contrast CSS did not revert (--hc-enabled still 1) when disabled', + }); + const storedOff = await session.evaluate(storedValueExpr()); + if (storedOff !== 'false') { + throw new Error(`expected persisted "false" after disabling, saw ${JSON.stringify(storedOff)}`); + } + }, +}; + +export default assertion; diff --git a/packages/shared/src/i18n/locales/de.json b/packages/shared/src/i18n/locales/de.json index d407e5dbb..36bf2d04d 100644 --- a/packages/shared/src/i18n/locales/de.json +++ b/packages/shared/src/i18n/locales/de.json @@ -787,6 +787,8 @@ "settings.appearance.richToolDescriptions": "Ausführliche Werkzeugbeschreibungen", "settings.appearance.reduceMotion": "Bewegung reduzieren", "settings.appearance.reduceMotionDesc": "Animationen und Übergänge in der gesamten App minimieren.", + "settings.appearance.increaseContrast": "Kontrast erhöhen", + "settings.appearance.increaseContrastDesc": "Ränder, Trennlinien und sekundären Text für bessere Lesbarkeit verstärken.", "settings.appearance.pet": "Begleiter", "settings.appearance.petDesc": "Ein Begleiter, der auf die Aktivität des Agents reagiert.", "settings.appearance.petEnabled": "Begleiter anzeigen", diff --git a/packages/shared/src/i18n/locales/en.json b/packages/shared/src/i18n/locales/en.json index 6a06e456e..55b6adedb 100644 --- a/packages/shared/src/i18n/locales/en.json +++ b/packages/shared/src/i18n/locales/en.json @@ -787,6 +787,8 @@ "settings.appearance.richToolDescriptions": "Rich tool descriptions", "settings.appearance.reduceMotion": "Reduce motion", "settings.appearance.reduceMotionDesc": "Minimize animations and transitions throughout the app.", + "settings.appearance.increaseContrast": "Increase contrast", + "settings.appearance.increaseContrastDesc": "Strengthen borders, dividers, and secondary text for better readability.", "settings.appearance.pet": "Pet", "settings.appearance.petDesc": "A companion that reacts to what the agent is doing.", "settings.appearance.petEnabled": "Show pet companion", diff --git a/packages/shared/src/i18n/locales/es.json b/packages/shared/src/i18n/locales/es.json index 6461320a6..da6aa375b 100644 --- a/packages/shared/src/i18n/locales/es.json +++ b/packages/shared/src/i18n/locales/es.json @@ -787,6 +787,8 @@ "settings.appearance.richToolDescriptions": "Descripciones detalladas de herramientas", "settings.appearance.reduceMotion": "Reducir movimiento", "settings.appearance.reduceMotionDesc": "Minimiza las animaciones y transiciones en toda la aplicación.", + "settings.appearance.increaseContrast": "Aumentar contraste", + "settings.appearance.increaseContrastDesc": "Refuerza los bordes, separadores y el texto secundario para mejorar la legibilidad.", "settings.appearance.pet": "Mascota", "settings.appearance.petDesc": "Un compañero que reacciona a lo que hace el agente.", "settings.appearance.petEnabled": "Mostrar mascota", diff --git a/packages/shared/src/i18n/locales/hu.json b/packages/shared/src/i18n/locales/hu.json index 641c6d16f..735b11b7c 100644 --- a/packages/shared/src/i18n/locales/hu.json +++ b/packages/shared/src/i18n/locales/hu.json @@ -787,6 +787,8 @@ "settings.appearance.richToolDescriptions": "Részletes eszközleírások", "settings.appearance.reduceMotion": "Mozgás csökkentése", "settings.appearance.reduceMotionDesc": "Az animációk és átmenetek minimalizálása az egész alkalmazásban.", + "settings.appearance.increaseContrast": "Kontraszt növelése", + "settings.appearance.increaseContrastDesc": "A szegélyek, elválasztók és másodlagos szöveg erősítése a jobb olvashatóságért.", "settings.appearance.pet": "Kabala", "settings.appearance.petDesc": "Egy társ, aki reagál arra, amit az ügynök csinál.", "settings.appearance.petEnabled": "Kabala megjelenítése", diff --git a/packages/shared/src/i18n/locales/ja.json b/packages/shared/src/i18n/locales/ja.json index 5e2191990..b474a67c8 100644 --- a/packages/shared/src/i18n/locales/ja.json +++ b/packages/shared/src/i18n/locales/ja.json @@ -787,6 +787,8 @@ "settings.appearance.richToolDescriptions": "リッチなツール説明", "settings.appearance.reduceMotion": "モーションを減らす", "settings.appearance.reduceMotionDesc": "アプリ全体のアニメーションとトランジションを最小限にします。", + "settings.appearance.increaseContrast": "コントラストを上げる", + "settings.appearance.increaseContrastDesc": "境界線・区切り線・補助テキストを強調して読みやすくします。", "settings.appearance.pet": "ペット", "settings.appearance.petDesc": "エージェントの動きに反応するコンパニオン。", "settings.appearance.petEnabled": "ペットを表示", diff --git a/packages/shared/src/i18n/locales/pl.json b/packages/shared/src/i18n/locales/pl.json index f85aa9c9f..632706025 100644 --- a/packages/shared/src/i18n/locales/pl.json +++ b/packages/shared/src/i18n/locales/pl.json @@ -787,6 +787,8 @@ "settings.appearance.richToolDescriptions": "Rozbudowane opisy narzędzi", "settings.appearance.reduceMotion": "Ogranicz ruch", "settings.appearance.reduceMotionDesc": "Ogranicz animacje i przejścia w całej aplikacji.", + "settings.appearance.increaseContrast": "Zwiększ kontrast", + "settings.appearance.increaseContrastDesc": "Wzmocnij obramowania, separatory i tekst pomocniczy dla lepszej czytelności.", "settings.appearance.pet": "Maskotka", "settings.appearance.petDesc": "Towarzysz reagujący na to, co robi agent.", "settings.appearance.petEnabled": "Pokaż maskotkę", diff --git a/packages/shared/src/i18n/locales/zh-Hans.json b/packages/shared/src/i18n/locales/zh-Hans.json index 845231913..cdd9083aa 100644 --- a/packages/shared/src/i18n/locales/zh-Hans.json +++ b/packages/shared/src/i18n/locales/zh-Hans.json @@ -787,6 +787,8 @@ "settings.appearance.richToolDescriptions": "丰富的工具描述", "settings.appearance.reduceMotion": "减少动态效果", "settings.appearance.reduceMotionDesc": "在整个应用中尽量减少动画和过渡效果。", + "settings.appearance.increaseContrast": "提高对比度", + "settings.appearance.increaseContrastDesc": "增强边框、分隔线和次要文字,提升可读性。", "settings.appearance.pet": "宠物", "settings.appearance.petDesc": "一个会根据 agent 当前状态做出反应的小伙伴。", "settings.appearance.petEnabled": "显示宠物伙伴", From 13e767ef14275f6c2c5f2b8210a33adc0cf1fb29 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 13:43:39 +0000 Subject: [PATCH 2/2] docs(loop): mark high-contrast pr-open (#67) --- docs/loop/feature-ledger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/loop/feature-ledger.md b/docs/loop/feature-ledger.md index 5636dbb13..339ab607f 100644 --- a/docs/loop/feature-ledger.md +++ b/docs/loop/feature-ledger.md @@ -32,7 +32,7 @@ log, not the system of record. | slug | title | source | feasibility | status | issue | pr | branch | updated | notes | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| high-contrast | "Increase contrast" accessibility setting in Appearance | macOS Accessibility "Increase contrast" / Windows contrast themes / VS Code High Contrast | frontend-only | in-progress | [#66](https://github.com/modelstudioai/openwork/issues/66) | — | loop/high-contrast | 2026-07-07 | Companion to merged reduce-motion. Renderer-only pref (`craft-high-contrast` localStorage) → `HighContrastProvider` context → `data-high-contrast` on `` → CSS overrides of theme tokens (`--border`/`--input`/`--muted-foreground`/`--ring`) gated on `:root[data-high-contrast='true']` (higher specificity beats theme `:root` injection). Toggle in Appearance→Interface below Reduce motion; 2 i18n keys ×7 locales. | +| high-contrast | "Increase contrast" accessibility setting in Appearance | macOS Accessibility "Increase contrast" / Windows contrast themes / VS Code High Contrast | frontend-only | pr-open | [#66](https://github.com/modelstudioai/openwork/issues/66) | [#67](https://github.com/modelstudioai/openwork/pull/67) | loop/high-contrast | 2026-07-07 | Companion to merged reduce-motion. Renderer-only pref (`craft-high-contrast` localStorage) → `HighContrastProvider` context → `data-high-contrast` on `` → CSS overrides of theme tokens (`--border`/`--input`/`--muted-foreground`/`--ring`) gated on `:root[data-high-contrast='true']` (higher specificity beats theme `:root` injection; works light+dark since tokens derive from `--foreground`). Toggle in Appearance→Interface below Reduce motion; 2 i18n keys ×7 locales. typecheck:all/`bun test` zero-delta vs main (11 electron / 56 test pre-existing failures byte-identical); i18n parity OK; renderer build ✅ (verified rule in bundled CSS). CDP assertion written (`high-contrast.assert.ts`); **not run locally** — egress 403 blocks Electron binary download (same env block as #51). | | reduce-motion | "Reduce motion" accessibility setting in Appearance | Claude desktop / macOS / Windows reduce-motion + `prefers-reduced-motion` | frontend-only | merged | [#50](https://github.com/modelstudioai/openwork/issues/50) | [#51](https://github.com/modelstudioai/openwork/pull/51) | loop/reduce-motion | 2026-07-07 | **Merged** into `main` (2026-07-06). Renderer-only pref (localStorage) applied app-wide via `` + `data-reduce-motion` on `` + global CSS guard. Off ⇒ `reducedMotion="user"` (still honors OS). | | composer-expand | Expand / collapse (maximize) toggle for the chat composer | Claude/ChatGPT/Codex desktop composer maximize | frontend-only | pr-open | [#48](https://github.com/modelstudioai/openwork/issues/48) | [#49](https://github.com/modelstudioai/openwork/pull/49) | loop/composer-expand | 2026-07-03 | Opened by a prior run. Adds `isComposerExpanded` toggle in `FreeFormInput`; 2 new i18n keys. Awaiting review. | | scroll-to-bottom | "Jump to latest" (scroll-to-bottom) button in the chat transcript | Claude Code / ChatGPT / Codex desktop | frontend-only | pr-open | [#46](https://github.com/modelstudioai/openwork/issues/46) | [#47](https://github.com/modelstudioai/openwork/pull/47) | loop/scroll-to-bottom | 2026-07-02 | Opened by a prior run. Floating jump button in `ChatDisplay` + `seed()` harness hook. Awaiting review. |