Two new built-in themes tuned for color vision deficiency:
- Colorblind: for red-green CVD (protanopia and deuteranopia). Diff
additions are blue (#4f83d1 dark / #216cab light) and deletions are
orange (#c96e12 dark / #a55c1e light), a pair on the blue-yellow axis
that survives red-green dichromacy.
- Colorblind Tritanopia: for blue-yellow CVD. Additions are teal
(#2b939e dark / #1b7981 light) and deletions are vermilion (#db5f38
dark / #c14f2b light), a pair on the surviving red-green axis.
Both pairs were verified computationally with Machado et al. (2009)
severity-1.0 CVD simulation matrices: the simulated add/del line
backgrounds keep CIEDE2000 >= 8.9 (red-green theme) and >= 18.6
(tritanopia theme) at every diffLineBgIntensity in both dark and light
modes, and normal-vision foreground contrast stays >= 4.7:1 over the
strongest tint. The +/- gutter signs and indicator bars reuse the same
base colors and keep >= 4.2:1 contrast on the page background.
The review editor's Pierre diff pipeline picks up the pair via the
library's own override seam: the theme files set
--diffs-addition-color-override / --diffs-deletion-color-override,
which inherit into the diff shadow DOM, so no app code changes were
needed for the line backgrounds. Syntax colors in diffs map to the
@pierre/theme colorblind shiki themes (already in the dependency tree),
and plan-editor highlight.js code blocks get theme-scoped token
overrides mirroring the same palettes.
A new built-in app theme designed for red-green color vision deficiency, plus the findings from an attempted tritanopia companion theme that was investigated, measured, and deliberately dropped (details below). The Colorblind theme is a complete bundle in the existing theme system: UI chrome tokens, diff line backgrounds, gutter indicators, review-editor syntax colors, and plan-editor code-block colors, with full dark and light support.
Note: this revision corrects the baseline numbers from the original PR description. The original baseline was computed on Pierre's stock CSS constants; the app actually ships the pierre-dark/pierre-light gitDecoration pair (add
light-dark(#18a46c, #07c480), dellight-dark(#d52c36, #ff2e3f)) via the attached syntax theme. All baseline numbers below are computed on MEASURED rendered backgrounds of the shipped default theme, and every simulation is reported under two independent models: Machado et al. (2009) severity-1.0 and Vienot 1999.The theme
Colorblind (id
colorblind) targets red-green CVD (protanopia and deuteranopia, the common ~95% of cases). Diff additions are blue and deletions are orange: the pair differs along the blue-yellow axis that survives red-green dichromacy. Base pair: add#4f83d1/ del#c96e12in dark, add#216cab/ del#a55c1ein light.Why the shipped default needed this
Measured rendered line backgrounds of the shipped default (plannotator theme with the pierre pair), CVD-simulated, CIEDE2000 between the simulated add/del pair:
At the default subtle intensity the real separation for a protanope in light mode is 1.7 (deut 3.5), which is effectively indistinguishable. The deuteranopia range across configs is 3.5 to 12.8 (Machado). This is bad enough to justify the theme but not the near-zero numbers originally claimed; the correction does not change the motivation, only its magnitude.
Colorblind theme numbers (measured rendered backgrounds)
Same methodology, matched pair for pair against the table above. The Colorblind theme beats the shipped default in all 6 mode/intensity configurations under both models for both protanopia and deuteranopia:
The worst config (light subtle, prot) goes from 1.7 to 9.0. Normal-vision foreground contrast stays at or above 4.70:1 over the strongest tint (the shipped default drops to 2.96:1 there). The base colors also paint the +/- gutter signs, indicator bars, and per-file counts, and keep 4.2:1 to 5.3:1 contrast on the page background; measured via CDP, classic +/- signs render exactly the base pair (
rgb(79,131,209)/rgb(201,110,18)in dark) and the bars indicator renders the addition bar solid and the deletion bar as Pierre's dashed gradient from the same deletion base, so change direction is also encoded by shape.Measurement methodology: headless Chrome driven over CDP against a live review server on a real git diff; computed
background-colorread from rest-state addition/deletion lines inside Pierre's shadow DOM at each of the threediffLineBgIntensitylevels in both modes, for both the shipped default theme and this theme; CVD simulation applied to the measured values.The tritanopia theme was investigated and dropped
The original revision of this branch included a Colorblind Tritanopia theme with a teal/vermilion diff pair. Adversarial review found it backwards-effective, and re-measurement confirms: tritanopia destroys the blue-yellow axis and PRESERVES red-green, so the shipped default's green/red pair is already close to optimal for tritanopes. On measured rendered backgrounds the default scores tritan dE00 35.9 to 62.8 (Machado) and 40.1 to 70.0 (Vienot) across the 6 configs; the teal/vermilion pair loses to that in most configs because teal carries too much blue.
A matched-pair grid search (1,890 dark and 1,024 light candidates over green/yellow-green additions vs red/vermilion deletions, in oklch space) was run to find a replacement pair that beats the default in all 6 configs under both models while keeping 4.5:1 text contrast over the strongest tint. Result: zero feasible pairs. The best candidates converge on the default's own vivid green/red and still fall 2.8 to 6.1 dE00 short in the worst configuration, because beating the default requires brighter, more saturated colors than the text-contrast constraint allows (the default itself only reaches its numbers by dropping strong-intensity text contrast to 2.96:1).
Given that a theme named for tritanopia could at best tie the stock palette while implying it does better, it was removed rather than renamed: a renamed generic palette would add nothing over the Colorblind theme. Two useful findings stand: tritanopes are already well served by the default diff colors, and the Colorblind theme itself also holds up under tritanopia simulation (Vienot dE00 33.9 to 69.6 across all configs), so recommending it as the single CVD theme does not harm tritanope users.
How the diff colors flow
No app code was needed for the line backgrounds.
@pierre/diffsderives everything (line bg mixes, emphasis chips, signs, bars, counts) from--diffs-addition-base/--diffs-deletion-base, whose fallback chains start at--diffs-addition-color-override/--diffs-deletion-color-override. Custom properties inherit into the diff shadow DOM from the document root, so the theme CSS file simply sets those override tokens per mode. Existing themes define neither token, so nothing changes for them, andbuildLineBgOverrides's intensity machinery composes on top unchanged. Plan-diff views and the file tree's +N/-N follow--success/--destructive, which map to the same pair.How syntax mapping works
SHIKI_THEME_MAPinpackages/review-editor/hooks/usePierreTheme.tsmapscolorblindto thepierre-dark-protanopia-deuteranopia/pierre-light-protanopia-deuteranopiapair. These themes ship in@pierre/theme2.0.0..theme-colorblind .hljs-*token rules (dark and light) mirroring the corresponding Pierre shiki palettes, so plan code blocks avoid red-vs-green token pairs. Verified live: computed token colors in the annotate editor match the palette in both modes.Bundle impact
The Pierre colorblind shiki themes were already in the bundle:
@pierre/theming's collection statically registers dynamic imports for all ten pierre themes, and the single-file builds inline them (the baseline bundle already containspierre-dark-softand friends). Measured delta, built at origin/main vs this branch head:apps/reviewdist/index.html: 18,132.08 kB to 18,136.79 kB (+4.71 kB raw, +0.94 kB gzip)apps/hookdist/index.html: 23,026.88 kB to 23,031.48 kB (+4.60 kB raw, +0.85 kB gzip)That delta is the theme CSS file and registry entry.
Verification
bun run typecheckbun test(full suite)DOM_TESTS=1file-browser isolated test (per test.yml)DOM_TESTS=1UI seam-contract + DOM tests (per test.yml)Remaining human QA