fix(tui): use muted selection highlights in dark themes
This commit is contained in:
@@ -25,6 +25,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- **Sidebar hover popovers (#3088).** Streaming turns now keep sidebar hover
|
||||
popovers responsive while continuing to throttle transcript/body mouse
|
||||
motion.
|
||||
- **Dark-theme selection contrast (#3074, thanks @drpars).** Session, config,
|
||||
help, context-menu, and approval selections now use the muted selection
|
||||
background instead of the bright accent color.
|
||||
- **Cursor-style activity metadata rows (#3146).** Dense successful tool-run
|
||||
summaries now render as a single muted `Explored ...` / `Updated metadata`
|
||||
row, include short command-family labels for successful generic verifier
|
||||
|
||||
@@ -25,6 +25,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- **Sidebar hover popovers (#3088).** Streaming turns now keep sidebar hover
|
||||
popovers responsive while continuing to throttle transcript/body mouse
|
||||
motion.
|
||||
- **Dark-theme selection contrast (#3074, thanks @drpars).** Session, config,
|
||||
help, context-menu, and approval selections now use the muted selection
|
||||
background instead of the bright accent color.
|
||||
- **Cursor-style activity metadata rows (#3146).** Dense successful tool-run
|
||||
summaries now render as a single muted `Explored ...` / `Updated metadata`
|
||||
row, include short command-family labels for successful generic verifier
|
||||
|
||||
@@ -221,6 +221,24 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_row_text_contrasts_on_selection_bg() {
|
||||
for theme in ALL_THEMES {
|
||||
let name = theme.name;
|
||||
let Some(fg) = rgb(theme.text_body) else {
|
||||
continue;
|
||||
};
|
||||
let Some(bg) = rgb(theme.selection_bg) else {
|
||||
continue;
|
||||
};
|
||||
let cr = contrast_ratio(fg, bg);
|
||||
assert!(
|
||||
cr >= 4.5,
|
||||
"{name}: selected-row text contrast {cr:.1}:1 is below 4.5:1 (fg={fg:?}, bg={bg:?})"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn surface_layers_are_distinct() {
|
||||
for theme in ALL_THEMES {
|
||||
|
||||
@@ -188,8 +188,8 @@ impl ModalView for ContextMenuView {
|
||||
let text = trim_to_width(&format!("{label}{description}"), inner_width);
|
||||
let style = if idx == self.selected {
|
||||
Style::default()
|
||||
.fg(palette::TEXT_PRIMARY)
|
||||
.bg(palette::DEEPSEEK_BLUE)
|
||||
.fg(palette::SELECTION_TEXT)
|
||||
.bg(palette::SELECTION_BG)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default()
|
||||
|
||||
@@ -668,7 +668,7 @@ fn build_list_lines(
|
||||
let style = if idx == selected {
|
||||
Style::default()
|
||||
.fg(palette::SELECTION_TEXT)
|
||||
.bg(palette::DEEPSEEK_BLUE)
|
||||
.bg(palette::SELECTION_BG)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(palette::TEXT_PRIMARY)
|
||||
@@ -1086,7 +1086,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_list_lines_selected_row_uses_strong_highlight() {
|
||||
fn build_list_lines_selected_row_uses_muted_selection_highlight() {
|
||||
let sessions = vec![
|
||||
test_session(1, "first session"),
|
||||
test_session(2, "second session"),
|
||||
@@ -1109,7 +1109,8 @@ mod tests {
|
||||
.expect("selected row should have a span");
|
||||
|
||||
assert_eq!(span.style.fg, Some(palette::SELECTION_TEXT));
|
||||
assert_eq!(span.style.bg, Some(palette::DEEPSEEK_BLUE));
|
||||
assert_eq!(span.style.bg, Some(palette::SELECTION_BG));
|
||||
assert_ne!(span.style.bg, Some(palette::DEEPSEEK_BLUE));
|
||||
assert!(span.style.add_modifier.contains(Modifier::BOLD));
|
||||
}
|
||||
|
||||
|
||||
@@ -451,7 +451,7 @@ impl ModalView for HelpView {
|
||||
let style = if is_selected {
|
||||
Style::default()
|
||||
.fg(palette::SELECTION_TEXT)
|
||||
.bg(palette::DEEPSEEK_BLUE)
|
||||
.bg(palette::SELECTION_BG)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(palette::TEXT_PRIMARY)
|
||||
@@ -699,7 +699,7 @@ mod tests {
|
||||
let cell = &buf[(x, y)];
|
||||
row.push_str(cell.symbol());
|
||||
row_has_highlight |=
|
||||
cell.bg == palette::DEEPSEEK_BLUE && cell.fg == palette::SELECTION_TEXT;
|
||||
cell.bg == palette::SELECTION_BG && cell.fg == palette::SELECTION_TEXT;
|
||||
}
|
||||
if row_has_highlight && row.contains(&selected_label) {
|
||||
highlighted_label = true;
|
||||
@@ -714,7 +714,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_help_row_uses_stronger_highlight() {
|
||||
fn selected_help_row_uses_selection_highlight() {
|
||||
let view = HelpView::new();
|
||||
let area = Rect::new(0, 0, 96, 32);
|
||||
let mut buf = Buffer::empty(area);
|
||||
@@ -724,7 +724,7 @@ mod tests {
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
let cell = &buf[(x, y)];
|
||||
if cell.bg == palette::DEEPSEEK_BLUE && cell.fg == palette::SELECTION_TEXT {
|
||||
if cell.bg == palette::SELECTION_BG && cell.fg == palette::SELECTION_TEXT {
|
||||
found_highlight = true;
|
||||
break;
|
||||
}
|
||||
@@ -733,7 +733,7 @@ mod tests {
|
||||
|
||||
assert!(
|
||||
found_highlight,
|
||||
"selected row should use a strong blue highlight"
|
||||
"selected row should use the semantic selection highlight"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1484,8 +1484,8 @@ impl ModalView for ConfigView {
|
||||
let selected = *idx == self.selected;
|
||||
let style = if selected {
|
||||
Style::default()
|
||||
.fg(ratatui::style::Color::White)
|
||||
.bg(palette::DEEPSEEK_BLUE)
|
||||
.fg(palette::SELECTION_TEXT)
|
||||
.bg(palette::SELECTION_BG)
|
||||
.add_modifier(ratatui::style::Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(palette::TEXT_PRIMARY)
|
||||
@@ -2088,6 +2088,7 @@ mod tests {
|
||||
};
|
||||
use crate::config::Config;
|
||||
use crate::localization::{Locale, MessageId, tr};
|
||||
use crate::palette;
|
||||
use crate::settings::Settings;
|
||||
use crate::tools::subagent::{
|
||||
SubAgentAssignment, SubAgentResult, SubAgentStatus, SubAgentType,
|
||||
@@ -2598,6 +2599,46 @@ base_url = "https://api.xiaomimimo.com/v1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_view_selected_row_uses_muted_selection_highlight() {
|
||||
let mut view = create_config_view(Locale::En);
|
||||
view.selected = view
|
||||
.rows
|
||||
.iter()
|
||||
.position(|row| row.key == "theme")
|
||||
.expect("theme row");
|
||||
view.adjust_scroll(8);
|
||||
let area = Rect::new(0, 0, 100, 24);
|
||||
let mut buf = Buffer::empty(area);
|
||||
|
||||
view.render(area, &mut buf);
|
||||
|
||||
let y = view
|
||||
.last_row_hitboxes
|
||||
.borrow()
|
||||
.iter()
|
||||
.find_map(|(y, idx)| (*idx == view.selected).then_some(*y))
|
||||
.expect("selected config row should have a hitbox");
|
||||
let highlighted_cells = (area.x..area.x.saturating_add(area.width))
|
||||
.filter(|&x| {
|
||||
let cell = &buf[(x, y)];
|
||||
!cell.symbol().trim().is_empty()
|
||||
&& cell.bg == palette::SELECTION_BG
|
||||
&& cell.fg == palette::SELECTION_TEXT
|
||||
})
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
highlighted_cells >= 4,
|
||||
"selected config row should render readable selection text"
|
||||
);
|
||||
assert!(
|
||||
!(area.x..area.x.saturating_add(area.width))
|
||||
.any(|x| buf[(x, y)].bg == palette::DEEPSEEK_BLUE),
|
||||
"selected config row should not use the bright accent background"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_view_keeps_scope_column_aligned_for_long_keys() {
|
||||
let mut view = create_config_view(Locale::ZhHans);
|
||||
|
||||
@@ -1454,7 +1454,7 @@ fn approval_palette(risk: RiskLevel) -> ApprovalColors {
|
||||
fn approval_selected_style() -> Style {
|
||||
Style::default()
|
||||
.fg(palette::SELECTION_TEXT)
|
||||
.bg(palette::DEEPSEEK_BLUE)
|
||||
.bg(palette::SELECTION_BG)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
}
|
||||
|
||||
@@ -4072,21 +4072,21 @@ mod tests {
|
||||
let selected_row = (area.y..area.y.saturating_add(area.height))
|
||||
.find(|&y| {
|
||||
(area.x..area.x.saturating_add(area.width))
|
||||
.any(|x| buf[(x, y)].bg == palette::DEEPSEEK_BLUE)
|
||||
.any(|x| buf[(x, y)].bg == palette::SELECTION_BG)
|
||||
})
|
||||
.expect("selected approval row should use blue background");
|
||||
.expect("selected approval row should use selection background");
|
||||
let highlighted_cells = (area.x..area.x.saturating_add(area.width))
|
||||
.filter(|&x| {
|
||||
let cell = &buf[(x, selected_row)];
|
||||
!cell.symbol().trim().is_empty()
|
||||
&& cell.bg == palette::DEEPSEEK_BLUE
|
||||
&& cell.bg == palette::SELECTION_BG
|
||||
&& cell.fg == palette::SELECTION_TEXT
|
||||
})
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
highlighted_cells >= 4,
|
||||
"selected destructive option should render visible blue/white text"
|
||||
"selected destructive option should render visible selection text"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user