mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 00:01:06 +00:00
@@ -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)),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -571,6 +571,67 @@ fn agent_sidebar_honors_priority_symbols_tokens_and_stable_hits() {
|
||||
assert_eq!(compact.cells[row_start].bg, compact.cells[row_start + 2].bg);
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
@@ -164,6 +164,39 @@ fn tab_bar_renders_endpoint_status_ellipses_and_clamps_to_useful_scroll() {
|
||||
assert!(!top.contains("ZOOM · host"));
|
||||
}
|
||||
|
||||
#[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>(
|
||||
|
||||
@@ -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,
|
||||
|
||||
+1
-3
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user