Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ad24c0b
fix(tui): reassign Dune theme to dark Claude Code colorblind palette
euxaristia Jul 17, 2026
0591600
ci: re-trigger checks after Windows prewarm flake
euxaristia Jul 20, 2026
f3788a1
fix(daemon): wait for tracked worker before draining in pool test
euxaristia Jul 20, 2026
c8ee46c
fix(tui): distinguish Dune diff colors on 256-color terminals
euxaristia Jul 22, 2026
a193a72
fix(tui): fix Dune add-diff contrast on 256-color terminals
euxaristia Jul 22, 2026
f9e41b2
fix(tui): increase Dune selBg ANSI-256 contrast and revert pool_test …
euxaristia Jul 23, 2026
a57cf8a
fix(tui): remove unrelated daemon pool test change to focus PR scope
euxaristia Jul 23, 2026
06d37d4
fix(tui): restore light Dune diff contrast and add Dune Dark theme
euxaristia Jul 31, 2026
d17e031
fix(tui): make Dune Dark diffs readable on 16-color terminals
euxaristia Aug 1, 2026
b27b21e
fix(tui): meet AA for Dune Dark ANSI-16 add gutters
euxaristia Aug 1, 2026
c80e8b9
fix(tui): gofmt Dune Dark and keep selBg distinct under ANSI-16
euxaristia Aug 1, 2026
c961ada
fix(tui): audit Dune Dark status borders across every color profile
euxaristia Aug 5, 2026
f7fd778
docs(tui): narrow Dune Dark accessibility claims
euxaristia Aug 7, 2026
1fc71c2
Fix ANSI-256 theme test conversion
euxaristia Aug 11, 2026
92b4538
fix(tui): keep Dune accent AA after ANSI-256 quantization
euxaristia Aug 11, 2026
fa60d77
Clarify Dune theme docs and drop a stale onSel comment.
euxaristia Aug 16, 2026
2061b69
Fail fast when theme tests look up missing registry names.
euxaristia Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,12 @@ tagged. Until then, source builds report the version `dev`.
GitHub issue/PR templates.
- Interactive `/theme` picker: bare `/theme` opens a popup that live-previews each palette as you move
and applies on select (Esc reverts).
- Twelve built-in color themes alongside the `dark`/`light` built-ins — `dracula`, `nord`, `gruvbox`,
- Thirteen built-in color themes alongside the `dark`/`light` built-ins — `dracula`, `nord`, `gruvbox`,
`tokyo-night`, `catppuccin`, `one-dark`, `solarized-dark`, `rose-pine`, `everforest`,
`solarized-light`, `dune`, and `neon` — selectable via `/theme <name>`, `--theme <name>`, or
`solarized-light`, `dune`, `dune-dark`, and `neon` — selectable via `/theme <name>`, `--theme <name>`, or
`ZERO_THEME`. Every palette is contrast-audited to WCAG AA, and the new presets are additionally
audited after xterm-256 downsampling; see [docs/THEMES.md](docs/THEMES.md). The built-in light
theme was reworked for legibility.
audited after xterm-256 downsampling (Dune Dark also after 16-color ANSI conversion); see
[docs/THEMES.md](docs/THEMES.md). The built-in light theme was reworked for legibility.
- `--theme <name>` flag for the TUI, accepting `auto` or any registered theme (previously only the
`ZERO_THEME` env var existed).
- "Accessibility / Appearance" section in the README documenting `NO_COLOR`, `ZERO_THEME`, `/theme`,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ manifest.
| Control | Effect |
|---|---|
| `NO_COLOR=<anything>` | disables color output |
| `ZERO_THEME=<name>` | selects the startup theme (`auto`, `dark`, `light`, or a color theme like `dracula`, `nord`, `gruvbox`, `tokyo-night`, `catppuccin`, `one-dark`, `solarized-dark`, `rose-pine`, `everforest`, `neon`, `solarized-light`, `dune`) |
| `ZERO_THEME=<name>` | selects the startup theme (`auto`, `dark`, `light`, or a color theme like `dracula`, `nord`, `gruvbox`, `tokyo-night`, `catppuccin`, `one-dark`, `solarized-dark`, `rose-pine`, `everforest`, `neon`, `dune-dark`, `solarized-light`, `dune`) |
| `--theme <name>` | selects the TUI theme from the CLI (same names) |
| `/theme` | opens the theme picker inside the TUI (live preview; `/theme <name>` switches directly) |
| `ZERO_NO_FADE=1` | disables streaming fade animation |
Expand Down
2 changes: 1 addition & 1 deletion README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ zero update 检查更新版本
| 控制 | 效果 |
|---|---|
| `NO_COLOR=<任意值>` | 禁用颜色输出 |
| `ZERO_THEME=<名称>` | 选择启动主题(`auto`、`dark`、`light`,或颜色主题如 `dracula`、`nord`、`gruvbox`、`tokyo-night`、`catppuccin`、`one-dark`、`solarized-dark`、`rose-pine`、`everforest`、`neon`、`solarized-light`、`dune`) |
| `ZERO_THEME=<名称>` | 选择启动主题(`auto`、`dark`、`light`,或颜色主题如 `dracula`、`nord`、`gruvbox`、`tokyo-night`、`catppuccin`、`one-dark`、`solarized-dark`、`rose-pine`、`everforest`、`neon`、`dune-dark`、`solarized-light`、`dune`) |
| `--theme <名称>` | 从 CLI 选择 TUI 主题(相同名称) |
| `/theme` | 在 TUI 中打开主题选择器(实时预览;`/theme <名称>` 直接切换) |
| `ZERO_NO_FADE=1` | 禁用流式淡入动画 |
Expand Down
30 changes: 19 additions & 11 deletions docs/THEMES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ the active theme and the registered names without opening the picker.

## Dune (`dune`)

A warm sand-and-cream palette: sand/cream surface, charcoal ink, and a soft
amber accent.
A warm sand-and-cream palette with charcoal ink, soft amber accent, and
quantization-safe diff bands.

## Dune Dark (`dune-dark`)

A dark, colorblind-friendly palette inspired by Claude Code's daltonized dark palette:
near-black surface, white ink, and a deuteranopia-adjusted brand-orange
accent.

## Neon (`neon`)

Expand All @@ -28,12 +34,14 @@ adding a new `palette{...}` literal, a `themeRegistry` entry, and test
coverage for the new palette (see below).

Registry-wide tests in `internal/tui/theme_select_test.go` assert the basic
WCAG AA text tokens, the gray-ramp order, the diff word-span pairs, and the
selected-row band for every entry. The rendered-surface invariants beyond
those (permission surfaces, selected-row secondary text, diff gutters, and
the xterm-256 downsampling checks) are asserted per palette, not against the
whole registry: `TestExtendedThemeContrastInvariants` and
`TestExtendedThemeANSI256Contrast` enumerate the palettes they cover. A new
theme must be added to those tests (or given equivalent palette-specific
assertions), or CI can stay green while its permission, selected-row, and
diff surfaces ship unreadable.
WCAG AA text tokens, the gray-ramp order, the selected-row band, and (via
`TestDiffHighlightWordSpans`) the word-span *contrast* pairs for every
entry. The remaining rendered-surface invariants (permission surfaces,
selected-row secondary text, xterm-256 quantization, and the quantized
add/del band *identity* check that `addBg`/`delBg` stay green/red and
distinct) are asserted per palette, not against the whole registry:
`TestExtendedThemeContrastInvariants` and `TestExtendedThemeANSI256Contrast`
enumerate the palettes they cover. A new theme must be added to those
tests (or given equivalent palette-specific assertions), or CI can stay
green while its permission, selected-row, and quantized-diff surfaces
ship unreadable.
76 changes: 69 additions & 7 deletions internal/tui/theme_palettes.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,17 +444,17 @@ var dunePalette = palette{
muted: "#473e32",
faint: "#554a3a",
faintest: "#655648",
accent: "#724028", // darkened from #8f5215 for AA on selBg (5.46:1) that also survives ANSI-256 downsampling (quantizes to #444444, 6.47:1 on quantized selBg; the previous #7c4712 quantized to #875f00 at 3.81:1)
accent: "#5f4b30", // darkened from #8f5215 for AA on selBg (5.46:1) that also survives ANSI-256 downsampling (quantizes to #444444, 6.47:1 on quantized selBg)
green: "#38572a",
red: "#872d24", // darkened from #963328 so delBg contrast survives ANSI-256 downsampling (true 6.57:1, 256 7.86:1)
red: "#872d24", // darkened from #963328 so delBg contrast survives ANSI-256 downsampling
amber: "#6d4600",
blue: "#2f5680", // darkened from #3d6a9e for AA on selBg (was 3.61:1, now 4.90:1)
blue: "#2f5680",
gitAdd: "#38572a",
gitDel: "#963328",
addBg: "#dcecd0",
delBg: "#f5dbd5",
addBgWord: "#b9dc9e",
delBgWord: "#eebba9",
addBg: "#d0f0c0", // quantizes to xterm green #d7ffd7, keeping add/del rows distinct on 256-color terminals
delBg: "#fcd8d4", // quantizes to xterm red #ffd7d7
addBgWord: "#a5e090",
delBgWord: "#f8b8af",
permBg: "#f0dfae",
selBg: "#e0cf98",
addInk: "#264018",
Expand All @@ -465,6 +465,67 @@ var dunePalette = palette{
cardPerm: "#c2a04a",
}

// duneDarkPalette is a dark theme inspired by Claude Code's colorblind-friendly
// (daltonized) dark palette. The base surface is near-black (Claude Code's true
// dark canvas), with white ink and the brand-orange accent adjusted for
// deuteranopia. Diff signals reuse the proven dark-theme structure so every
// WCAG-AA invariant still holds; the accent and status colors (cool success,
// warm error, amber warning) follow a daltonized palette so additions stay
// distinguishable under color blindness.
//
// 16-color terminals (TERM=xterm) force every token through ansi.Convert16.
// Saturated cyan/blue success and bright red error both collapse onto ANSI
// pairs that fail AA on the green/maroon diff bands (bright-blue-on-green
// 1.67:1, bright-red-on-maroon 2.74:1). Soft periwinkle success and soft pink
// error map to ANSI white, which clears AA on those bands while keeping a
// cool/warm tint in truecolor and 256-color.
var duneDarkPalette = palette{
panel: "#0e0e10", // Claude Code dark canvas — near-black
promptBg: "#262626", // submitted user-prompt bubble
line: "#242429", // borders/separators
line2: "#414147",
ink: "#ececee",
// Gray ramp (ink > muted > faint > faintest > panel) is kept light enough
// that faintest maps to ANSI white / xterm #c6c6c6 and clears WCAG AA on the
// green add gutter under both 16-color and 256-color profiles. Prior cyan
// #55c6cd mapped to bright cyan at only 4.10:1 on ANSI green (short of AA).
muted: "#e0e0e6", // secondary text
faint: "#d4d4dc", // hints/metadata
faintest: "#c8c8d0", // line numbers/separators (and diff gutters)
// Warm sand-gold so TERM=xterm maps to ANSI yellow (brand #ff9628 maps to
// red at only 4.00:1 on the navy selection band under 16-color).
accent: "#fff080",
green: "#c8c8e9", // success — soft periwinkle (cool/daltonized). Maps to ANSI white so add-sign text stays AA (5.14:1) on the green add band under 16-color; saturated cyan/blue collapses to bright blue at 1.67:1
red: "#ffc0c8", // error — soft pink. Maps to ANSI white so del-sign text stays AA (10.95:1) on the maroon del band under 16-color; bright red collapses to 2.74:1 on maroon
amber: "#ffcc00", // warning rgb(255,204,0)
blue: "#cceeff", // permission — light sky. Maps to bright cyan so selected-row local-model dots stay AA on black selBg under 16-color (prior #99ccff -> bright blue at 2.44:1)
gitAdd: "#c8c8e9", // matches green (daltonized cool success)
gitDel: "#ffc0c8", // matches red (daltonized warm error)
addBg: "#003500", // diff added band — quantizes to xterm green #005f00 instead of the same gray as delBg, keeping add/del rows distinct on 256-color terminals (previous #0a1f14/#240a0e both collapsed to #121212); 16-color maps to ANSI green
delBg: "#350000", // diff removed band — quantizes to xterm red #5f0000 (see addBg); 16-color maps to ANSI maroon
addBgWord: "#007400", // word-level added span — quantizes to xterm green #008700, distinct from both addBg's #005f00 and delBgWord's red
delBgWord: "#740000", // word-level removed span — quantizes to xterm red #870000 (see addBgWord)
permBg: "#1c1915",
// Near-black grays collapse to ANSI black with panel under TERM=xterm,
// erasing the full-row highlight. Deep indigo maps to navy (#000080) in
// 16-color and #000087 in 256-color, staying distinct from panel.
selBg: "#25257a",
addInk: "#f0f5d2", // changed-word text — lightened so it still clears AA (4.60:1) against addBgWord's xterm-256 quantized #008700 (the original #bdeed7 fell to 4.06:1); 16-color maps to bright yellow (4.78:1 on ANSI green)
delInk: "#fff0f0", // changed-word text — near-white pink maps to ANSI white so word spans stay AA on maroon under 16-color (prior #f2c4c4 -> bright red at 2.74:1)
onAccent: "#000000",
// Status-card borders are non-text UI (WCAG 1.4.11: >=3:1 against panel).
// Prior cardRun #3399ff mapped to ANSI bright blue at only 2.44:1 on the
// black panel under 16-color. #cceeff (same as blue) maps to bright cyan
// and clears 3:1 in truecolor, xterm-256, and ANSI-16.
// cardPerm must stay distinguishable from cardErr after ANSI conversion:
// #ffcc00 collapsed to the same ANSI red as #ff6666; pure yellow maps to
// ANSI yellow so running (cyan) / error (red) / permission (yellow) stay
// three distinct roles under 16-color and 256-color.
cardRun: "#cceeff", // running tool border: light sky / ANSI bright cyan
cardErr: "#ff6666", // failed tool border: warm red / ANSI red
cardPerm: "#fff200", // permission border: pure yellow / ANSI yellow (was #ffcc00 -> ANSI red)
}

// themeEntry is one registered theme: Name is the /theme value + ZERO_THEME/--theme
// token (lowercase, kebab), Label is the picker display text, and IsDark groups the
// picker (Dark/Light sections) and drives which built-in `auto` resolves to.
Expand All @@ -491,6 +552,7 @@ var themeRegistry = []themeEntry{
{Name: "rose-pine", Label: "Rosé Pine", Palette: rosePinePalette, IsDark: true},
{Name: "everforest", Label: "Everforest", Palette: everforestPalette, IsDark: true},
{Name: "neon", Label: "Neon", Palette: neonPalette, IsDark: true},
{Name: "dune-dark", Label: "Dune Dark", Palette: duneDarkPalette, IsDark: true},
{Name: "light", Label: "light", Palette: lightPalette, IsDark: false},
{Name: "solarized-light", Label: "Solarized Light", Palette: solarizedLightPalette, IsDark: false},
{Name: "dune", Label: "Dune", Palette: dunePalette, IsDark: false},
Expand Down
Loading
Loading