Spec: docs/superpowers/specs/2026-08-23-web-ui-rewrite-design.md § Accessibility.
Independent of the rest of the web UI work. This improves the UI that ships today, needs none of the rewrite, and should not wait behind seven other issues.
1. --good and --bad are photometrically identical
keel/web/render.py:41-49, light mode:
good #1f5f4f relative luminance 0.0904
bad #96322a relative luminance 0.0893
delta 0.0011 → 1.01:1
Profit and loss are separated by hue alone. render.py:82 confirms nothing else carries the distinction:
.good { color: var(--good); } .warn { color: var(--warn); } .bad { color: var(--bad); }
This fails WCAG 1.4.1 Use of Color in an application whose central signal is gain versus loss. Roughly one in twelve men has red-green colour deficiency and cannot reliably tell them apart; greyscale, e-ink and direct sunlight collapse the distinction for everyone. Dark mode is better but thin at 1.34:1.
Fix: never encode P&L in colour alone. A glyph and sign carry the meaning — ▲ +2.4% / ▼ −2.4% — and colour reinforces it. Separate the two luminances as well.
Also split --accent from --good. They are byte-identical in both themes today (#1f5f4f light, #6fbf9f dark), so a link and a gain render alike.
2. Form inputs have no visible boundary
render.py:96-97:
.field input, .field select { ... border: 1px solid var(--line); background: var(--bg); ... }
The input's background is the page background, so a --line border at 1.27:1 is the only thing marking where the control is. Fails WCAG 1.4.11 Non-text Contrast (3:1 for UI component boundaries).
Decorative dividers at 1.27:1 are exempt and stay — table rules, the footer line, card edges. Form controls are not, and this lands squarely on the first-run wizard (#437), which is almost entirely forms, for an audience that has never seen the app before.
3. Assert contrast in CI
The WCAG contrast formula is about twenty lines of Python with no dependencies. Every foreground/background pair asserts its minimum ratio, and good/bad assert a minimum luminance separation, so a palette regression fails the build instead of being noticed a year later.
No contrast tooling, no dependency — just the arithmetic in a readable test.
Acceptance
Spec:
docs/superpowers/specs/2026-08-23-web-ui-rewrite-design.md§ Accessibility.Independent of the rest of the web UI work. This improves the UI that ships today, needs none of the rewrite, and should not wait behind seven other issues.
1.
--goodand--badare photometrically identicalkeel/web/render.py:41-49, light mode:Profit and loss are separated by hue alone.
render.py:82confirms nothing else carries the distinction:This fails WCAG 1.4.1 Use of Color in an application whose central signal is gain versus loss. Roughly one in twelve men has red-green colour deficiency and cannot reliably tell them apart; greyscale, e-ink and direct sunlight collapse the distinction for everyone. Dark mode is better but thin at 1.34:1.
Fix: never encode P&L in colour alone. A glyph and sign carry the meaning —
▲ +2.4%/▼ −2.4%— and colour reinforces it. Separate the two luminances as well.Also split
--accentfrom--good. They are byte-identical in both themes today (#1f5f4flight,#6fbf9fdark), so a link and a gain render alike.2. Form inputs have no visible boundary
render.py:96-97:The input's background is the page background, so a
--lineborder at 1.27:1 is the only thing marking where the control is. Fails WCAG 1.4.11 Non-text Contrast (3:1 for UI component boundaries).Decorative dividers at 1.27:1 are exempt and stay — table rules, the footer line, card edges. Form controls are not, and this lands squarely on the first-run wizard (#437), which is almost entirely forms, for an audience that has never seen the app before.
3. Assert contrast in CI
The WCAG contrast formula is about twenty lines of Python with no dependencies. Every foreground/background pair asserts its minimum ratio, and
good/badassert a minimum luminance separation, so a palette regression fails the build instead of being noticed a year later.No contrast tooling, no dependency — just the arithmetic in a readable test.
Acceptance
goodandbaddiffer in luminance by a margin the test pins, not only in hue.--accentand--goodare distinct values.