From 8c5208cd1e7362bfb9eb2899f9d3c10d7270e0a8 Mon Sep 17 00:00:00 2001 From: akbash-bot <300245827+akbash-bot@users.noreply.github.com> Date: Tue, 8 Sep 2026 03:02:05 +0000 Subject: [PATCH 1/2] fix: preserve focused viewer geometry during activation refs #3744 --- src/server/headless/surface_interest.rs | 8 +- src/server/headless/tests/surface_interest.rs | 136 ++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/server/headless/surface_interest.rs b/src/server/headless/surface_interest.rs index 7270be0d63..46cf40c077 100644 --- a/src/server/headless/surface_interest.rs +++ b/src/server/headless/surface_interest.rs @@ -69,7 +69,13 @@ impl HeadlessServer { // explicitly requests the bounded replay after its coherent frame is visible. self.sent_window_title = None; self.resize_shared_runtime_to_effective_size_with_pending_agent_resumes(true); - self.claim_shell_tab_geometry(client_id, true); + let focused_viewer_already_owns_tab = changed + && self + .shell_tab_id_for_client(client_id) + .is_some_and(|tab_id| focused_tabs_before.contains(&tab_id)); + if !focused_viewer_already_owns_tab { + self.claim_shell_tab_geometry(client_id, true); + } } else { self.tab_geometry_controllers .retain(|_, controller_id| *controller_id != client_id); diff --git a/src/server/headless/tests/surface_interest.rs b/src/server/headless/tests/surface_interest.rs index 591f6a8f1c..f851e423d2 100644 --- a/src/server/headless/tests/surface_interest.rs +++ b/src/server/headless/tests/surface_interest.rs @@ -243,6 +243,142 @@ async fn metadata_only_shell_is_isolated_until_surface_activation() { shutdown_test_runtimes(&mut server); } +#[tokio::test] +async fn background_surface_activation_preserves_focused_viewer_geometry() { + let mut server = test_headless_server(); + let pane_id = install_shared_view_test_runtime(&mut server); + let (focused_control, _) = connect_test_shell(&mut server, 7, 68, 17); + let _ = focused_control.recv().expect("focused client snapshot"); + assert!(server.handle_server_event(ServerEvent::ClientShellFocus { + client_id: 7, + focused: true, + })); + let focused_size = server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(); + assert_eq!(focused_size, (17, 67)); + let shared_tab_id = server.shell_tab_id_for_client(7).expect("focused tab"); + assert_eq!( + server.tab_geometry_controllers.get(&shared_tab_id), + Some(&7) + ); + + let (writer, background_control, _) = test_client_writer(); + assert!( + server.handle_server_event(ServerEvent::ClientShellConnected { + client_id: 8, + surface_cols: 100, + surface_rows: 35, + cell_width_px: 0, + cell_height_px: 0, + pixel_mouse: false, + direct_graphics: false, + endpoint_keybindings: false, + mouse_capture: false, + surface_active: false, + writer, + }) + ); + let _ = background_control + .recv() + .expect("background client snapshot"); + + let boot_id = server.client_shell_boot_id.clone(); + assert!( + server.handle_server_event(ServerEvent::ClientShellEndpointRequest { + client_id: 8, + boot_id, + request: Box::new(api::schema::Request { + id: "activate-background-surface".into(), + method: api::schema::Method::ClientShellSurfaceSet( + api::schema::ClientShellSurfaceSetParams { active: true }, + ), + }), + }) + ); + let _ = background_control + .recv() + .expect("background surface activation response"); + assert_eq!( + server.shell_tab_id_for_client(8).as_deref(), + Some(shared_tab_id.as_str()) + ); + assert_eq!(server.clients[&7].outer_terminal_focus, Some(true)); + assert_eq!(server.clients[&8].outer_terminal_focus, None); + assert_eq!( + server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(), + focused_size, + "surface activation must not transiently resize a focused viewer's tab" + ); + assert_eq!( + server.tab_geometry_controllers.get(&shared_tab_id), + Some(&7) + ); + + assert!(server.handle_server_event(ServerEvent::ClientShellFocus { + client_id: 8, + focused: false, + })); + assert_eq!(server.clients[&7].outer_terminal_focus, Some(true)); + assert_eq!(server.clients[&8].outer_terminal_focus, Some(false)); + assert_eq!( + server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(), + focused_size + ); + assert_eq!( + server.tab_geometry_controllers.get(&shared_tab_id), + Some(&7) + ); + shutdown_test_runtimes(&mut server); +} + +#[tokio::test] +async fn focused_surface_reassertion_reclaims_tab_geometry() { + let mut server = test_headless_server(); + let pane_id = install_shared_view_test_runtime(&mut server); + let (focused_control, _) = connect_test_shell(&mut server, 8, 100, 35); + let _ = focused_control.recv().expect("focused client snapshot"); + assert!(server.handle_server_event(ServerEvent::ClientShellFocus { + client_id: 8, + focused: true, + })); + let shared_tab_id = server.shell_tab_id_for_client(8).expect("focused tab"); + + let (other_control, _) = connect_test_shell(&mut server, 7, 68, 17); + let _ = other_control.recv().expect("other client snapshot"); + assert!(server.claim_shell_tab_geometry(7, false)); + assert_eq!( + server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(), + (17, 67) + ); + + let boot_id = server.client_shell_boot_id.clone(); + assert!( + server.handle_server_event(ServerEvent::ClientShellEndpointRequest { + client_id: 8, + boot_id, + request: Box::new(api::schema::Request { + id: "reassert-focused-surface".into(), + method: api::schema::Method::ClientShellSurfaceSet( + api::schema::ClientShellSurfaceSetParams { active: true }, + ), + }), + }) + ); + let _ = focused_control + .recv() + .expect("focused surface reassertion response"); + + assert_eq!(server.clients[&8].outer_terminal_focus, Some(true)); + assert_eq!( + server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(), + (35, 99) + ); + assert_eq!( + server.tab_geometry_controllers.get(&shared_tab_id), + Some(&8) + ); + shutdown_test_runtimes(&mut server); +} + #[tokio::test] async fn presentation_sync_epoch_replays_modes_and_title() { let mut server = test_headless_server(); From c2d200eda162c58d021fd96114831f57b72593d7 Mon Sep 17 00:00:00 2001 From: akbash-bot <300245827+akbash-bot@users.noreply.github.com> Date: Tue, 8 Sep 2026 03:15:42 +0000 Subject: [PATCH 2/2] fix: preserve geometry through activation sync refs #3744 --- src/server/headless/surface_interest.rs | 15 +++-- src/server/headless/tests/surface_interest.rs | 57 ++++++++++--------- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/server/headless/surface_interest.rs b/src/server/headless/surface_interest.rs index 46cf40c077..9772fc034a 100644 --- a/src/server/headless/surface_interest.rs +++ b/src/server/headless/surface_interest.rs @@ -69,10 +69,17 @@ impl HeadlessServer { // explicitly requests the bounded replay after its coherent frame is visible. self.sent_window_title = None; self.resize_shared_runtime_to_effective_size_with_pending_agent_resumes(true); - let focused_viewer_already_owns_tab = changed - && self - .shell_tab_id_for_client(client_id) - .is_some_and(|tab_id| focused_tabs_before.contains(&tab_id)); + let focused_viewer_already_owns_tab = self + .shell_tab_id_for_client(client_id) + .is_some_and(|tab_id| { + self.clients.iter().any(|(&other_id, client)| { + other_id != client_id + && client.is_active_shell_client() + && client.outer_terminal_focus == Some(true) + && self.shell_tab_id_for_client(other_id).as_deref() + == Some(tab_id.as_str()) + }) + }); if !focused_viewer_already_owns_tab { self.claim_shell_tab_geometry(client_id, true); } diff --git a/src/server/headless/tests/surface_interest.rs b/src/server/headless/tests/surface_interest.rs index f851e423d2..df06dc03ad 100644 --- a/src/server/headless/tests/surface_interest.rs +++ b/src/server/headless/tests/surface_interest.rs @@ -36,6 +36,22 @@ fn lifecycle_resize() -> crate::protocol::ClientMessage { } } +fn request_active_surface(server: &mut HeadlessServer, client_id: u64, request_id: &str) { + let boot_id = server.client_shell_boot_id.clone(); + assert!( + server.handle_server_event(ServerEvent::ClientShellEndpointRequest { + client_id, + boot_id, + request: Box::new(api::schema::Request { + id: request_id.into(), + method: api::schema::Method::ClientShellSurfaceSet( + api::schema::ClientShellSurfaceSetParams { active: true }, + ), + }), + }) + ); +} + #[tokio::test] async fn metadata_only_shell_is_isolated_until_surface_activation() { let mut server = test_headless_server(); @@ -281,19 +297,7 @@ async fn background_surface_activation_preserves_focused_viewer_geometry() { .recv() .expect("background client snapshot"); - let boot_id = server.client_shell_boot_id.clone(); - assert!( - server.handle_server_event(ServerEvent::ClientShellEndpointRequest { - client_id: 8, - boot_id, - request: Box::new(api::schema::Request { - id: "activate-background-surface".into(), - method: api::schema::Method::ClientShellSurfaceSet( - api::schema::ClientShellSurfaceSetParams { active: true }, - ), - }), - }) - ); + request_active_surface(&mut server, 8, "activate-background-surface"); let _ = background_control .recv() .expect("background surface activation response"); @@ -327,6 +331,19 @@ async fn background_surface_activation_preserves_focused_viewer_geometry() { server.tab_geometry_controllers.get(&shared_tab_id), Some(&7) ); + + request_active_surface(&mut server, 8, "synchronize-background-surface"); + let _ = background_control + .recv() + .expect("background presentation synchronization response"); + assert_eq!( + server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(), + focused_size + ); + assert_eq!( + server.tab_geometry_controllers.get(&shared_tab_id), + Some(&7) + ); shutdown_test_runtimes(&mut server); } @@ -350,19 +367,7 @@ async fn focused_surface_reassertion_reclaims_tab_geometry() { (17, 67) ); - let boot_id = server.client_shell_boot_id.clone(); - assert!( - server.handle_server_event(ServerEvent::ClientShellEndpointRequest { - client_id: 8, - boot_id, - request: Box::new(api::schema::Request { - id: "reassert-focused-surface".into(), - method: api::schema::Method::ClientShellSurfaceSet( - api::schema::ClientShellSurfaceSetParams { active: true }, - ), - }), - }) - ); + request_active_surface(&mut server, 8, "reassert-focused-surface"); let _ = focused_control .recv() .expect("focused surface reassertion response");