From 9446eef9b95b13dd433296b8f1b380b384c04c2d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 12 Feb 2026 16:59:28 -0500 Subject: [PATCH] Preserve panel zoom state across workspace switches (#49069) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the agent panel (or any dock panel) is open and fullscreened/zoomed, switching to a different workspace in the sidebar and then switching back caused the panel to close. It should remain both open and zoomed. The root cause was in `MultiWorkspace::focus_active_workspace()` — it always focused the center pane of the active workspace. This triggered `dismiss_zoomed_items_to_reveal(None)`, which closed any zoomed dock panel (the same behavior as when a user intentionally clicks away from a zoomed panel). The fix checks if any dock has a zoomed panel before deciding what to focus. If a zoomed panel exists, it focuses that panel instead of the center pane, preventing the dismiss logic from firing. Closes AI-22 Release Notes: - Fixed panels losing their fullscreen state when switching between workspaces. --- crates/workspace/src/multi_workspace.rs | 24 ++++++- crates/workspace/src/workspace.rs | 95 +++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index ffa1b07a735558df86fe3b4bb4007ad6647a45a8..6f853bfae20f4e79ce1a17338d9d9ad6e79af42c 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -302,8 +302,28 @@ impl MultiWorkspace { } fn focus_active_workspace(&self, window: &mut Window, cx: &mut App) { - let pane = self.workspace().read(cx).active_pane().clone(); - let focus_handle = pane.read(cx).focus_handle(cx); + // If a dock panel is zoomed, focus it instead of the center pane. + // Otherwise, focusing the center pane triggers dismiss_zoomed_items_to_reveal + // which closes the zoomed dock. + let focus_handle = { + let workspace = self.workspace().read(cx); + let mut target = None; + for dock in workspace.all_docks() { + let dock = dock.read(cx); + if dock.is_open() { + if let Some(panel) = dock.active_panel() { + if panel.is_zoomed(window, cx) { + target = Some(panel.panel_focus_handle(cx)); + break; + } + } + } + } + target.unwrap_or_else(|| { + let pane = workspace.active_pane().clone(); + pane.read(cx).focus_handle(cx) + }) + }; window.focus(&focus_handle, cx); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 85f4a9d8e0eed422c13715f05be72a05b841f0e9..cbaa5c7451588c369602ca0e576d713c50a839d0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -12724,6 +12724,101 @@ mod tests { }); } + #[gpui::test] + async fn test_panel_zoom_preserved_across_workspace_switch(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + + let project_a = Project::test(fs.clone(), [], cx).await; + let project_b = Project::test(fs, [], cx).await; + + let multi_workspace_handle = + cx.add_window(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx)); + + let workspace_a = multi_workspace_handle + .read_with(cx, |mw, _| mw.workspace().clone()) + .unwrap(); + + let _workspace_b = multi_workspace_handle + .update(cx, |mw, window, cx| { + mw.test_add_workspace(project_b, window, cx) + }) + .unwrap(); + + // Switch to workspace A + multi_workspace_handle + .update(cx, |mw, window, cx| { + mw.activate_index(0, window, cx); + }) + .unwrap(); + + let cx = &mut VisualTestContext::from_window(multi_workspace_handle.into(), cx); + + // Add a panel to workspace A's right dock and open the dock + let panel = workspace_a.update_in(cx, |workspace, window, cx| { + let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, 100, cx)); + workspace.add_panel(panel.clone(), window, cx); + workspace + .right_dock() + .update(cx, |dock, cx| dock.set_open(true, window, cx)); + panel + }); + + // Focus the panel through the workspace (matching existing test pattern) + workspace_a.update_in(cx, |workspace, window, cx| { + workspace.toggle_panel_focus::(window, cx); + }); + + // Zoom the panel + panel.update_in(cx, |panel, window, cx| { + panel.set_zoomed(true, window, cx); + }); + + // Verify the panel is zoomed and the dock is open + workspace_a.update_in(cx, |workspace, window, cx| { + assert!( + workspace.right_dock().read(cx).is_open(), + "dock should be open before switch" + ); + assert!( + panel.is_zoomed(window, cx), + "panel should be zoomed before switch" + ); + assert!( + panel.read(cx).focus_handle(cx).contains_focused(window, cx), + "panel should be focused before switch" + ); + }); + + // Switch to workspace B + multi_workspace_handle + .update(cx, |mw, window, cx| { + mw.activate_index(1, window, cx); + }) + .unwrap(); + cx.run_until_parked(); + + // Switch back to workspace A + multi_workspace_handle + .update(cx, |mw, window, cx| { + mw.activate_index(0, window, cx); + }) + .unwrap(); + cx.run_until_parked(); + + // Verify the panel is still zoomed and the dock is still open + workspace_a.update_in(cx, |workspace, window, cx| { + assert!( + workspace.right_dock().read(cx).is_open(), + "dock should still be open after switching back" + ); + assert!( + panel.is_zoomed(window, cx), + "panel should still be zoomed after switching back" + ); + }); + } + fn pane_items_paths(pane: &Entity, cx: &App) -> Vec { pane.read(cx) .items()