From f867dabbe676255e75a512df343617bd4ca45d0d Mon Sep 17 00:00:00 2001 From: JJ Liebig Date: Mon, 14 Sep 2026 22:31:31 +0200 Subject: fix: reject stale pane surfaces after reconnect (#4141) --- src/client/shell/composition.rs | 8 +++++--- src/client/shell/tests/endpoints.rs | 2 ++ src/client/shell/tests/workspace_navigation.rs | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) (limited to 'src/client') diff --git a/src/client/shell/composition.rs b/src/client/shell/composition.rs index 0a577295..e0888d0d 100644 --- a/src/client/shell/composition.rs +++ b/src/client/shell/composition.rs @@ -147,9 +147,11 @@ impl ClientShellState { return Some(self.compose_unavailable(cols, rows)); } let snapshot = self.snapshot.as_deref()?; - // A one-step successor is retained separately until its exact snapshot arrives; do not - // keep composing the now-superseded current pair while it is pending. - if self.pending_pane_surface.is_some() { + // Do not compose a retained surface while waiting for its matching snapshot or + // connection generation. + if self.pending_pane_surface.is_some() + || self.pane_surface_generation != self.active_snapshot_generation + { return None; } let surface = self.pane_surface.as_ref()?; diff --git a/src/client/shell/tests/endpoints.rs b/src/client/shell/tests/endpoints.rs index 4ac8183c..b43b9a31 100644 --- a/src/client/shell/tests/endpoints.rs +++ b/src/client/shell/tests/endpoints.rs @@ -1559,6 +1559,7 @@ fn reconnect_same_endpoint_accepts_new_generation_surface_revision() { state.set_endpoint_status(&endpoint_id, ClientEndpointStatus::Online); assert!(state.activate_endpoint_projection(&endpoint_id)); + assert!(state.compose(106, 20).is_none()); let mut reconnected_surface = surface(); reconnected_surface.boot_id = "shared-server-boot".into(); reconnected_surface.projection_revision = 1; @@ -1570,6 +1571,7 @@ fn reconnect_same_endpoint_accepts_new_generation_surface_revision() { assert_eq!(state.pane_surface.as_ref().unwrap().surface_revision, 1); assert!(state.pending_pane_surface.is_none()); assert_eq!(state.agent_scroll, 7); + assert!(state.compose(106, 20).is_some()); } } diff --git a/src/client/shell/tests/workspace_navigation.rs b/src/client/shell/tests/workspace_navigation.rs index 2a100957..7ea02066 100644 --- a/src/client/shell/tests/workspace_navigation.rs +++ b/src/client/shell/tests/workspace_navigation.rs @@ -431,6 +431,7 @@ fn active_preview_is_not_retargeted_by_deletion_or_reboot() { 7, Box::new(local.clone()), ); + state.set_pane_surface(surface()); state.compose(100, 28).unwrap(); enter_navigation(&mut state); preview_key(&mut state, b"\x1b[B"); -- cgit v1.2.3