From d48373617ff20f3d9a7c765f6e47fc01e0ba0948 Mon Sep 17 00:00:00 2001 From: CodeWhale Agent Date: Fri, 12 Jun 2026 15:54:08 -0700 Subject: [PATCH] fix(tui): clear sidebar hover highlight on exit while loading --- crates/tui/src/tui/mouse_ui.rs | 17 ++++++++++++----- crates/tui/src/tui/ui/tests.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/crates/tui/src/tui/mouse_ui.rs b/crates/tui/src/tui/mouse_ui.rs index 7730ab74..abb31170 100644 --- a/crates/tui/src/tui/mouse_ui.rs +++ b/crates/tui/src/tui/mouse_ui.rs @@ -35,7 +35,10 @@ pub(crate) fn should_drop_loading_mouse_motion(app: &App, mouse: MouseEvent) -> match mouse.kind { MouseEventKind::Moved => { let over_sidebar = mouse_hits_rect(mouse, app.viewport.last_sidebar_area); - !(over_sidebar || app.sidebar_hover_tooltip.is_some()) + let was_over_sidebar = app.last_mouse_pos.is_some_and(|(column, row)| { + point_hits_rect(column, row, app.viewport.last_sidebar_area) + }); + !(over_sidebar || was_over_sidebar || app.sidebar_hover_tooltip.is_some()) } MouseEventKind::Drag(_) => { // Sidebar drag-to-resize must stay live during active turns — @@ -668,14 +671,18 @@ pub(crate) fn tick_selection_autoscroll(app: &mut App) { } pub(crate) fn mouse_hits_rect(mouse: MouseEvent, area: Option) -> bool { + point_hits_rect(mouse.column, mouse.row, area) +} + +fn point_hits_rect(column: u16, row: u16, area: Option) -> bool { let Some(area) = area else { return false; }; - mouse.column >= area.x - && mouse.column < area.x.saturating_add(area.width) - && mouse.row >= area.y - && mouse.row < area.y.saturating_add(area.height) + column >= area.x + && column < area.x.saturating_add(area.width) + && row >= area.y + && row < area.y.saturating_add(area.height) } pub(crate) fn open_context_menu(app: &mut App, mouse: MouseEvent) { diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index 921fc2bb..09d5ccec 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -830,6 +830,38 @@ fn loading_mouse_filter_allows_sidebar_hover_to_clear() { assert_eq!(app.last_mouse_pos, Some((12, 5))); } +#[test] +fn loading_mouse_filter_allows_sidebar_exit_to_clear_highlight() { + let mut app = create_test_app(); + app.is_loading = true; + app.viewport.last_sidebar_area = Some(Rect::new(60, 4, 20, 6)); + app.last_mouse_pos = Some((60, 5)); + + let exit_left = MouseEvent { + kind: MouseEventKind::Moved, + column: 59, + row: 5, + modifiers: KeyModifiers::NONE, + }; + + assert!( + !should_drop_loading_mouse_motion(&app, exit_left), + "first move out of the sidebar must clear stale sidebar hover state" + ); + handle_mouse_event(&mut app, exit_left); + + assert_eq!(app.last_mouse_pos, Some((59, 5))); + assert!(should_drop_loading_mouse_motion( + &app, + MouseEvent { + kind: MouseEventKind::Moved, + column: 58, + row: 5, + modifiers: KeyModifiers::NONE, + } + )); +} + #[test] fn jump_to_latest_button_click_scrolls_to_tail() { let mut app = create_test_app();