From 2da01367e687cae6bc1f6422cf38644e42258e14 Mon Sep 17 00:00:00 2001 From: CodeWhale Agent Date: Fri, 12 Jun 2026 15:26:53 -0700 Subject: [PATCH] fix(tui): keep sidebar resize live during active turns The loading mouse filter (should_drop_loading_mouse_motion) dropped all Drag events while app.is_loading unless a transcript selection or scrollbar drag was active. A sidebar resize started on the handle (Down passes the filter) then never received its Drag events, leaving the resize wedged mid-gesture during live runs (#3063, symptom of the #3096 subagent-runtime pressure on the TUI). - Allow Drag events through the loading filter while app.sidebar_resizing is set. - Clear last_sidebar_area / last_sidebar_handle_area and any in-flight resize when the sidebar is hidden or doesn't fit, so stale handle hit-areas can't capture clicks. - Tests: resize down/drag/up while loading, mouse-up outside the handle still ends the resize. Co-Authored-By: Claude Fable 5 --- crates/tui/src/tui/mouse_ui.rs | 3 ++ crates/tui/src/tui/ui.rs | 9 +++++ crates/tui/src/tui/ui/tests.rs | 73 ++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/crates/tui/src/tui/mouse_ui.rs b/crates/tui/src/tui/mouse_ui.rs index c523b303..deab0242 100644 --- a/crates/tui/src/tui/mouse_ui.rs +++ b/crates/tui/src/tui/mouse_ui.rs @@ -38,8 +38,11 @@ pub(crate) fn should_drop_loading_mouse_motion(app: &App, mouse: MouseEvent) -> !(over_sidebar || app.sidebar_hover_tooltip.is_some()) } MouseEventKind::Drag(_) => { + // Sidebar drag-to-resize must stay live during active turns — + // dropping these events wedges the resize state mid-drag (#3063). !app.viewport.transcript_selection.dragging && !app.viewport.transcript_scrollbar_dragging + && !app.sidebar_resizing } _ => false, } diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index bcf2330d..68a83668 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -7654,6 +7654,15 @@ fn render(f: &mut Frame, app: &mut App) { // hit-testing can route scroll events correctly. app.viewport.last_sidebar_area = sidebar_area; + // When the sidebar is hidden or doesn't fit, drop its stale mouse + // hit areas and any in-flight resize so clicks on those columns + // don't keep routing to an invisible handle (#3063). + if sidebar_area.is_none() { + app.last_sidebar_area = None; + app.last_sidebar_handle_area = None; + app.sidebar_resizing = false; + } + let chat_widget = ChatWidget::new(app, chat_area); let buf = f.buffer_mut(); chat_widget.render(chat_area, buf); diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index d9c7007b..a6d31548 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -727,6 +727,53 @@ fn loading_mouse_filter_keeps_active_drags() { app.viewport.transcript_selection.dragging = false; app.viewport.transcript_scrollbar_dragging = true; assert!(!should_drop_loading_mouse_motion(&app, drag)); + + // Sidebar drag-to-resize must also survive the loading filter (#3063). + app.viewport.transcript_scrollbar_dragging = false; + app.sidebar_resizing = true; + assert!(!should_drop_loading_mouse_motion(&app, drag)); +} + +#[test] +fn loading_mouse_filter_allows_sidebar_resize_down_drag_up() { + let mut app = create_test_app(); + app.is_loading = true; + setup_resize_handle(&mut app, 80, 33, 120); + + let down = MouseEvent { + kind: MouseEventKind::Down(MouseButton::Left), + column: 80, + row: 5, + modifiers: KeyModifiers::NONE, + }; + assert!(!should_drop_loading_mouse_motion(&app, down)); + handle_mouse_event(&mut app, down); + assert!(app.sidebar_resizing, "down on handle starts resize"); + + let drag = MouseEvent { + kind: MouseEventKind::Drag(MouseButton::Left), + column: 76, + row: 5, + modifiers: KeyModifiers::NONE, + }; + assert!( + !should_drop_loading_mouse_motion(&app, drag), + "resize drag must not be dropped while loading" + ); + handle_mouse_event(&mut app, drag); + let expected = ((37u32 * 100) / 120) as u16; + assert_eq!(app.sidebar_width_percent, expected); + + let up = MouseEvent { + kind: MouseEventKind::Up(MouseButton::Left), + column: 76, + row: 5, + modifiers: KeyModifiers::NONE, + }; + assert!(!should_drop_loading_mouse_motion(&app, up)); + handle_mouse_event(&mut app, up); + assert!(!app.sidebar_resizing); + assert!(app.sidebar_width_dirty); } #[test] @@ -3626,6 +3673,32 @@ fn sidebar_resize_up_ends_resizing_and_marks_dirty() { ); } +#[test] +fn sidebar_resize_up_outside_handle_still_ends_resizing() { + let mut app = create_test_app(); + setup_resize_handle(&mut app, 80, 33, 120); + app.sidebar_resizing = true; + app.sidebar_resize_anchor_x = 80; + app.sidebar_resize_anchor_width = 33; + + // Release far away from the handle and the sidebar entirely. + handle_mouse_event( + &mut app, + MouseEvent { + kind: MouseEventKind::Up(MouseButton::Left), + column: 5, + row: 20, + modifiers: KeyModifiers::NONE, + }, + ); + + assert!( + !app.sidebar_resizing, + "mouse up must clear resize state even outside the handle" + ); + assert!(app.sidebar_width_dirty); +} + fn make_subagent( id: &str, status: crate::tools::subagent::SubAgentStatus,