From 3ff09dfb6e930d9fd818053129dbd2f67dfaf1ec Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 3 Jun 2026 16:54:13 +0200 Subject: [PATCH] docs: note the don't-retune-published-themes rule in ui-design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the D-69 lesson surfaced live: a named theme's palette is a user contract — ship an -hc sibling for a11y rather than retuning the artist's colours. Includes the baseline-vs-extended split and the Catppuccin Latte case where even the baseline chrome pairs are too soft. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/skills/ui-design/references/theme.md | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.claude/skills/ui-design/references/theme.md b/.claude/skills/ui-design/references/theme.md index de3e4b01..dcfbcc5b 100644 --- a/.claude/skills/ui-design/references/theme.md +++ b/.claude/skills/ui-design/references/theme.md @@ -62,6 +62,36 @@ types, priority levels) should NOT add tokens to `SurfaceTokens`. Instead: This keeps the core token surface lean and lets each extension own its palette. The pattern scales to any extension needing domain colors. +## Named themes are user contracts — never retune their palette (D-69) + +A bundled theme that ships under a recognisable name (`clide`, `midnight`, +`paper`, `terminal`, `catppuccin-*`, …) is a **user contract**. Its palette — +including syntax tokens, status colours, and `borderHi` — **MUST NOT be +retuned to pass a contrast gate**. Users pick Catppuccin because it looks like +Catppuccin; quietly darkening a swatch to clear WCAG changes what they signed +up for. Don't "fix" the artist's vision. + +When a theme fails the contrast gate ([D-22](../../../governance/decisions/accessibility.md)), +the fix is **a sibling `-hc` (high-contrast) theme**, not an edit to the +faithful one (D-69): + +- Keep `foo.yaml` pixel-faithful to the source palette. +- Add `foo-hc.yaml` — same silhouette, but muted text / status chips / syntax + tokens / focus border bumped to clear the strict (`extendedPairs`) gate. Only + `-hc`/`-cb` variants are held to the extended set; base themes pass the + baseline (`canonicalPairs`) only. +- Register both in `main.dart` `_loadBundledThemes` **and** the bundled list in + `test/a11y/contrast_test.dart`. + +Mechanics: `canonicalPairs` (baseline, every theme) vs `extendedPairs` +(strict, `-hc`/`-cb` only) live in `lib/kernel/src/theme/contrast.dart`. If a +faithful palette is so soft it can't clear even the *baseline* chrome pairs, +that's a design call — surface it, don't silently retune. (Catppuccin Latte +hit exactly this on two chrome pairs.) + +This is the same split VS Code ships: `Default Dark+` plus a separate +`Default High Contrast`. + ## Where to look in the codebase - Full token list: `lib/kernel/src/theme/tokens.dart`