feat: add per-mode theme overrides - #2324
Conversation
allow [theme.custom.light] and [theme.custom.dark] blocks so custom overrides can differ per resolved appearance when auto_switch flips. precedence: theme defaults, then [theme.custom], then the block for the active mode. configs without the new blocks resolve unchanged. refs herdrdev#837
📝 WalkthroughWalkthroughThe theme configuration now supports light- and dark-appearance custom color overrides. Automatic theme switching applies the active mode overrides after shared custom and legacy accent colors. Tests and configuration documentation cover the new behavior. ChangesTheme appearance overrides
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant HostAppearance
participant resolve_effective_theme
participant resolve_palette_for_theme_name
participant Palette
HostAppearance->>resolve_effective_theme: select light or dark appearance
resolve_effective_theme->>resolve_palette_for_theme_name: pass active mode overrides
resolve_palette_for_theme_name->>Palette: apply shared custom colors
Palette->>Palette: apply active mode-specific colors
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Hi @aneym, thanks for your interest in contributing! Herdr automatically admits focused bug fixes from contributors who are not maintainers when the title uses Contributors who are not maintainers may submit only focused bug fixes. If this pull request fixes a bug, rename it to use a conventional Feature requests, behavior changes, and other proposals belong in GitHub Discussions and require maintainer approval before a pull request. If this gate classified the pull request incorrectly, reply and tag a maintainer listed in Patch size: 7 changed files, 391 changed lines. See https://github.com/herdrdev/herdr/blob/master/CONTRIBUTING.md for the contribution policy. |
Greptile SummaryThis PR adds light- and dark-appearance custom theme overrides, layered after shared overrides when automatic theme switching resolves an appearance.
Confidence Score: 4/5The Settings preview inconsistency should be fixed before merging so appearance-specific configurations are represented accurately while selecting a theme. Effective theme resolution applies the active mode block, but Settings constructs previews through a separate path that omits both mode-specific blocks and can display materially different colors. Files Needing Attention: src/app/mod.rs and src/app/input/settings.rs
|
| Filename | Overview |
|---|---|
| src/config/theme.rs | Adds serde-backed light and dark mode override structures with complete token coverage and parsing tests. |
| src/app/mod.rs | Selects the active mode overrides during auto-switch resolution, but the separate Settings preview remains inconsistent with this pipeline. |
| src/app/state.rs | Adds complete per-mode palette override application matching all supported shared color tokens. |
| docs/next/website/src/data/config-reference.json | Documents all light and dark mode-specific color keys. |
| docs/next/website/src/content/docs/configuration.mdx | Explains mode-specific configuration and the intended override precedence. |
| src/main.rs | Adds the new mode subtables to the generated default configuration example. |
| src/config.rs | Re-exports the new mode-specific theme color type. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
C[Theme config] --> B[Built-in palette]
B --> S[Shared theme.custom overrides]
S --> A{Resolved host appearance}
A -->|Light| L[theme.custom.light]
A -->|Dark| D[theme.custom.dark]
L --> P[Effective palette]
D --> P
S -. Settings preview currently stops here .-> V[Preview palette]
Reviews (1): Last reviewed commit: "feat: add per-mode theme overrides" | Re-trigger Greptile
| if let Some(custom) = mode_custom { | ||
| palette = palette.with_mode_overrides(custom); |
There was a problem hiding this comment.
Settings preview drops mode overrides
When auto_switch is enabled and theme.custom.light or theme.custom.dark is configured, effective theme resolution applies the active mode override here, but the Settings preview constructs its palette using only shared overrides and the legacy accent, causing the previewed colors to differ from the restored or applied palette.
Knowledge Base Used: App core: state, runtime loop, popups, worktrees, and the pane API
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Preview here intentionally matches what applying will produce. save_theme writes auto_switch = false alongside the name, and with auto-switch off there is no resolved appearance, so the per-mode blocks do not apply — same as resolve_effective_theme, which passes no mode overrides in its manual branch. Folding the active mode override into the preview would show colors that applying cannot produce. Cancel restores the exact pre-settings palette, mode overrides included, via settings.original_palette.
There was a problem hiding this comment.
That's a solid explanation — the invariant holds: preview shows exactly what apply produces, apply disables auto_switch, and without auto_switch the mode blocks are inert. The comment about settings.original_palette restoring on cancel ties the loop cleanly. My concern was unfounded.
Would you like me to remember this as a rule: "In the Settings preview, do not fold active mode overrides into the palette preview, because applying a theme always writes auto_switch = false, making per-mode blocks inactive in the applied state"?
Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.
Summary
[theme.custom]applies one set of overrides in both modes, so withauto_switcha value tuned for the dark theme washes out when the light theme activates. #837 proposed per-mode blocks and @ogulcancelik approved a PR there.This adds
[theme.custom.light]and[theme.custom.dark]. Resolution order: theme defaults, then[theme.custom], then the block matching the resolved appearance. Configs that do not use the new blocks resolve exactly as they do today.Both panes are the same seeded layout rendered with
catppuccin-latteactive; the only difference is the[theme.custom.light]block.Refs #837. #2032 asks for the same capability from a full-theme-pair angle; this shape covers it through
[theme.custom.light]/[theme.custom.dark]alone.Test plan
just checksteps all pass: fmt, clippy, windows-lint, config-reference parity, integration assets, plugin marketplace, script unittestslive_handoffx2,api_pingpane-info) are pty-timing timeouts that fail identically on unmodified master in the same environmentsrc/config/theme.rscover parsing both blocks, per-mode resolution precedence, and that configs without the new blocks resolve unchangedauto_switchflipping between catppuccin-mocha and catppuccin-latte