fix: keep sidebar collapse clickable with agent scrollbars (#4734)

* fix: keep sidebar collapse clickable with agent scrollbars

refs #4692

* docs: document sidebar mouse handling and regression coverage

refs #4692
This commit is contained in:
Eric Yue
2026-10-05 13:43:24 +03:00
committed by GitHub
parent e35f3937b0
commit 2923ce8747
2 changed files with 63 additions and 12 deletions
+15 -12
View File
@@ -676,6 +676,10 @@ impl ClientShellState {
}
}
/// Routes mouse input through overlays, shell controls, and pane interactions.
///
/// Hit-test order determines which overlapping control receives the event;
/// the sidebar toggle takes precedence over the agent scrollbar beneath it.
pub(super) fn handle_mouse(&mut self, mouse: MouseEvent, outcome: &mut ClientShellInput) {
self.update_link_hover(mouse, outcome);
let point = (mouse.column, mouse.row);
@@ -1935,9 +1939,17 @@ impl ClientShellState {
self.workspace_press = None;
self.tab_press = None;
self.chrome_drag = None;
if super::contains(self.hits.sidebar_divider, point)
&& !super::contains(self.hits.sidebar_toggle, point)
{
// The toggle is painted over the agent scrollbar's last cell.
if super::contains(self.hits.sidebar_toggle, point) {
self.sidebar_collapsed = !self.sidebar_collapsed;
self.sidebar_collapsed_manual = true;
self.invalidate_pane_surface();
outcome.repaint = true;
outcome.resize = true;
self.persist_chrome_preferences(outcome);
return;
}
if super::contains(self.hits.sidebar_divider, point) {
let now = std::time::Instant::now();
let double_click = self.last_sidebar_divider_click.is_some_and(|last| {
now.duration_since(last) <= std::time::Duration::from_millis(350)
@@ -2075,15 +2087,6 @@ impl ClientShellState {
outcome.repaint = true;
return;
}
if super::contains(self.hits.sidebar_toggle, point) {
self.sidebar_collapsed = !self.sidebar_collapsed;
self.sidebar_collapsed_manual = true;
self.invalidate_pane_surface();
outcome.repaint = true;
outcome.resize = true;
self.persist_chrome_preferences(outcome);
return;
}
let group_toggle = self.hits.workspaces.iter().find_map(|hit| {
let (rect, key) = hit.group_toggle.as_ref()?;
super::contains(*rect, point).then(|| (hit.endpoint_id.clone(), key.clone()))
+48
View File
@@ -247,6 +247,54 @@ fn state_with_scrollable_agents() -> (ClientShellState, ClientEndpointId) {
(state, remote)
}
/// Checks that the visible toggle wins overlapping scrollbar clicks and can reopen
/// the sidebar, for both endpoint layouts and both ends of the overflowing list.
#[test]
fn sidebar_toggle_remains_clickable_with_overflowing_agents() {
for saved_machine in [false, true] {
for scroll_to_bottom in [false, true] {
let (mut state, _) = state_with_scrollable_agents();
if !saved_machine {
state.set_endpoint_catalog(&[]);
}
state.agent_scroll = if scroll_to_bottom { usize::MAX } else { 0 };
let frame = state.compose(100, 28).expect("overflowing agent panel");
assert!(!state.hits.agent_scrollbar.is_empty());
let scroll = state.agent_scroll;
let toggle = state.hits.sidebar_toggle;
let buffer = frame.to_ratatui_buffer().expect("sidebar buffer");
assert_eq!(buffer[(toggle.x, toggle.y)].symbol(), "«");
let outcome = state.handle_raw_events(vec![RawInputEvent::Mouse(MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: toggle.x,
row: toggle.y,
modifiers: KeyModifiers::NONE,
})]);
assert!(
state.sidebar_collapsed,
"collapse with saved_machine={saved_machine}, scroll_to_bottom={scroll_to_bottom}"
);
assert!(state.sidebar_collapsed_manual);
assert!(outcome.repaint && outcome.resize);
assert_eq!(state.agent_scroll, scroll);
assert!(state.chrome_drag.is_none());
state.compose(100, 28).expect("collapsed sidebar");
let toggle = state.hits.sidebar_toggle;
let outcome = state.handle_raw_events(vec![RawInputEvent::Mouse(MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: toggle.x,
row: toggle.y,
modifiers: KeyModifiers::NONE,
})]);
assert!(!state.sidebar_collapsed);
assert!(outcome.repaint && outcome.resize);
assert!(state.chrome_drag.is_none());
}
}
}
#[test]
fn agent_navigation_reveals_offscreen_targets() {
use crate::input::KeybindAction;