Decisions
ADR-009: Wallpaper-Driven Theming Engine
Status
Accepted
Date
2026-05-12
Context
Most dotfiles distributions ship a small fixed palette (Solarized, Gruvbox, Tokyo Night) chosen once and recycled across every terminal, editor, and status bar. When the user changes wallpapers — a daily occurrence on macOS with HEIC dark/light variants and on Linux with swww/hyprpaper — terminal chrome stays static, creating visual incoherence between the chosen wallpaper and the surrounding tooling.
Manual palette switching solves coherence but adds friction: it requires the user to (a) generate a palette, (b) regenerate config for every tool (alacritty.toml, kitty.conf, wezterm.lua, neovim themes, …), and (c) reload each tool. Tools like pywal/wal automate (a) and (b), but pywal palettes routinely fail WCAG contrast thresholds (especially for bright wallpapers) and lack support for many of the format files this project ships.
Problem: Reuse the user's chosen wallpaper as the single source of truth for terminal + status-bar colors, with accessibility guaranteed and no manual regeneration step.
Constraints:
- Output must satisfy WCAG 2.2 AAA contrast (≥ 7:1 for normal text, ≥ 4.5:1 for large text) — non-negotiable, the dotfiles ship as workstation infrastructure.
- Palette extraction must work offline on both macOS (HEIC, dynamic wallpapers with embedded dark/light variants) and Linux (PNG/JPEG via
swww,hyprpaper,feh, GNOME). - Total time from "user changes wallpaper" → "all terminals retinted" must be ≤ 3 seconds (rebuild trigger + write of templated configs).
- Targets include Warp, iTerm2, Alacritty, Ghostty, Kitty, Wezterm, Tmux, Neovim (multiple themes), VS Code, Firefox theme JSON, Niri borders, Waybar, GTK/Qt (matugen pipeline).
Decision
Build a self-contained theming engine — dot theme rebuild — implemented in scripts/theme/ with the following pipeline:
-
Source detection — locate the active wallpaper across macOS (
defaults read … wallpaper), GNOME/dconf, KDE, Niri, swww, hyprpaper. HEIC dynamic wallpapers decompose into dark and light variants; both feed the engine. -
Color extraction — K-Means clustering in CIELAB (not RGB). CIELAB is perceptually uniform, so distances correlate with the way humans see color similarity. 8-cluster K-Means yields a 16-color ANSI palette (8 normal + 8 bright) plus 4 accent slots.
-
Contrast enforcement — compute WCAG 2.2 contrast against the chosen background; nudge each foreground hue along the lightness axis until the ratio passes AAA. The nudge stays within the cluster to preserve aesthetic intent. If AAA cannot be reached, fall back to AA with a logged warning (never silently regress).
Large accent blocks use mode-aware polarity: dark themes lift the wallpaper hue until black text reaches AAA, while light themes deepen it until white text reaches AAA. This prevents dark themes from becoming muddy and light themes from becoming washed out.
Apple Human Interface Guidelines provide the semantic model for the two appearances. Wallpaper hues are calibrated against Apple's independently tuned increased-contrast light/dark system colors; primary, secondary, and tertiary surfaces follow the system-gray elevation hierarchy. Large terminal reading surfaces remain opaque so wallpaper translucency cannot alter the tested sRGB values or their contrast.
-
Format generation — emit one canonical TOML palette to
.chezmoidata/themes.toml, then runchezmoi applyso every theme-aware template (terminals, editors, status bars, browsers) regenerates from a single declarative source.Tmux hashes each session name across twelve shades derived from the palette's three block colors, with collision probing across active sessions, making concurrent projects visually distinct without per-project config. The session name is the only persistent chromatic element; window, path, system, and time modules use neutral semantic text. Width gates hide secondary telemetry in narrow panes, and a single platform-aware sample collects CPU, memory, and battery data every ten seconds.
AI CLI integration is provider-neutral first: terminal ANSI colours and an explicit
COLORFGBGmode reach every child process, including panes behind tmux. Small adapters then select native theme facilities where available (Codex, Claude, Gemini, Antigravity, Qwen, OpenCode, and Aider) while preserving unrelated provider settings. -
Companion pipelines —
dot-theme-syncfeeds the same accent colors to matugen for Material You-style GTK/Qt theming on Linux, keeping desktop chrome in lockstep with the terminal.
The whole flow is idempotent (chezmoi apply is no-op if nothing changed), repeatable, and tested under tests/unit/theme/.
Consequences
Positive
- Single source of truth: change the wallpaper, every tool retints in one keystroke.
- Accessibility is structural, not opt-in — every shipped palette passes WCAG AAA before it touches a config file.
- No competing "premium" dotfiles distribution (mathiasbynens, holman, paulirish, omakub) ships anything similar. The engine is a defining differentiator and surfaced in the README hero.
- Reuses chezmoi's templating — no new templating layer to maintain.
Negative
- K-Means on a 4K wallpaper takes ~500–800 ms; cached after first run, but the cold path is non-trivial.
- HEIC handling on Linux requires
libheif(extra dep on Debian/Ubuntu before 24.04). - Contrast enforcement can produce slightly different palettes from the same wallpaper across major OS versions when system color profiles differ (mitigated by snapshot tests).
Risks
- Wallpapers with extreme dynamic range (pure-white backgrounds, deep monochrome) may fail to produce an aesthetically pleasing 16-color palette even when WCAG AAA is satisfied. Mitigation: maintainer curation of a fallback theme set in
.chezmoidata/themes.toml. - Future Wayland compositors may not expose a stable wallpaper-detection API. The engine isolates source detection in a single module so the blast radius of compositor churn is one file.
Alternatives Considered
| Alternative | Why rejected |
|---|---|
| pywal/wal | RGB K-Means, no WCAG enforcement, limited target list. |
| matugen alone | Excellent for GTK/Qt; not terminal-aware. We use it in addition, not instead. |
| Static curated themes (Tokyo Night et al.) | Loses the "wallpaper coherence" property that motivates the whole project. |
| Hand-roll per-terminal scripts | Doesn't compose with chezmoi; loses the single-source-of-truth invariant. |
References
- WCAG 2.2 contrast: https://www.w3.org/TR/WCAG22/#contrast-minimum
- Apple Human Interface Guidelines — Color: https://developer.apple.com/design/human-interface-guidelines/color
- CIELAB color space: https://en.wikipedia.org/wiki/CIELAB_color_space
scripts/theme/— engine source.chezmoidata/themes.toml— output palette schema- Issue #873 — captures this ADR alongside
llms.txt