Problem or Motivation
OpenWork's Settings → Appearance → Interface section already offers one
accessibility affordance — the Reduce motion toggle (shipped in #51). But it
has no way to increase visual contrast. Today the app's borders, dividers,
input outlines and secondary/"muted" text are intentionally low-contrast
(derived from --foreground at very low alpha, e.g. --border is
oklch(from var(--foreground) l c h / 0.05) and --muted-foreground is only a
50% mix in apps/electron/src/renderer/index.css). That looks clean but is hard
to read for low-vision users and in bright environments.
Increasing contrast is a standard desktop-app accessibility control that
OpenWork's peers expose and OpenWork lacks:
- macOS — System Settings → Accessibility → Display → Increase contrast.
- Windows — Contrast themes.
- VS Code (which Claude Code / Codex desktop build on) — dedicated
High Contrast themes.
- Claude Desktop / ChatGPT desktop — standing accessibility asks for a
higher-contrast reading mode.
Reduce motion is already here; Increase contrast is its natural companion in
the same Interface section.
Proposed Solution
Add an "Increase contrast" toggle to Settings → Appearance → Interface,
directly below the existing Reduce motion toggle. It is a renderer-only,
frontend preference — no backend involvement:
- Persist it in
localStorage (new key craft-high-contrast), mirroring
Reduce motion's craft-reduce-motion and the other lightweight prefs in
apps/electron/src/renderer/lib/local-storage.ts.
- Provide it app-wide via a small
HighContrastProvider context (mirroring
ReduceMotionContext), wired in main.tsx.
- When enabled, set
data-high-contrast="true" on <html>. A global CSS block
in index.css gated on :root[data-high-contrast='true'] then raises the
contrast of theme tokens — strengthening --border, --input,
--muted-foreground, --foreground-dimmed and the focus --ring, and adding
a visible focus outline. Because every theme derives these tokens from
--foreground (which flips per light/dark), the alpha-based overrides work in
both light and dark and across preset themes.
- Add two i18n keys (
settings.appearance.increaseContrast /
...Desc) across all 7 locales, matching how reduceMotion is localized.
Off by default, so existing users see no change.
Feasibility
Frontend-only — confirmed feasible. This is the exact mechanism the merged
Reduce motion feature uses (localStorage pref → React context → <html> data
attribute → CSS guard). No qwen-code backend change is required. Theme variables
are injected as a :root { … } stylesheet rule
(ThemeContext.tsx), so a higher-specificity :root[data-high-contrast='true']
selector overrides them cleanly without !important.
Acceptance Criteria (what the CDP assertion will check)
Driven over CDP in the draft/no-session state (no backend), mirroring the
reduce-motion assertion:
- Open Settings → Appearance; the Increase contrast toggle renders and
is off initially, with no data-high-contrast attribute on <html>.
- Enabling it: the switch reads
aria-checked="true", <html> gains
data-high-contrast="true", localStorage['craft-high-contrast'] === "true",
and the high-contrast CSS actually applies (a dedicated
--hc-enabled custom property computes to 1 on :root).
- Disabling it reverts all four: switch off, attribute removed,
localStorage "false", and --hc-enabled no longer 1 — proving it both
applies and reverts, not merely renders.
Problem or Motivation
OpenWork's Settings → Appearance → Interface section already offers one
accessibility affordance — the Reduce motion toggle (shipped in #51). But it
has no way to increase visual contrast. Today the app's borders, dividers,
input outlines and secondary/"muted" text are intentionally low-contrast
(derived from
--foregroundat very low alpha, e.g.--borderisoklch(from var(--foreground) l c h / 0.05)and--muted-foregroundis only a50% mix in
apps/electron/src/renderer/index.css). That looks clean but is hardto read for low-vision users and in bright environments.
Increasing contrast is a standard desktop-app accessibility control that
OpenWork's peers expose and OpenWork lacks:
High Contrast themes.
higher-contrast reading mode.
Reduce motion is already here; Increase contrast is its natural companion in
the same Interface section.
Proposed Solution
Add an "Increase contrast" toggle to Settings → Appearance → Interface,
directly below the existing Reduce motion toggle. It is a renderer-only,
frontend preference — no backend involvement:
localStorage(new keycraft-high-contrast), mirroringReduce motion'scraft-reduce-motionand the other lightweight prefs inapps/electron/src/renderer/lib/local-storage.ts.HighContrastProvidercontext (mirroringReduceMotionContext), wired inmain.tsx.data-high-contrast="true"on<html>. A global CSS blockin
index.cssgated on:root[data-high-contrast='true']then raises thecontrast of theme tokens — strengthening
--border,--input,--muted-foreground,--foreground-dimmedand the focus--ring, and addinga visible focus outline. Because every theme derives these tokens from
--foreground(which flips per light/dark), the alpha-based overrides work inboth light and dark and across preset themes.
settings.appearance.increaseContrast/...Desc) across all 7 locales, matching howreduceMotionis localized.Off by default, so existing users see no change.
Feasibility
Frontend-only — confirmed feasible. This is the exact mechanism the merged
Reduce motionfeature uses (localStorage pref → React context →<html>dataattribute → CSS guard). No qwen-code backend change is required. Theme variables
are injected as a
:root { … }stylesheet rule(
ThemeContext.tsx), so a higher-specificity:root[data-high-contrast='true']selector overrides them cleanly without
!important.Acceptance Criteria (what the CDP assertion will check)
Driven over CDP in the draft/no-session state (no backend), mirroring the
reduce-motionassertion:is off initially, with no
data-high-contrastattribute on<html>.aria-checked="true",<html>gainsdata-high-contrast="true",localStorage['craft-high-contrast'] === "true",and the high-contrast CSS actually applies (a dedicated
--hc-enabledcustom property computes to1on:root).localStorage"false", and--hc-enabledno longer1— proving it bothapplies and reverts, not merely renders.