fix: keep spaces sidebar stable during resize (#3862)

refs #3817

Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
This commit is contained in:
akbash
2026-09-10 13:20:44 +03:00
committed by GitHub
co-authored by akbash-bot
parent 0911056d7e
commit e1bb3df0ac
2 changed files with 102 additions and 39 deletions
+59 -37
View File
@@ -40,35 +40,55 @@ impl ClientShellState {
.navigate_workspace_id
.as_ref()
.is_some_and(|target| self.navigation_target_valid(target));
super::endpoint_sidebar::render_expanded(
&mut buffer,
sidebar,
self.snapshot.as_deref(),
&self.config,
&mut render::ShellRenderState {
endpoints: &self.endpoints,
active_endpoint_id: &self.active_endpoint_id,
collapsed_endpoints: &self.collapsed_endpoints,
collapsed_groups: &self.collapsed_groups,
remote_collapsed_groups: &self.remote_collapsed_groups,
workspace_scroll: &mut self.workspace_scroll,
agent_scroll: &mut self.agent_scroll,
tab_scroll: &mut self.tab_scroll,
reveal_focused_workspace: &mut self.reveal_focused_workspace,
reveal_focused_tab: &mut self.reveal_focused_tab,
sidebar_collapsed: false,
sidebar_section_split: self.sidebar_section_split,
tab_drag_insert_index: None,
selected_workspace_id: self
.navigate_workspace_id
.as_ref()
.filter(|_| valid_navigation_target),
reveal_navigation_workspace: &mut self.reveal_navigation_workspace,
dragged_workspace_id: None,
workspace_drop_indicator_row: None,
},
&mut self.hits,
);
// A resize invalidates pane geometry, not the healthy Local workspace chrome.
let local_snapshot = self.snapshot.as_deref().filter(|_| {
self.endpoints.len() == 1
&& !self.sidebar_collapsed
&& layout.sidebar.width > 0
&& self.endpoint_status(&self.active_endpoint_id)
== Some(ClientEndpointStatus::Online)
});
let mut render_state = render::ShellRenderState {
endpoints: &self.endpoints,
active_endpoint_id: &self.active_endpoint_id,
collapsed_endpoints: &self.collapsed_endpoints,
collapsed_groups: &self.collapsed_groups,
remote_collapsed_groups: &self.remote_collapsed_groups,
workspace_scroll: &mut self.workspace_scroll,
agent_scroll: &mut self.agent_scroll,
tab_scroll: &mut self.tab_scroll,
reveal_focused_workspace: &mut self.reveal_focused_workspace,
reveal_focused_tab: &mut self.reveal_focused_tab,
sidebar_collapsed: false,
sidebar_section_split: self.sidebar_section_split,
tab_drag_insert_index: None,
selected_workspace_id: self
.navigate_workspace_id
.as_ref()
.filter(|_| valid_navigation_target),
reveal_navigation_workspace: &mut self.reveal_navigation_workspace,
dragged_workspace_id: None,
workspace_drop_indicator_row: None,
};
if let Some(snapshot) = local_snapshot {
render::render_sidebar(
&mut buffer,
sidebar,
snapshot,
&self.config,
&mut render_state,
&mut self.hits,
);
} else {
super::endpoint_sidebar::render_expanded(
&mut buffer,
sidebar,
self.snapshot.as_deref(),
&self.config,
&mut render_state,
&mut self.hits,
);
}
if !self.config.mouse_capture {
self.hits = ShellHitMap::default();
}
@@ -87,14 +107,16 @@ impl ClientShellState {
} else {
Rect::new(0, 0, cols, 1)
};
render::put_text(
&mut buffer,
message_area.x,
message_area.y,
message_area.width,
&message,
Style::default().fg(self.config.palette.overlay0),
);
if local_snapshot.is_none() || self.endpoint_error.is_some() {
render::put_text(
&mut buffer,
message_area.x,
message_area.y,
message_area.width,
&message,
Style::default().fg(self.config.palette.overlay0),
);
}
render::render_mode_bar(
&mut buffer,
Rect::new(0, 0, cols, rows),
+43 -2
View File
@@ -99,6 +99,7 @@ fn client_owned_sidebar_dividers_resize_live() {
state.set_snapshot(Box::new(snapshot()));
state.set_pane_surface(surface());
state.compose(106, 30).expect("expanded sidebar");
assert!(state.hits.machines.is_empty());
let workspace_body = state.hits.workspace_body;
let needless_scroll =
state.handle_raw_events(vec![RawInputEvent::Mouse(crossterm::event::MouseEvent {
@@ -128,15 +129,55 @@ fn client_owned_sidebar_dividers_resize_live() {
assert!(state.sidebar_width_manual);
assert!(resize.repaint);
assert!(resize.resize);
let waiting_frame = state.compose(106, 30).expect("waiting for resized surface");
let waiting_text: String = waiting_frame
.cells
.iter()
.map(|cell| cell.symbol.as_str())
.collect();
assert!(
waiting_text.contains(" spaces"),
"local sidebar must keep spaces while resizing: {waiting_text}"
);
assert!(!waiting_text.contains(" machines"));
assert!(!waiting_text.contains("Select a connected machine"));
assert!(!waiting_text.contains("LIVE"));
assert!(waiting_frame.cursor.is_none());
assert!(state.pane_surface.is_none());
assert!(state.hits.panes.is_empty());
assert!(state.hits.pane_splits.is_empty());
assert!(state.hits.machines.is_empty());
assert_eq!(state.hits.sidebar_divider.x, 31);
assert_eq!(state.hits.workspaces[0].workspace_id, "ws_1");
let next_resize =
state.handle_raw_events(vec![RawInputEvent::Mouse(crossterm::event::MouseEvent {
kind: MouseEventKind::Drag(MouseButton::Left),
column: 32,
row: width_divider.y + 2,
modifiers: KeyModifiers::empty(),
})]);
assert!(next_resize.resize);
state.compose(106, 30).expect("continued resize");
assert_eq!(state.hits.sidebar_divider.x, 32);
state.handle_raw_events(vec![RawInputEvent::Mouse(crossterm::event::MouseEvent {
kind: MouseEventKind::Up(MouseButton::Left),
column: 31,
column: 32,
row: width_divider.y + 2,
modifiers: KeyModifiers::empty(),
})]);
assert!(state.chrome_drag.is_none());
state.set_pane_surface(surface());
state.compose(106, 30).expect("resized sidebar");
let recovered_frame = state.compose(106, 30).expect("resized sidebar");
let recovered_text: String = recovered_frame
.cells
.iter()
.map(|cell| cell.symbol.as_str())
.collect();
assert!(recovered_text.contains(" spaces"));
assert!(recovered_text.contains("LIVE"));
assert!(!state.hits.panes.is_empty());
let section_divider = state.hits.sidebar_section_divider;
state.handle_raw_events(vec![RawInputEvent::Mouse(crossterm::event::MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),