diff --git a/src/app/input/sidebar.rs b/src/app/input/sidebar.rs index 39affc83..32ad14b3 100644 --- a/src/app/input/sidebar.rs +++ b/src/app/input/sidebar.rs @@ -1,6 +1,6 @@ use ratatui::layout::Rect; -use crate::app::state::{AppState, Mode, ViewLayout}; +use crate::app::state::{AppState, ViewLayout}; use super::ScrollbarClickTarget; @@ -330,24 +330,6 @@ impl AppState { (idx < self.workspaces.len()).then_some(idx) } - fn collapsed_detail_workspace_idx(&self) -> Option { - if matches!( - self.mode, - Mode::Navigate - | Mode::RenameWorkspace - | Mode::Resize - | Mode::ConfirmClose - | Mode::ContextMenu - | Mode::Settings - | Mode::GlobalMenu - | Mode::KeybindHelp - ) { - Some(self.selected) - } else { - self.active - } - } - pub(super) fn collapsed_agent_detail_target_at( &self, row: u16, @@ -370,12 +352,10 @@ impl AppState { return None; } - let ws_idx = self.collapsed_detail_workspace_idx()?; - let ws = self.workspaces.get(ws_idx)?; let detail_idx = (row - detail_content_area.y) as usize; - let details = ws.pane_details(&self.terminals); + let details = crate::ui::agent_panel_entries(self); let detail = details.get(detail_idx)?; - Some((ws_idx, detail.tab_idx, detail.pane_id)) + Some((detail.ws_idx, detail.tab_idx, detail.pane_id)) } pub(super) fn workspace_drop_index_at_row(&self, row: u16) -> Option { @@ -498,7 +478,7 @@ mod tests { use crate::{ app::state::{AgentPanelSort, DragTarget, Mode}, config::SidebarCollapsedModeConfig, - detect::Agent, + detect::{Agent, AgentState}, workspace::Workspace, }; @@ -952,6 +932,51 @@ mod tests { assert_eq!(app.state.mode, Mode::Terminal); } + #[test] + fn clicking_collapsed_priority_agent_row_switches_to_matching_workspace() { + let mut app = app_for_mouse_test(); + let first = Workspace::test_new("one"); + let first_pane = first.tabs[0].root_pane; + let second = Workspace::test_new("two"); + let second_pane = second.tabs[0].root_pane; + + app.state.workspaces = vec![first, second]; + app.state.ensure_test_terminals(); + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + app.state.sidebar_collapsed = true; + app.state.agent_panel_sort = AgentPanelSort::Priority; + app.state.view.sidebar_rect = Rect::new(0, 0, 4, 20); + app.state.view.terminal_area = Rect::new(4, 0, 80, 20); + + let set_state = |app: &mut crate::app::App, ws_idx: usize, pane_id, state| { + let terminal_id = app.state.workspaces[ws_idx].tabs[0].panes[&pane_id] + .attached_terminal_id + .clone(); + let terminal = app.state.terminals.get_mut(&terminal_id).unwrap(); + terminal.detected_agent = Some(Agent::Claude); + terminal.state = state; + }; + set_state(&mut app, 0, first_pane, AgentState::Working); + set_state(&mut app, 1, second_pane, AgentState::Blocked); + + let (_, _, detail_area) = + crate::ui::collapsed_sidebar_sections(app.state.view.sidebar_rect); + app.handle_mouse(mouse( + MouseEventKind::Down(MouseButton::Left), + detail_area.x, + detail_area.y, + )); + + assert_eq!(app.state.active, Some(1)); + assert_eq!(app.state.selected, 1); + assert_eq!( + app.state.workspaces[1].tabs[0].layout.focused(), + second_pane + ); + } + #[test] fn clicking_collapsed_sidebar_toggle_expands_sidebar() { let mut app = app_for_mouse_test(); diff --git a/src/ui/sidebar.rs b/src/ui/sidebar.rs index 72b344fb..687e0ad5 100644 --- a/src/ui/sidebar.rs +++ b/src/ui/sidebar.rs @@ -710,11 +710,6 @@ pub(super) fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area: } } - let detail_ws_idx = if is_navigating { - Some(app.selected) - } else { - app.active - }; let detail_content_area = Rect::new( detail_area.x, detail_area.y, @@ -722,29 +717,26 @@ pub(super) fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area: detail_area.height.saturating_sub(1), ); if detail_content_area != Rect::default() { - if let Some(ws_idx) = detail_ws_idx { - if let Some(ws) = app.workspaces.get(ws_idx) { - for (detail_idx, detail) in ws.pane_details(&app.terminals).iter().enumerate() { - let y = detail_content_area.y + detail_idx as u16; - if y >= detail_content_area.y + detail_content_area.height { - break; - } - let pane_num = ws - .public_pane_number(detail.pane_id) - .unwrap_or(detail_idx + 1); - let pane_style = Style::default().fg(p.overlay0); - let (icon, icon_style) = - agent_icon(detail.state, detail.seen, app.spinner_tick, p); - frame.render_widget( - Paragraph::new(Line::from(vec![ - Span::styled(format!("{pane_num}"), pane_style), - Span::styled(" ", pane_style), - Span::styled(icon, icon_style), - ])), - Rect::new(detail_content_area.x, y, detail_content_area.width, 1), - ); - } + for (detail_idx, detail) in agent_panel_entries(app).iter().enumerate() { + let y = detail_content_area.y + detail_idx as u16; + if y >= detail_content_area.y + detail_content_area.height { + break; } + let pane_num = app + .workspaces + .get(detail.ws_idx) + .and_then(|ws| ws.public_pane_number(detail.pane_id)) + .unwrap_or(detail_idx + 1); + let pane_style = Style::default().fg(p.overlay0); + let (icon, icon_style) = agent_icon(detail.state, detail.seen, app.spinner_tick, p); + frame.render_widget( + Paragraph::new(Line::from(vec![ + Span::styled(format!("{pane_num}"), pane_style), + Span::styled(" ", pane_style), + Span::styled(icon, icon_style), + ])), + Rect::new(detail_content_area.x, y, detail_content_area.width, 1), + ); } } @@ -1297,6 +1289,45 @@ mod tests { assert_eq!(labels, ["four", "two", "one", "three"]); } + #[test] + fn collapsed_sidebar_uses_all_workspaces_agent_panel_order() { + let mut app = crate::app::state::AppState::test_new(); + app.workspaces = vec![Workspace::test_new("one"), Workspace::test_new("two")]; + app.ensure_test_terminals(); + app.active = Some(0); + app.selected = 0; + app.agent_panel_sort = crate::app::state::AgentPanelSort::Priority; + + let set_state = |app: &mut crate::app::state::AppState, ws_idx: usize, state| { + let pane = app.workspaces[ws_idx].tabs[0].root_pane; + let terminal_id = app.workspaces[ws_idx].tabs[0].panes[&pane] + .attached_terminal_id + .clone(); + let terminal = app.terminals.get_mut(&terminal_id).unwrap(); + terminal.detected_agent = Some(Agent::Claude); + terminal.state = state; + }; + set_state(&mut app, 0, AgentState::Working); + set_state(&mut app, 1, AgentState::Blocked); + + let area = Rect::new(0, 0, 5, 12); + let (_, _, detail_area) = collapsed_sidebar_sections(area); + let first_detail_y = detail_area.y; + let mut terminal = Terminal::new(TestBackend::new(area.width, area.height)) + .expect("test terminal should initialize"); + + terminal + .draw(|frame| render_sidebar_collapsed(&app, frame, area)) + .expect("collapsed sidebar should render"); + + let buffer = terminal.backend().buffer(); + assert_eq!(buffer[(detail_area.x + 2, first_detail_y)].symbol(), "◉"); + assert_eq!( + buffer[(detail_area.x + 2, first_detail_y)].style().fg, + Some(app.palette.red) + ); + } + #[cfg(unix)] #[tokio::test] async fn all_workspaces_agent_panel_entries_use_live_root_runtime_cwd_for_workspace_label() {