fix(theme): keep default active rows subtle and add navigate cursor color (#2838)

refs #2792
This commit is contained in:
Can Celik
2026-08-15 15:36:12 +03:00
committed by GitHub
parent 15539d3dec
commit 9166e07b31
10 changed files with 89 additions and 28 deletions
+1
View File
@@ -13,6 +13,7 @@
- Panes can now route normal right-click gestures to mouse-reporting applications through the pane menu, `herdr pane input`, `pane.input.set`, or the `pane split --right-click pane` launch option.
- `theme.custom.sidebar_bg` can now give the desktop sidebar its own background without changing built-in theme defaults.
- Settings and `ui.status_indicators = "symbols"` can now use distinct static shapes for blocked, working, done, idle, and unknown agent states. (#2260)
- Navigate-mode selection rows now use a dedicated per-theme cursor color, customizable via `theme.custom.selection_bg`, so the cursor stays distinguishable from the active Space and Agent highlight.
- The plugin marketplace now discovers valid manifests at repository roots and subdirectories, groups multiple plugins under each repository, and publishes their versions and exact default-branch commits.
### Changed
@@ -263,7 +263,8 @@ You can override individual colors:
```toml
[theme.custom]
sidebar_bg = "#181825"
active_row_bg = "#313244"
active_row_bg = "#1e1e2e"
selection_bg = "#313244"
panel_bg = "reset"
accent = "#a6e3a1"
green = "#a6e3a1"
@@ -272,7 +273,7 @@ red = "#f38ba8"
yellow = "#f9e2af"
```
`sidebar_bg` optionally gives the desktop sidebar its own background. When omitted, the sidebar keeps the host terminal background. `active_row_bg` changes the active Space and focused Agent row background without affecting separators or scrollbar tracks.
`sidebar_bg` optionally gives the desktop sidebar its own background. When omitted, the sidebar keeps the host terminal background. `active_row_bg` changes the active Space and focused Agent row background without affecting separators or scrollbar tracks. `selection_bg` changes the Navigate-mode cursor row background in the sidebar.
Color values accept hex, named colors, `rgb(r,g,b)`, or reset aliases like `reset`, `default`, `none`, and `transparent`.
@@ -239,7 +239,8 @@ dark_name = "catppuccin"
```toml
[theme.custom]
sidebar_bg = "#181825"
active_row_bg = "#313244"
active_row_bg = "#1e1e2e"
selection_bg = "#313244"
panel_bg = "reset"
accent = "#a6e3a1"
green = "#a6e3a1"
@@ -248,7 +249,7 @@ red = "#f38ba8"
yellow = "#f9e2af"
```
`sidebar_bg` を使うと、デスクトップのサイドバーだけに背景色を設定できます。省略した場合、サイドバーはホストターミナルの背景を使います。`active_row_bg` は、区切り線やスクロールバートラックに影響を与えず、アクティブな Space とフォーカス中の Agent 行の背景色を変更します。
`sidebar_bg` を使うと、デスクトップのサイドバーだけに背景色を設定できます。省略した場合、サイドバーはホストターミナルの背景を使います。`active_row_bg` は、区切り線やスクロールバートラックに影響を与えず、アクティブな Space とフォーカス中の Agent 行の背景色を変更します。`selection_bg` は、サイドバーの Navigate モードのカーソル行の背景色を変更します。
色の値には、hex、名前付きの色、`rgb(r,g,b)`、または `reset`、`default`、`none`、`transparent` のようなリセットエイリアスが使えます。
@@ -239,7 +239,8 @@ dark_name = "catppuccin"
```toml
[theme.custom]
sidebar_bg = "#181825"
active_row_bg = "#313244"
active_row_bg = "#1e1e2e"
selection_bg = "#313244"
panel_bg = "reset"
accent = "#a6e3a1"
green = "#a6e3a1"
@@ -248,7 +249,7 @@ red = "#f38ba8"
yellow = "#f9e2af"
```
`sidebar_bg` 可单独设置桌面侧边栏的背景色。省略时,侧边栏继续使用宿主终端背景。`active_row_bg` 可更改当前 Space 和已聚焦 Agent 行的背景色,而不会影响分隔线或滚动条轨道。
`sidebar_bg` 可单独设置桌面侧边栏的背景色。省略时,侧边栏继续使用宿主终端背景。`active_row_bg` 可更改当前 Space 和已聚焦 Agent 行的背景色,而不会影响分隔线或滚动条轨道。`selection_bg` 可更改侧边栏中 Navigate 模式光标行的背景色。
颜色值支持十六进制、命名颜色、`rgb(r,g,b)`,以及 `reset`、`default`、`none`、`transparent` 等重置别名。
@@ -82,6 +82,12 @@
"default": "unset",
"description": "Set the active Space and focused Agent row background without changing separators or scrollbar tracks. Accepts hex, named colors, rgb(r,g,b), or reset aliases."
},
{
"key": "theme.custom.selection_bg",
"type": "color",
"default": "unset",
"description": "Set the Navigate-mode cursor row background in the sidebar without changing other selection surfaces. Accepts hex, named colors, rgb(r,g,b), or reset aliases."
},
{
"key": "theme.custom.surface0",
"type": "color",
+62 -15
View File
@@ -75,6 +75,8 @@ pub struct Palette {
pub sidebar_bg: Color,
/// Background for the active workspace and focused agent rows.
pub active_row_bg: Color,
/// Background for the Navigate-mode cursor row in the sidebar.
pub selection_bg: Color,
/// Subtle surface background for selected/focused items.
pub surface0: Color,
/// Slightly lighter surface for hover/active states.
@@ -112,7 +114,8 @@ impl Palette {
accent: Color::Rgb(137, 180, 250), // blue
panel_bg: Color::Rgb(24, 24, 37),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(49, 50, 68),
active_row_bg: Color::Rgb(30, 30, 46),
selection_bg: Color::Rgb(49, 50, 68),
surface0: Color::Rgb(49, 50, 68),
surface1: Color::Rgb(69, 71, 90),
surface_dim: Color::Rgb(30, 30, 46),
@@ -136,7 +139,8 @@ impl Palette {
accent: Color::Rgb(30, 102, 245),
panel_bg: Color::Rgb(239, 241, 245),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(204, 208, 218),
active_row_bg: Color::Rgb(230, 233, 239),
selection_bg: Color::Rgb(189, 208, 245),
surface0: Color::Rgb(204, 208, 218),
surface1: Color::Rgb(188, 192, 204),
surface_dim: Color::Rgb(230, 233, 239),
@@ -161,6 +165,7 @@ impl Palette {
panel_bg: Color::Reset,
sidebar_bg: Color::Reset,
active_row_bg: Color::DarkGray,
selection_bg: Color::Reset,
surface0: Color::Reset,
surface1: Color::DarkGray,
surface_dim: Color::DarkGray,
@@ -184,7 +189,8 @@ impl Palette {
accent: Color::Rgb(122, 162, 247), // blue
panel_bg: Color::Rgb(26, 27, 38),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(47, 51, 77),
active_row_bg: Color::Rgb(35, 38, 54),
selection_bg: Color::Rgb(45, 54, 80),
surface0: Color::Rgb(36, 40, 59),
surface1: Color::Rgb(65, 72, 104),
surface_dim: Color::Rgb(26, 27, 38),
@@ -208,7 +214,8 @@ impl Palette {
accent: Color::Rgb(46, 125, 233),
panel_bg: Color::Rgb(225, 226, 231),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(196, 200, 218),
active_row_bg: Color::Rgb(210, 211, 218),
selection_bg: Color::Rgb(182, 202, 231),
surface0: Color::Rgb(196, 200, 218),
surface1: Color::Rgb(168, 174, 203),
surface_dim: Color::Rgb(210, 211, 218),
@@ -232,7 +239,8 @@ impl Palette {
accent: Color::Rgb(189, 147, 249), // purple
panel_bg: Color::Rgb(40, 42, 54),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(68, 71, 90),
active_row_bg: Color::Rgb(55, 60, 82),
selection_bg: Color::Rgb(70, 63, 93),
surface0: Color::Rgb(68, 71, 90),
surface1: Color::Rgb(98, 114, 164),
surface_dim: Color::Rgb(40, 42, 54),
@@ -257,6 +265,7 @@ impl Palette {
panel_bg: Color::Rgb(46, 52, 64),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(67, 76, 94),
selection_bg: Color::Rgb(64, 80, 93),
surface0: Color::Rgb(59, 66, 82),
surface1: Color::Rgb(67, 76, 94),
surface_dim: Color::Rgb(46, 52, 64),
@@ -280,7 +289,8 @@ impl Palette {
accent: Color::Rgb(215, 153, 33), // yellow
panel_bg: Color::Rgb(40, 40, 40),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(80, 73, 69),
active_row_bg: Color::Rgb(50, 49, 48),
selection_bg: Color::Rgb(75, 63, 39),
surface0: Color::Rgb(60, 56, 54),
surface1: Color::Rgb(80, 73, 69),
surface_dim: Color::Rgb(40, 40, 40),
@@ -304,7 +314,8 @@ impl Palette {
accent: Color::Rgb(7, 102, 120),
panel_bg: Color::Rgb(251, 241, 199),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(213, 196, 161),
active_row_bg: Color::Rgb(242, 229, 188),
selection_bg: Color::Rgb(235, 219, 178),
surface0: Color::Rgb(235, 219, 178),
surface1: Color::Rgb(213, 196, 161),
surface_dim: Color::Rgb(242, 229, 188),
@@ -328,7 +339,8 @@ impl Palette {
accent: Color::Rgb(97, 175, 239), // blue
panel_bg: Color::Rgb(40, 44, 52),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(62, 68, 81),
active_row_bg: Color::Rgb(49, 54, 64),
selection_bg: Color::Rgb(51, 70, 89),
surface0: Color::Rgb(44, 49, 58),
surface1: Color::Rgb(62, 68, 81),
surface_dim: Color::Rgb(40, 44, 52),
@@ -353,6 +365,7 @@ impl Palette {
panel_bg: Color::Rgb(250, 250, 250),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(216, 219, 226),
selection_bg: Color::Rgb(205, 219, 248),
surface0: Color::Rgb(240, 240, 241),
surface1: Color::Rgb(229, 229, 230),
surface_dim: Color::Rgb(245, 245, 246),
@@ -377,6 +390,7 @@ impl Palette {
panel_bg: Color::Rgb(0, 43, 54),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(22, 75, 87),
selection_bg: Color::Rgb(8, 62, 85),
surface0: Color::Rgb(7, 54, 66),
surface1: Color::Rgb(88, 110, 117),
surface_dim: Color::Rgb(0, 43, 54),
@@ -400,7 +414,8 @@ impl Palette {
accent: Color::Rgb(38, 139, 210),
panel_bg: Color::Rgb(253, 246, 227),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(222, 216, 198),
active_row_bg: Color::Rgb(238, 232, 213),
selection_bg: Color::Rgb(201, 220, 223),
surface0: Color::Rgb(238, 232, 213),
surface1: Color::Rgb(147, 161, 161),
surface_dim: Color::Rgb(238, 232, 213),
@@ -425,6 +440,7 @@ impl Palette {
panel_bg: Color::Rgb(31, 31, 40),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(54, 54, 70),
selection_bg: Color::Rgb(50, 56, 75),
surface0: Color::Rgb(42, 42, 55),
surface1: Color::Rgb(54, 54, 70),
surface_dim: Color::Rgb(31, 31, 40),
@@ -448,7 +464,8 @@ impl Palette {
accent: Color::Rgb(77, 105, 155),
panel_bg: Color::Rgb(242, 236, 188),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(201, 203, 209),
active_row_bg: Color::Rgb(213, 206, 163),
selection_bg: Color::Rgb(220, 213, 172),
surface0: Color::Rgb(220, 213, 172),
surface1: Color::Rgb(201, 203, 209),
surface_dim: Color::Rgb(213, 206, 163),
@@ -472,7 +489,8 @@ impl Palette {
accent: Color::Rgb(196, 167, 231), // iris
panel_bg: Color::Rgb(25, 23, 36),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(64, 61, 82),
active_row_bg: Color::Rgb(38, 35, 58),
selection_bg: Color::Rgb(59, 52, 75),
surface0: Color::Rgb(31, 29, 46),
surface1: Color::Rgb(38, 35, 58),
surface_dim: Color::Rgb(38, 35, 58),
@@ -497,6 +515,7 @@ impl Palette {
panel_bg: Color::Rgb(250, 244, 237),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(227, 217, 207),
selection_bg: Color::Rgb(242, 233, 225),
surface0: Color::Rgb(242, 233, 225),
surface1: Color::Rgb(255, 250, 243),
surface_dim: Color::Rgb(242, 233, 225),
@@ -520,7 +539,8 @@ impl Palette {
accent: Color::Rgb(255, 199, 153),
panel_bg: Color::Rgb(26, 26, 26),
sidebar_bg: Color::Reset,
active_row_bg: Color::Rgb(51, 51, 51),
active_row_bg: Color::Rgb(16, 16, 16),
selection_bg: Color::Rgb(35, 35, 35),
surface0: Color::Rgb(35, 35, 35),
surface1: Color::Rgb(40, 40, 40),
surface_dim: Color::Rgb(16, 16, 16),
@@ -578,6 +598,9 @@ impl Palette {
if let Some(c) = &custom.active_row_bg {
self.active_row_bg = parse_color(c);
}
if let Some(c) = &custom.selection_bg {
self.selection_bg = parse_color(c);
}
if let Some(c) = &custom.surface0 {
self.surface0 = parse_color(c);
}
@@ -2412,7 +2435,7 @@ mod tests {
let palette = Palette::from_name(name).unwrap();
let background_contrast = contrast_ratio(palette.panel_bg, palette.active_row_bg);
assert!(
background_contrast >= 1.25,
background_contrast >= 1.05,
"active row blends into the matching terminal background for {name}: {background_contrast:.2}:1"
);
@@ -2421,9 +2444,31 @@ mod tests {
text_contrast >= 3.0,
"active row text loses contrast for {name}: {text_contrast:.2}:1"
);
}
}
#[test]
fn built_in_selection_rows_stay_distinct_from_background_and_active_rows() {
for name in THEME_NAMES
.iter()
.copied()
.filter(|name| *name != "terminal")
{
let palette = Palette::from_name(name).unwrap();
let background_contrast = contrast_ratio(palette.panel_bg, palette.selection_bg);
assert!(
background_contrast >= 1.05,
"selection row blends into the matching terminal background for {name}: {background_contrast:.2}:1"
);
let text_contrast = contrast_ratio(palette.text, palette.selection_bg);
assert!(
text_contrast >= 3.0,
"selection row text loses contrast for {name}: {text_contrast:.2}:1"
);
assert_ne!(
palette.active_row_bg, palette.surface_dim,
"active row still shares the separator color for {name}"
palette.selection_bg, palette.active_row_bg,
"selection row shares the active row color for {name}"
);
}
}
@@ -2445,12 +2490,14 @@ mod tests {
let custom = crate::config::CustomThemeColors {
sidebar_bg: Some("#181825".to_string()),
active_row_bg: Some("#313244".to_string()),
selection_bg: Some("#45475a".to_string()),
..Default::default()
};
let palette = Palette::catppuccin().with_overrides(&custom);
assert_eq!(palette.sidebar_bg, Color::Rgb(24, 24, 37));
assert_eq!(palette.active_row_bg, Color::Rgb(49, 50, 68));
assert_eq!(palette.selection_bg, Color::Rgb(69, 71, 90));
}
#[test]
+3
View File
@@ -104,6 +104,7 @@ pub struct CustomThemeColors {
pub panel_bg: Option<String>,
pub sidebar_bg: Option<String>,
pub active_row_bg: Option<String>,
pub selection_bg: Option<String>,
pub surface0: Option<String>,
pub surface1: Option<String>,
pub surface_dim: Option<String>,
@@ -266,6 +267,7 @@ name = "nord"
panel_bg = "#1e1e2e"
sidebar_bg = "#181825"
active_row_bg = "#313244"
selection_bg = "#45475a"
accent = "#ff79c6"
red = "rgb(255, 85, 85)"
"##;
@@ -275,6 +277,7 @@ red = "rgb(255, 85, 85)"
assert_eq!(custom.panel_bg.as_deref(), Some("#1e1e2e"));
assert_eq!(custom.sidebar_bg.as_deref(), Some("#181825"));
assert_eq!(custom.active_row_bg.as_deref(), Some("#313244"));
assert_eq!(custom.selection_bg.as_deref(), Some("#45475a"));
assert_eq!(custom.accent.as_deref(), Some("#ff79c6"));
assert_eq!(custom.red.as_deref(), Some("rgb(255, 85, 85)"));
assert!(custom.green.is_none());
+2 -1
View File
@@ -130,7 +130,8 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration
# Accepts: hex (#rrggbb), named colors, rgb(r,g,b), or panel_bg = "reset"
# [theme.custom]
# sidebar_bg = "#181825"
# active_row_bg = "#313244"
# active_row_bg = "#1e1e2e"
# selection_bg = "#313244"
# panel_bg = "reset"
# accent = "#f5c2e7"
# red = "#ff6188"
+5 -5
View File
@@ -788,14 +788,14 @@ pub(super) fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area:
let is_selected = visible_idx == app.selected && is_navigating;
let is_active = Some(visible_idx) == app.active;
let row_style = if is_selected {
Style::default().bg(p.surface0)
Style::default().bg(p.selection_bg)
} else if is_active {
Style::default().bg(p.active_row_bg)
} else {
Style::default()
};
let num_style = if is_selected {
Style::default().fg(p.overlay1).bg(p.surface0)
Style::default().fg(p.overlay1).bg(p.selection_bg)
} else if is_active {
Style::default().fg(p.text).bg(p.active_row_bg)
} else {
@@ -1248,7 +1248,7 @@ fn render_workspace_list(
if highlighted {
let bg = if selected {
p.surface0
p.selection_bg
} else if is_dragged {
p.surface1
} else {
@@ -1779,8 +1779,8 @@ rows = [[{ token = "workspace", bold = false }, { token = "agent", dim = false }
);
assert_eq!(
buffer[(0, selected_row)].bg,
app.palette.surface0,
"navigate selection should keep its existing surface0 background"
app.palette.selection_bg,
"navigate selection should use its dedicated cursor background"
);
}
+1 -1
View File
@@ -304,7 +304,7 @@ mod tests {
assert_eq!(frame.hyperlinks, vec![uri.to_owned()]);
assert_eq!(
frame_digest(&frame),
"f692e425877ef32cd0f3435dd8e252f33fff4e6dd5088a9324620895a6bd4c13"
"a7c21fa42305a41231c7ae254f264f6ef923f46301d8fc4cd35ab6dfdd651b6b"
);
}