aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-09-13 20:17:42 +0400
committerGitHub <noreply@github.com>2026-09-13 18:17:42 +0200
commit5f26879bcd4190750c79cbff92454e400166ca98 (patch)
tree5843579cb70ad81b3dc102294496cdc6868e99da /src
parent981fe83ca2e23474255f4cacc4da2814f9de0744 (diff)
fix(ui): keep muted sidebar and tab labels readable (#4062)
refs #2692
Diffstat (limited to 'src')
-rw-r--r--src/client/shell/agent_sidebar.rs12
-rw-r--r--src/client/shell/sidebar.rs4
-rw-r--r--src/client/shell/tabs.rs5
-rw-r--r--src/client/shell/tests/agents_worktrees_notifications.rs61
-rw-r--r--src/client/shell/tests/keybindings_settings.rs33
-rw-r--r--src/client/shell/tests/mod.rs35
-rw-r--r--src/ui/sidebar.rs4
7 files changed, 134 insertions, 20 deletions
diff --git a/src/client/shell/agent_sidebar.rs b/src/client/shell/agent_sidebar.rs
index d6fe3352..e7ebabf3 100644
--- a/src/client/shell/agent_sidebar.rs
+++ b/src/client/shell/agent_sidebar.rs
@@ -332,16 +332,8 @@ pub(super) fn render_agent_row(
.fg(palette.subtext0)
.add_modifier(Modifier::BOLD)
};
- let status_style = Style::default()
- .fg(status_color(row.status, palette))
- .add_modifier(if row.focused {
- Modifier::empty()
- } else {
- Modifier::DIM
- });
- let secondary = Style::default()
- .fg(palette.overlay0)
- .add_modifier(Modifier::DIM);
+ let status_style = Style::default().fg(status_color(row.status, palette));
+ let secondary = Style::default().fg(palette.overlay0);
let icon = (
status_icon(row.status, config.status_indicators),
Style::default().fg(status_color(row.status, palette)),
diff --git a/src/client/shell/sidebar.rs b/src/client/shell/sidebar.rs
index c002fd62..161aecd5 100644
--- a/src/client/shell/sidebar.rs
+++ b/src/client/shell/sidebar.rs
@@ -703,9 +703,7 @@ pub(in crate::client::shell) fn render_workspace_rows(
status_icon(status, indicators),
Style::default().fg(status_color(status, palette)),
),
- Style::default()
- .fg(status_color(status, palette))
- .add_modifier(Modifier::DIM),
+ Style::default().fg(status_color(status, palette)),
workspace_style,
secondary_style,
Style::default().fg(palette.overlay1),
diff --git a/src/client/shell/tabs.rs b/src/client/shell/tabs.rs
index cf179d80..9e9901ff 100644
--- a/src/client/shell/tabs.rs
+++ b/src/client/shell/tabs.rs
@@ -112,10 +112,7 @@ pub(crate) fn render_tab_bar(
} else if tab.custom_label {
Style::default().fg(palette.overlay1).bg(palette.surface0)
} else {
- Style::default()
- .fg(palette.overlay0)
- .bg(palette.surface0)
- .add_modifier(Modifier::DIM)
+ Style::default().fg(palette.overlay0).bg(palette.surface0)
};
let padding = width.saturating_sub(display_width(&name));
let left = padding / 2;
diff --git a/src/client/shell/tests/agents_worktrees_notifications.rs b/src/client/shell/tests/agents_worktrees_notifications.rs
index fb47ab87..0b936903 100644
--- a/src/client/shell/tests/agents_worktrees_notifications.rs
+++ b/src/client/shell/tests/agents_worktrees_notifications.rs
@@ -572,6 +572,67 @@ fn agent_sidebar_honors_priority_symbols_tokens_and_stable_hits() {
}
#[test]
+fn muted_agent_sidebar_rows_do_not_stack_terminal_faint() {
+ let mut projected = snapshot();
+ projected.tabs[0].label = "second".into();
+ projected.tabs[0].custom_label = true;
+ projected.agents = vec![ClientShellAgent {
+ pane_id: "pane_1".into(),
+ workspace_id: "ws_1".into(),
+ tab_id: "tab_1".into(),
+ name: Some("reviewer".into()),
+ display_agent: None,
+ agent: Some("pi".into()),
+ title: None,
+ terminal_title: None,
+ terminal_title_stripped: None,
+ agent_status: AgentStatus::Working,
+ state_change_seq: 1,
+ state_labels: Vec::new(),
+ tokens: Vec::new(),
+ focused: true,
+ }];
+ let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
+ state.set_snapshot(Box::new(projected));
+ state.set_pane_surface(surface());
+ let frame = state.compose(106, 30).expect("agent sidebar frame");
+ let row = state.hits.agents.first().expect("agent row hit").0;
+ let buffer = frame.to_ratatui_buffer().expect("agent sidebar buffer");
+
+ for (label, needle) in [("tab", "second"), ("agent", "reviewer"), ("separator", "ยท")] {
+ let (x, y) = cell_symbol_position(&frame, row, needle);
+ let cell = buffer.cell((x, y)).expect("muted sidebar cell");
+ assert!(
+ !cell.modifier.contains(Modifier::DIM),
+ "{label} cell at ({x},{y}) should not stack terminal faint: {cell:?}"
+ );
+ }
+}
+
+#[test]
+fn workspace_state_text_does_not_stack_terminal_faint() {
+ use crate::config::SpaceSidebarToken;
+
+ let mut config = Config::default();
+ config.ui.sidebar.spaces.rows = vec![
+ vec![SpaceSidebarToken::StateIcon, SpaceSidebarToken::Workspace],
+ vec![SpaceSidebarToken::StateText],
+ ];
+ let mut state = ClientShellState::new(ClientShellConfig::from_config(&config));
+ state.set_snapshot(Box::new(snapshot()));
+ state.set_pane_surface(surface());
+ let frame = state.compose(106, 30).expect("workspace sidebar frame");
+ let rect = state.hits.workspaces.first().expect("workspace hit").rect;
+ let buffer = frame.to_ratatui_buffer().expect("workspace sidebar buffer");
+ let (x, y) = cell_symbol_position(&frame, rect, "idle");
+ let cell = buffer.cell((x, y)).expect("workspace state text cell");
+ assert!(
+ !cell.modifier.contains(Modifier::DIM),
+ "workspace state text at ({x},{y}) should not stack terminal faint: {cell:?}"
+ );
+}
+
+#[test]
fn active_agent_view_controls_sidebar_order_and_focus_indices() {
let mut projected = snapshot();
let mut second_pane = projected.panes[0].clone();
diff --git a/src/client/shell/tests/keybindings_settings.rs b/src/client/shell/tests/keybindings_settings.rs
index 1654d0df..e1b43a01 100644
--- a/src/client/shell/tests/keybindings_settings.rs
+++ b/src/client/shell/tests/keybindings_settings.rs
@@ -165,6 +165,39 @@ fn tab_bar_renders_endpoint_status_ellipses_and_clamps_to_useful_scroll() {
}
#[test]
+fn inactive_auto_named_tab_label_does_not_stack_terminal_faint() {
+ let mut projected = snapshot();
+ projected.tabs.push(ClientShellTab {
+ tab_id: "tab_2".into(),
+ workspace_id: "ws_1".into(),
+ number: 2,
+ label: "beta".into(),
+ custom_label: false,
+ zoomed: false,
+ focused: false,
+ agent_status: AgentStatus::Idle,
+ });
+ let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
+ state.set_snapshot(Box::new(projected));
+ state.set_pane_surface(surface());
+ let frame = state.compose(106, 20).expect("tab bar frame");
+ let rect = state
+ .hits
+ .tabs
+ .iter()
+ .find(|(_, tab_id)| tab_id == "tab_2")
+ .expect("inactive tab hit")
+ .0;
+ let buffer = frame.to_ratatui_buffer().expect("tab bar buffer");
+ let (x, y) = cell_symbol_position(&frame, rect, "beta");
+ let cell = buffer.cell((x, y)).expect("inactive tab cell");
+ assert!(
+ !cell.modifier.contains(Modifier::DIM),
+ "inactive tab label at ({x},{y}) should not stack terminal faint: {cell:?}"
+ );
+}
+
+#[test]
fn configured_prefix_is_client_owned_and_renders_its_bar() {
let config = toml::from_str::<Config>(
r#"
diff --git a/src/client/shell/tests/mod.rs b/src/client/shell/tests/mod.rs
index 497ce2c6..a308c1f9 100644
--- a/src/client/shell/tests/mod.rs
+++ b/src/client/shell/tests/mod.rs
@@ -133,6 +133,41 @@ fn surface() -> PaneSurfaceFrame {
}
}
+fn frame_rows(frame: &FrameData) -> Vec<String> {
+ frame
+ .cells
+ .chunks(frame.width as usize)
+ .map(|row| row.iter().map(|cell| cell.symbol.as_str()).collect())
+ .collect()
+}
+
+/// Absolute cell position of `needle` inside `area`, for style assertions.
+fn cell_symbol_position(frame: &FrameData, area: Rect, needle: &str) -> (u16, u16) {
+ let rows = frame_rows(frame);
+ for y in area.y..area.bottom().min(frame.height) {
+ let row = &rows[y as usize];
+ let slice = row
+ .chars()
+ .skip(area.x as usize)
+ .take(area.width as usize)
+ .collect::<String>();
+ if let Some(byte) = slice.find(needle) {
+ let column = slice[..byte].chars().count() as u16 + area.x;
+ return (column, y);
+ }
+ }
+ let visible = (area.y..area.bottom().min(frame.height))
+ .map(|y| {
+ rows[y as usize]
+ .chars()
+ .skip(area.x as usize)
+ .take(area.width as usize)
+ .collect::<String>()
+ })
+ .collect::<Vec<_>>();
+ panic!("symbol {needle:?} not found in {area:?}: {visible:?}");
+}
+
fn pane_scroll_result(
offset_from_bottom: u64,
max_offset_from_bottom: u64,
diff --git a/src/ui/sidebar.rs b/src/ui/sidebar.rs
index 178bdb1f..cbf6f1a9 100644
--- a/src/ui/sidebar.rs
+++ b/src/ui/sidebar.rs
@@ -219,9 +219,7 @@ pub(crate) fn resolved_token_spans(
let previous = &resolved[visible_indices[position - 1]];
spans.push(Span::styled(
tokens::separator(previous, token),
- Style::default()
- .fg(palette.overlay0)
- .add_modifier(Modifier::DIM),
+ Style::default().fg(palette.overlay0),
));
}
match &token.kind {