From 4e7980c42e65d6bf5c08dca6cfec2e675f0f3204 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:03:03 -0700 Subject: [PATCH 1/7] test: fence Cloud attachment presentation ownership --- .../WorkspaceRemoteReconnectPolicyTests.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift b/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift index 2b08b61a515e..fb44376c992d 100644 --- a/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift +++ b/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift @@ -106,6 +106,22 @@ struct WorkspaceRemoteReconnectPolicyTests { @Suite("Cloud terminal reconnect overlay policy") struct CloudTerminalReconnectOverlayPolicyTests { + @Test @MainActor + func nativeCloudAttachmentOwnsPresentationWhenCatalogProjectionIsMissing() throws { + let workspace = Workspace() + workspace.cloudVMBinding = WorkspaceCloudVMBinding( + vmID: "machine", + isBase: false + ) + let panelID = try #require(workspace.focusedPanelId) + let panel = try #require(workspace.panels[panelID] as? TerminalPanel) + let status = CloudTerminalAttachmentStatus(machineID: "machine") + panel.cloudAttachment = status + workspace.remoteConnectionState = .reconnecting + workspace.remoteConnectionDetail = nil + #expect(workspace.cloudTerminalReconnectOverlayPresentation(forSurfaceId: panelID) == nil) + } + @Test("Cloud terminal surfaces show reconnect UI when disconnected") func cloudTerminalShowsReconnectWhenDisconnected() { let presentation = CloudTerminalReconnectOverlayPolicy.presentation( From 33a93af41278411e25dc433dc19ff8710a32cf89 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:11:10 -0700 Subject: [PATCH 2/7] fix: make Cloud pane attachment own reconnect presentation --- .../Cloud/CloudTerminalAttachmentStatus.swift | 15 +++++++++++++-- Sources/Cloud/CloudTuiManualMirrorSession.swift | 15 ++++++++++++++- Sources/Workspace.swift | 7 +++++++ .../WorkspaceRemoteReconnectPolicyTests.swift | 17 +++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Sources/Cloud/CloudTerminalAttachmentStatus.swift b/Sources/Cloud/CloudTerminalAttachmentStatus.swift index 7550ded31b62..028e12f8a0d1 100644 --- a/Sources/Cloud/CloudTerminalAttachmentStatus.swift +++ b/Sources/Cloud/CloudTerminalAttachmentStatus.swift @@ -11,13 +11,24 @@ import Observation final class CloudTerminalAttachmentStatus { let machineID: String private(set) var state: CloudTerminalAttachmentState = .attaching(attempt: 1) + /// The overlay snapshot owned by the same attachment as ``state``. + /// + /// A native Cloud panel must not fall back to workspace-wide controller + /// state when its catalog projection is temporarily unavailable. Keeping + /// the presentation beside the attachment state makes that ownership + /// explicit and survives catalog refreshes and pane moves. + private(set) var presentation: CloudTerminalReconnectOverlayPolicy.Presentation? init(machineID: String) { self.machineID = machineID } - func update(_ state: CloudTerminalAttachmentState) { - guard self.state != state else { return } + func update( + _ state: CloudTerminalAttachmentState, + presentation: CloudTerminalReconnectOverlayPolicy.Presentation? + ) { + guard self.state != state || self.presentation != presentation else { return } self.state = state + self.presentation = presentation } } diff --git a/Sources/Cloud/CloudTuiManualMirrorSession.swift b/Sources/Cloud/CloudTuiManualMirrorSession.swift index 133b70de033c..6e8f1c591cfb 100644 --- a/Sources/Cloud/CloudTuiManualMirrorSession.swift +++ b/Sources/Cloud/CloudTuiManualMirrorSession.swift @@ -171,9 +171,11 @@ final class CloudTuiManualMirrorSession { return self.phase == .attached && self.diagnosticReplayReceived }, onReady: { [weak self] in self?.startupTrace?.mark("first-visible-frame", surfaceID: self?.remoteSurfaceID, outcome: "ready") + self?.publishAttachmentPresentation() self?.surface?.owningWorkspace()?.postRemoteConnectionPresentationDidChange() }, onEnded: { [weak self] in self?.clearStartupLoading() + self?.publishAttachmentPresentation() self?.surface?.owningWorkspace()?.postRemoteConnectionPresentationDidChange() }, onTimedOut: { [weak self] in self?.transitionToDisconnected(reason: .livenessTimedOut) @@ -181,6 +183,7 @@ final class CloudTuiManualMirrorSession { startupTrace?.mark("surface-bound", surfaceID: remoteSurfaceID) surface.flushPendingManualSizeReportIfAttached() runtimeReady() + publishAttachmentPresentation() } func visibilityChanged(_ visible: Bool) { @@ -436,6 +439,7 @@ final class CloudTuiManualMirrorSession { diagnosticDeadline?.cancel() diagnosticDeadline = nil if let error, !(error is CancellationError) { diagnosticFailure = .classify(error) } + publishAttachmentPresentation() surface?.owningWorkspace()?.postRemoteConnectionPresentationDidChange() let context = diagnosticContext ?? (error != nil && !(error is CancellationError) ? operations?.begin(.terminal, foreground: false) : nil) guard let context else { return } @@ -590,7 +594,16 @@ final class CloudTuiManualMirrorSession { attachAttempts = 0 } log.phase(machineID: machineID, terminalID: terminalID, surfaceID: remoteSurfaceID, phase: next, reason: reason) - attachmentStatus.update(attachmentState) + publishAttachmentPresentation() + } + + /// Publishes the one presentation snapshot for this pane's attachment. + /// Workspace-wide remote controller state is intentionally excluded. + private func publishAttachmentPresentation() { + attachmentStatus.update( + attachmentState, + presentation: connectionPresentation + ) } private var attachmentState: CloudTerminalAttachmentState { diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index b51e7724d28e..17f076660a0c 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -7526,6 +7526,13 @@ final class Workspace: Identifiable, ObservableObject, FilePreviewTabMetadataHos reference: failure.reference ) } + // A native Cloud pane owns its presentation through the attachment + // session. Do not let a stale workspace controller state cover a + // usable terminal when the catalog projection is being refreshed. + if let panel = panels[surfaceId] as? TerminalPanel, + let attachment = panel.cloudAttachment { + return attachment.presentation + } if let resource = cloudProjectedResource(forPanel: surfaceId), let machineID = resource.id.machine.cloudMachineID, let session = CmuxTuiSurfaceProviderRegistry.shared.provider(machineID: machineID)?.manualMirrorSessions[surfaceId] { diff --git a/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift b/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift index fb44376c992d..fc755412f542 100644 --- a/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift +++ b/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift @@ -120,6 +120,23 @@ struct CloudTerminalReconnectOverlayPolicyTests { workspace.remoteConnectionState = .reconnecting workspace.remoteConnectionDetail = nil #expect(workspace.cloudTerminalReconnectOverlayPresentation(forSurfaceId: panelID) == nil) + + let nativePresentation = try #require( + CloudTerminalReconnectOverlayPolicy.presentation( + isManagedCloudWorkspace: true, + isRemoteTerminalSurface: true, + connectionState: .disconnected, + detail: "the terminal attachment ended" + ) + ) + status.update( + .reconnecting(attempt: 2, reason: .transportClosed), + presentation: nativePresentation + ) + + #expect( + workspace.cloudTerminalReconnectOverlayPresentation(forSurfaceId: panelID) == nativePresentation + ) } @Test("Cloud terminal surfaces show reconnect UI when disconnected") From 0624a0f4c18e17c9bb13b07010be495a9056306c Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:16:06 -0700 Subject: [PATCH 3/7] fix: use one Cloud terminal reconnect presentation --- .../CloudTerminalStartupLoadingView.swift | 26 ------------- Sources/CloudTerminalOverlayCoordinator.swift | 5 ++- .../CloudTerminalAttachmentBanner.swift | 38 +++---------------- Sources/Panels/TerminalPanelView.swift | 6 --- cmux.xcodeproj/project.pbxproj | 4 -- 5 files changed, 10 insertions(+), 69 deletions(-) delete mode 100644 Sources/Cloud/CloudTerminalStartupLoadingView.swift diff --git a/Sources/Cloud/CloudTerminalStartupLoadingView.swift b/Sources/Cloud/CloudTerminalStartupLoadingView.swift deleted file mode 100644 index 34bb9b8fba4d..000000000000 --- a/Sources/Cloud/CloudTerminalStartupLoadingView.swift +++ /dev/null @@ -1,26 +0,0 @@ -import SwiftUI - -/// Covers a newly created Cloud terminal until its first visible frame. -struct CloudTerminalStartupLoadingView: View { - let readiness: CloudTerminalReadiness - - var body: some View { - if readiness.isLoading { - VStack(spacing: 10) { - ProgressView() - .controlSize(.small) - Text(String( - localized: "cloud.terminal.loading", - defaultValue: "Connecting to Cloud terminal…" - )) - .cmuxFont(size: 13, weight: .semibold) - .foregroundStyle(.primary) - } - .padding(.horizontal, 18) - .padding(.vertical, 14) - .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .allowsHitTesting(false) - } - } -} diff --git a/Sources/CloudTerminalOverlayCoordinator.swift b/Sources/CloudTerminalOverlayCoordinator.swift index b3083b77e6de..5861ec7e002c 100644 --- a/Sources/CloudTerminalOverlayCoordinator.swift +++ b/Sources/CloudTerminalOverlayCoordinator.swift @@ -72,7 +72,10 @@ final class CloudTerminalOverlayCoordinator { } let presentation: CloudTerminalReconnectOverlayPolicy.Presentation? if let session { - presentation = session.connectionPresentation + // The session publishes one immutable presentation snapshot for + // every phase/readiness change. The native card and workspace + // fallback both consume that same snapshot. + presentation = session.attachmentStatus.presentation } else { presentation = legacyPresentation } diff --git a/Sources/Panels/CloudTerminalAttachmentBanner.swift b/Sources/Panels/CloudTerminalAttachmentBanner.swift index 714ff8da7119..3eb60515a4be 100644 --- a/Sources/Panels/CloudTerminalAttachmentBanner.swift +++ b/Sources/Panels/CloudTerminalAttachmentBanner.swift @@ -1,37 +1,11 @@ -import SwiftUI +import Foundation -/// A small pill over a native cloud pane while its attachment is not live. +/// Text compatibility helper for attachment diagnostics. /// -/// The last screen stays visible underneath; the banner only says what the -/// session is doing about it. It waits a moment before appearing so the normal -/// sub-second attach never flashes it. -struct CloudTerminalAttachmentBanner: View { - let status: CloudTerminalAttachmentStatus? - @State private var visible = false - - var body: some View { - if let status, let text = Self.text(for: status.state, machineID: status.machineID) { - HStack(spacing: 6) { - ProgressView() - .controlSize(.mini) - Text(text) - .font(.system(size: 11)) - .lineLimit(2) - } - .padding(.horizontal, 10) - .padding(.vertical, 5) - .background(.regularMaterial, in: Capsule()) - .padding(.top, 8) - .opacity(visible ? 1 : 0) - .animation(.easeIn(duration: 0.15).delay(1), value: visible) - .allowsHitTesting(false) - .accessibilityIdentifier("cloud-attachment-banner") - // Visual timing only; removing the banner cancels its presentation. - .onAppear { visible = true } - .onDisappear { visible = false } - } - } - +/// The reconnect UI is rendered only by ``CloudTerminalOverlayCoordinator``. +/// Keeping this pure helper preserves localized diagnostic coverage without +/// creating a second SwiftUI presentation surface. +enum CloudTerminalAttachmentBanner { static func text(for state: CloudTerminalAttachmentState, machineID: String) -> String? { switch state { case .attached, .ended: diff --git a/Sources/Panels/TerminalPanelView.swift b/Sources/Panels/TerminalPanelView.swift index 2c951b8049e1..ce995335111f 100644 --- a/Sources/Panels/TerminalPanelView.swift +++ b/Sources/Panels/TerminalPanelView.swift @@ -122,12 +122,6 @@ struct TerminalPanelView: View { .id(panel.id) .background(Color.clear) .frame(maxWidth: .infinity, maxHeight: .infinity) - .overlay(alignment: .top) { - CloudTerminalAttachmentBanner(status: panel.cloudAttachment) - } - .overlay { - CloudTerminalStartupLoadingView(readiness: panel.cloudStartupReadiness) - } #if DEBUG .reportTerminalViewportGeometryForUITest(panel: panel) #endif diff --git a/cmux.xcodeproj/project.pbxproj b/cmux.xcodeproj/project.pbxproj index f4a34f9319a1..c31151c59650 100644 --- a/cmux.xcodeproj/project.pbxproj +++ b/cmux.xcodeproj/project.pbxproj @@ -823,7 +823,6 @@ C12469AA0000000000000038 /* CloudTerminalReadinessGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12469AA0000000000000039 /* CloudTerminalReadinessGate.swift */; }; C12469AA0000000042 /* CloudTerminalReadinessPhase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12469AA0000000043 /* CloudTerminalReadinessPhase.swift */; }; 4AF621B6A97C434F9DC5A035 /* CloudTerminalReconnectOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030176C8103741F8BDD27F26 /* CloudTerminalReconnectOverlayView.swift */; }; - C12469AA0000000000000025 /* CloudTerminalStartupLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12469AA0000000000000026 /* CloudTerminalStartupLoadingView.swift */; }; C12469AA0000000000000036 /* CloudTerminalStartupTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12469AA0000000000000037 /* CloudTerminalStartupTrace.swift */; }; 8E2A2A4AACE0400798CBFE81 /* CloudToolPaneTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31EDA1AEA6A437B8E75A489 /* CloudToolPaneTests.swift */; }; C986A0080000000000000001 /* CloudTreeActiveDrag.swift in Sources */ = {isa = PBXBuildFile; fileRef = C986A0070000000000000001 /* CloudTreeActiveDrag.swift */; }; @@ -4650,7 +4649,6 @@ C12469AA0000000000000039 /* CloudTerminalReadinessGate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudTerminalReadinessGate.swift; sourceTree = ""; }; C12469AA0000000043 /* CloudTerminalReadinessPhase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudTerminalReadinessPhase.swift; sourceTree = ""; }; 030176C8103741F8BDD27F26 /* CloudTerminalReconnectOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudTerminalReconnectOverlayView.swift; sourceTree = ""; }; - C12469AA0000000000000026 /* CloudTerminalStartupLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudTerminalStartupLoadingView.swift; sourceTree = ""; }; C12469AA0000000000000037 /* CloudTerminalStartupTrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudTerminalStartupTrace.swift; sourceTree = ""; }; F31EDA1AEA6A437B8E75A489 /* CloudToolPaneTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudToolPaneTests.swift; sourceTree = ""; }; C986A0070000000000000001 /* CloudTreeActiveDrag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudTreeActiveDrag.swift; sourceTree = ""; }; @@ -8137,7 +8135,6 @@ C12469AA0000000000000014 /* CloudTuiCreationResolution.swift */, C12469AA0000000000000018 /* CloudTreeNodeActions+Destination.swift */, C12469AA0000000000000024 /* CloudTerminalReadiness.swift */, - C12469AA0000000000000026 /* CloudTerminalStartupLoadingView.swift */, C12469AA0000000000000035 /* CloudTuiManualMirrorSession+StartupPresentation.swift */, C12469AA0000000000000037 /* CloudTerminalStartupTrace.swift */, C12469AA0000000000000039 /* CloudTerminalReadinessGate.swift */, @@ -12458,7 +12455,6 @@ C12469AA0000000000000038 /* CloudTerminalReadinessGate.swift in Sources */, C12469AA0000000042 /* CloudTerminalReadinessPhase.swift in Sources */, 4AF621B6A97C434F9DC5A035 /* CloudTerminalReconnectOverlayView.swift in Sources */, - C12469AA0000000000000025 /* CloudTerminalStartupLoadingView.swift in Sources */, C12469AA0000000000000036 /* CloudTerminalStartupTrace.swift in Sources */, C986A0080000000000000001 /* CloudTreeActiveDrag.swift in Sources */, 3BCFA4F10BC6F6673CFA3BFC /* CloudTreeCellView.swift in Sources */, From 22c7068f15eb34814cbf1b74b107c067174eefb5 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:16:30 -0700 Subject: [PATCH 4/7] fix: reconcile Cloud overlay after attachment state publish --- Sources/Cloud/CloudTuiManualMirrorSession.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/Cloud/CloudTuiManualMirrorSession.swift b/Sources/Cloud/CloudTuiManualMirrorSession.swift index 6e8f1c591cfb..1cea0339c554 100644 --- a/Sources/Cloud/CloudTuiManualMirrorSession.swift +++ b/Sources/Cloud/CloudTuiManualMirrorSession.swift @@ -604,6 +604,10 @@ final class CloudTuiManualMirrorSession { attachmentState, presentation: connectionPresentation ) + // The status snapshot is the presentation source for both the portal + // card and the workspace bridge. Reconcile after publishing so a + // phase didSet callback cannot leave the card on the previous phase. + surface?.hostedView.synchronizeCloudTerminalReconnectOverlay() } private var attachmentState: CloudTerminalAttachmentState { From 73b8e2510274cb383f1712fe6044e5a9f585cde3 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:18:22 -0700 Subject: [PATCH 5/7] fix: prioritize native Cloud attachment ownership --- Sources/Workspace.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 17f076660a0c..3edc7b739078 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -7520,12 +7520,6 @@ final class Workspace: Identifiable, ObservableObject, FilePreviewTabMetadataHos } func cloudTerminalReconnectOverlayPresentation(forSurfaceId surfaceId: UUID) -> CloudTerminalReconnectOverlayPolicy.Presentation? { - if let failure = cloudMaterializationFailures[surfaceId] { - return Self.cloudMaterializationFailurePresentation( - detail: failure.detail, - reference: failure.reference - ) - } // A native Cloud pane owns its presentation through the attachment // session. Do not let a stale workspace controller state cover a // usable terminal when the catalog projection is being refreshed. @@ -7533,6 +7527,12 @@ final class Workspace: Identifiable, ObservableObject, FilePreviewTabMetadataHos let attachment = panel.cloudAttachment { return attachment.presentation } + if let failure = cloudMaterializationFailures[surfaceId] { + return Self.cloudMaterializationFailurePresentation( + detail: failure.detail, + reference: failure.reference + ) + } if let resource = cloudProjectedResource(forPanel: surfaceId), let machineID = resource.id.machine.cloudMachineID, let session = CmuxTuiSurfaceProviderRegistry.shared.provider(machineID: machineID)?.manualMirrorSessions[surfaceId] { From b73d223b368230d61c1f5bc10186db59afbc776c Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:21:27 -0700 Subject: [PATCH 6/7] fix: derive Cloud card state per terminal --- Sources/Workspace.swift | 20 +++++++++++++- .../WorkspaceRemoteReconnectPolicyTests.swift | 27 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 3edc7b739078..7793196c7ec0 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -7538,10 +7538,28 @@ final class Workspace: Identifiable, ObservableObject, FilePreviewTabMetadataHos let session = CmuxTuiSurfaceProviderRegistry.shared.provider(machineID: machineID)?.manualMirrorSessions[surfaceId] { return session.connectionPresentation } + let surfaceConnectionState: WorkspaceRemoteConnectionState + switch remoteTerminalSessionStatesBySurfaceId[surfaceId]?.phase { + case .some(.connected): + // A workspace controller can reconnect while this terminal's + // established PTY remains usable. Its per-surface liveness owns + // the card, so the controller state cannot cover this pane. + return nil + case .some(.launching): + // A workspace can be connected through another pane. Keep this + // panel's loading card until its own attach callback arrives. + surfaceConnectionState = remoteConnectionState == .connected + ? .connecting + : remoteConnectionState + case .some(.ended): + surfaceConnectionState = .disconnected + case .none: + surfaceConnectionState = remoteConnectionState + } return CloudTerminalReconnectOverlayPolicy.presentation( isManagedCloudWorkspace: isManagedCloudVMWorkspace, isRemoteTerminalSurface: isRemoteTerminalSurface(surfaceId) || remoteDisconnectPlaceholderPanelIds.contains(surfaceId), - connectionState: remoteConnectionState, + connectionState: surfaceConnectionState, detail: remoteConnectionDetail ) } diff --git a/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift b/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift index fc755412f542..d72b47663bd9 100644 --- a/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift +++ b/cmuxTests/WorkspaceRemoteReconnectPolicyTests.swift @@ -106,6 +106,33 @@ struct WorkspaceRemoteReconnectPolicyTests { @Suite("Cloud terminal reconnect overlay policy") struct CloudTerminalReconnectOverlayPolicyTests { + @Test @MainActor + func connectedLegacyCloudSurfaceSuppressesWorkspaceReconnectCard() throws { + let workspace = Workspace() + let panel = try #require(workspace.focusedTerminalPanel) + let configuration = WorkspaceRemoteConfiguration( + destination: "cloud VM", + port: 22, + identityFile: nil, + sshOptions: [], + localProxyPort: nil, + relayPort: 64_015, + relayID: String(repeating: "a", count: 16), + relayToken: String(repeating: "b", count: 64), + localSocketPath: "/tmp/cmux-debug-test.sock", + managedCloudVMID: "machine", + terminalStartupCommand: "cmux vm ssh-attach --id machine" + ) + workspace.configureRemoteConnection(configuration, autoConnect: false) + #expect(workspace.markRemoteTerminalSessionConnected( + surfaceId: panel.id, + relayPort: configuration.relayPort + )) + workspace.remoteConnectionState = .reconnecting + + #expect(workspace.cloudTerminalReconnectOverlayPresentation(forSurfaceId: panel.id) == nil) + } + @Test @MainActor func nativeCloudAttachmentOwnsPresentationWhenCatalogProjectionIsMissing() throws { let workspace = Workspace() From c30a22e7a7d0683ee812a1c37e6f8c5226cbbe49 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:24:27 -0700 Subject: [PATCH 7/7] fix: remove catalog lookup from Cloud presentation --- Sources/Workspace.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 7793196c7ec0..65e969e868b8 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -7533,11 +7533,6 @@ final class Workspace: Identifiable, ObservableObject, FilePreviewTabMetadataHos reference: failure.reference ) } - if let resource = cloudProjectedResource(forPanel: surfaceId), - let machineID = resource.id.machine.cloudMachineID, - let session = CmuxTuiSurfaceProviderRegistry.shared.provider(machineID: machineID)?.manualMirrorSessions[surfaceId] { - return session.connectionPresentation - } let surfaceConnectionState: WorkspaceRemoteConnectionState switch remoteTerminalSessionStatesBySurfaceId[surfaceId]?.phase { case .some(.connected):