* refactor(theme): unify all presets behind one palette template
* docs(theme): update THEMES.md and AGENTS.md for the palette template
* fix(theme): restore per-preset error slot contrast, merge StatusColors into HermesStatusColors
- onErrorContainer becomes an explicit per-preset field (was derived from
onError — same-color pairs in Gruvbox/Catppuccin light made error text
invisible; 6 screens render this slot)
- Default dark + AMOLED onError -> White (were <3:1 on their error color)
- drop StatusColors + statusColorsOf mapper; PaletteColors.status is typed
HermesStatusColors directly (identical field lists, pure copy ceremony)
- ThemePalette getters now by lazy; schemeFor/statusFor accessors;
requireNotNull on the default-theme fallback (DefaultTheme is FULL)
- resolvers internal for testability
* test(theme): contrast, mode-validation, and fallback contract tests
- every shipped mode's onError/error + onErrorContainer/errorContainer
pair meets >= 3:1 WCAG contrast (kills the error-slot regression class)
- ThemeMode init validation rejects illegal palette combinations
- AMOLED darkOnly contract + light-mode fallback to Default light
* refactor(theme): Catppuccin — named swatches from the official palette spec
Hex values verified identical (zero color drift): repeated colors (Crust,
Surface0/1, Base, Text...) now live once as named swatches matching
catppuccin.com; bespoke light primary container tints stay local.
Mocha/Latte pair unchanged.
* refactor(theme): Gruvbox — authentic bright/faded accent scheme with named swatches
Redesign per gruvbox spec: bright accents on dark bg0-bg4, faded accents
on light bg0-bg4 (correct contrast play for light mode). Named swatches,
single Gray shared across modes. Dark error pair fixed for >= 3:1
(onErrorContainer -> DarkFg1 cream, was bright red on Bg3 = 1.87:1).
* feat(theme): replace default preset with Slate — modern slate blues + teal accents
The brand default (voltage purple/amber) becomes Slate: bright teal/blue
accents on dark bg0-bg4, faded variants on light bg0-bg4. Named swatches,
single neutral gray shared across modes. Slate is also the fallback for
darkOnly/lightOnly presets (AMOLED light mode).
* feat(theme): add Nord preset — Polar Night/Snow Storm surfaces, Frost/Aurora accents
Nord's single accent set is reused across both modes (pastel accents stay
light, so on* colors use Polar Night dark text in both modes — documented
in-file). Surface ladder shares outer tiers (Nord ships 4 dark + 3 light
tones for 5 tiers). Dark error pair fixed for >= 3:1 (onErrorContainer ->
Nord6, was Nord11 on Nord2 = 2.0:1). Wired into ThemePreset, the settings
picker, and docs; default label now reads Default (Slate).
* refactor(theme): Monochrome + AMOLED — pure grayscale ramp, true-black AMOLED variant
Monochrome: every swatch R=G=B, roles by lightness only (status colors
included — documented trade-off: pair with icons/labels). AMOLED: dark-only
true-black variant (pure #000000, OLED pixels off; #121212 baseline of
Monochrome doesn't do that), containers collapse where nothing darker
exists. Both ship maximum-contrast grayscale pairs (12.8:1+ on error
slots).
* chore(theme): drop Neon Noir, finalize docs + picker label
- remove NeonNoirScheme.kt + ThemePreset.NEON_NOIR everywhere (dispatcher,
picker, strings EN/KO, tests, AGENTS.md, THEMES.md) — 6 presets remain:
Default, Monochrome, Gruvbox, Catppuccin, AMOLED, Nord
- picker label for the default preset is now plain 'Default'
- THEMES.md: preset conventions section (named swatches, bright/faded
split, grayscale-status caveat, contrast test gate) + verify step runs
testDebugUnitTest
* fix(theme): make drawer icon colors visibly follow the active preset
Selected icon now tints with the theme's primary accent (teal for Slate,
orange for Gruvbox, mauve for Catppuccin, frost-blue for Nord) instead of
onPrimaryContainer — the new presets' containers are all dark gray with
near-white text, so the selected row looked identical across themes.
Unselected icon/text colors pinned explicitly to onSurfaceVariant.
* chore(ui): remove lightning emoji from the app
- drawer title + About name: '⚡ Hermes' -> 'Hermes' (EN + KO)
- skill markers (chat self-improvement badge, memory graph): ⚡ -> 🛠
- in-progress indicators (subagent inspection): ⚡ -> 🔄
---------
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
* refactor(theme): split per-preset color schemes into theme/presets/
Move each ThemePreset's ColorScheme + status colors into its own file under
theme/presets/ (Default, Monochrome, Gruvbox, Catppuccin, Amoled, NeonNoir).
Theme.kt becomes a thin dispatcher via resolveColorScheme/resolveStatusColors,
making individual themes far easier to create and edit.
- AMOLED keeps a dark-only scheme (falls back to default light)
- MONOCHROME/AMOLED reuse default status colors (no bespoke semantic set)
- aligned to current Voltage Purple palette + on* status-color params
Verified: ktlint clean, compileDebugKotlin passes.
* refactor(theme): make all preset files uniform 4-token shape
Every preset file now exports XxxDarkColorScheme / XxxLightColorScheme /
XxxDarkStatusColors / XxxLightStatusColors so they share one shape and import
order. MONOCHROME + AMOLED alias the default status colors; AMOLED also gets an
explicit AmoledLightColorScheme = DefaultLightColorScheme alias.
Theme.kt dispatcher is now a pure lookup (no special-casing) for both color
scheme and status colors. No runtime behavior change — pure structural cleanup.
Verified: ktlint clean, compileDebugKotlin passes.
* docs(theme): add THEMES.md documenting the per-preset convention
Co-located in theme/ — covers layout, the uniform 4-token shape, the pure
lookup dispatcher, why DEFAULT uses design tokens (intentional, not a bug),
and a step-by-step 'add a new theme' recipe.
Verified: ktlint clean.
---------
Co-authored-by: M57 <m57@hermes-control.app>