diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 3f192ca9..d2d4fded 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -27684,6 +27684,23 @@ } } }, + "dialog.closeTab.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, "dialog.closeTab.message": { "extractionState": "manual", "localizations": { diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 68a8ecc9..bce7a8d5 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -9758,11 +9758,6 @@ private struct TabItemView: View, Equatable { private var remoteWorkspaceSection: some View { if sidebarShowSSH, let remoteWorkspaceSidebarText { VStack(alignment: .leading, spacing: 2) { - Text(String(localized: "sidebar.remote.badge", defaultValue: "SSH")) - .font(.system(size: 9, weight: .semibold)) - .foregroundColor(activeSecondaryColor(0.62)) - .textCase(.uppercase) - HStack(spacing: 6) { Text(remoteWorkspaceSidebarText) .font(.system(size: 10, design: .monospaced)) diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 26eaf4ed..c3497281 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -1506,8 +1506,8 @@ class TabManager: ObservableObject { alert.messageText = title alert.informativeText = message alert.alertStyle = .warning - alert.addButton(withTitle: "Close") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "dialog.closeTab.close", defaultValue: "Close")) + alert.addButton(withTitle: String(localized: "dialog.closeTab.cancel", defaultValue: "Cancel")) // macOS convention: Cmd+D = confirm destructive close (e.g. "Don't Save"). // We only opt into this for the "close last workspace => close window" path to avoid diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 8cc85778..f8c78e22 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -3117,7 +3117,7 @@ class TerminalController { guard let windowId = v2UUID(params, "window_id") else { return .err(code: "invalid_params", message: "Missing or invalid window_id", data: nil) } - let focus = v2Bool(params, "focus") ?? true + let focus = v2FocusAllowed(requested: v2Bool(params, "focus") ?? false) var result: V2CallResult = .err(code: "internal_error", message: "Failed to move workspace", data: nil) v2MainSync { @@ -4149,14 +4149,6 @@ class TerminalController { result = .err(code: "not_found", message: "Workspace not found", data: nil) return } - if let windowId = v2ResolveWindowId(tabManager: tabManager) { - _ = AppDelegate.shared?.focusMainWindow(windowId: windowId) - setActiveTabManager(tabManager) - } - if tabManager.selectedTabId != ws.id { - tabManager.selectWorkspace(ws) - } - let targetSurfaceId: UUID? = v2UUID(params, "surface_id") ?? ws.focusedPanelId guard let targetSurfaceId else { result = .err(code: "not_found", message: "No focused surface", data: nil) @@ -4167,6 +4159,9 @@ class TerminalController { return } + v2MaybeFocusWindow(for: tabManager) + v2MaybeSelectWorkspace(tabManager, workspace: ws) + if let newId = tabManager.newSplit(tabId: ws.id, surfaceId: targetSurfaceId, direction: direction) { let paneUUID = ws.paneId(forPanelId: newId)?.id let windowId = v2ResolveWindowId(tabManager: tabManager) @@ -4340,7 +4335,7 @@ class TerminalController { let beforeSurfaceId = v2UUID(params, "before_surface_id") let afterSurfaceId = v2UUID(params, "after_surface_id") let explicitIndex = v2Int(params, "index") - let focus = v2Bool(params, "focus") ?? true + let focus = v2FocusAllowed(requested: v2Bool(params, "focus") ?? false) let anchorCount = (beforeSurfaceId != nil ? 1 : 0) + (afterSurfaceId != nil ? 1 : 0) if anchorCount > 1 { @@ -4451,7 +4446,7 @@ class TerminalController { ?? sourceWorkspace.bonsplitController.focusedPaneId ?? sourceWorkspace.bonsplitController.allPaneIds.first if let rollbackPane { - _ = sourceWorkspace.attachDetachedSurface(transfer, inPane: rollbackPane, atIndex: sourceIndex, focus: true) + _ = sourceWorkspace.attachDetachedSurface(transfer, inPane: rollbackPane, atIndex: sourceIndex, focus: focus) } result = .err(code: "internal_error", message: "Failed to attach surface to destination", data: nil) return @@ -4919,15 +4914,6 @@ class TerminalController { return } - // Ensure the flash is visible in the active UI. - if let windowId = v2ResolveWindowId(tabManager: tabManager) { - _ = AppDelegate.shared?.focusMainWindow(windowId: windowId) - setActiveTabManager(tabManager) - } - if tabManager.selectedTabId != ws.id { - tabManager.selectWorkspace(ws) - } - let surfaceId = v2UUID(params, "surface_id") ?? ws.focusedPanelId guard let surfaceId else { result = .err(code: "not_found", message: "No focused surface", data: nil) @@ -4938,6 +4924,9 @@ class TerminalController { return } + v2MaybeFocusWindow(for: tabManager) + v2MaybeSelectWorkspace(tabManager, workspace: ws) + ws.triggerFocusFlash(panelId: surfaceId) result = .ok(["workspace_id": ws.id.uuidString, "workspace_ref": v2Ref(kind: .workspace, uuid: ws.id), "surface_id": surfaceId.uuidString, "surface_ref": v2Ref(kind: .surface, uuid: surfaceId), "window_id": v2OrNull(v2ResolveWindowId(tabManager: tabManager)?.uuidString), "window_ref": v2Ref(kind: .window, uuid: v2ResolveWindowId(tabManager: tabManager))]) } @@ -11017,6 +11006,7 @@ class TerminalController { guard let windowId = UUID(uuidString: parts[1]) else { return "ERROR: Invalid window id" } var ok = false + let focus = socketCommandAllowsInAppFocusMutations() v2MainSync { guard let srcTM = AppDelegate.shared?.tabManagerFor(tabId: wsId), let dstTM = AppDelegate.shared?.tabManagerFor(windowId: windowId), @@ -11024,9 +11014,11 @@ class TerminalController { ok = false return } - dstTM.attachWorkspace(ws, select: true) - _ = AppDelegate.shared?.focusMainWindow(windowId: windowId) - setActiveTabManager(dstTM) + dstTM.attachWorkspace(ws, select: focus) + if focus { + _ = AppDelegate.shared?.focusMainWindow(windowId: windowId) + setActiveTabManager(dstTM) + } ok = true } diff --git a/cmuxTests/TerminalControllerSocketSecurityTests.swift b/cmuxTests/TerminalControllerSocketSecurityTests.swift index 48127765..5d94e6c3 100644 --- a/cmuxTests/TerminalControllerSocketSecurityTests.swift +++ b/cmuxTests/TerminalControllerSocketSecurityTests.swift @@ -102,6 +102,20 @@ final class TerminalControllerSocketSecurityTests: XCTestCase { XCTAssertTrue(focusV2.insideSuppressed) XCTAssertTrue(focusV2.insideAllowsFocus) XCTAssertFalse(focusV2.outsideSuppressed) + + let moveWorkspace = TerminalController.debugSocketCommandPolicySnapshot( + commandKey: "workspace.move_to_window", + isV2: true + ) + XCTAssertTrue(moveWorkspace.insideSuppressed) + XCTAssertFalse(moveWorkspace.insideAllowsFocus) + + let triggerFlash = TerminalController.debugSocketCommandPolicySnapshot( + commandKey: "surface.trigger_flash", + isV2: true + ) + XCTAssertTrue(triggerFlash.insideSuppressed) + XCTAssertFalse(triggerFlash.insideAllowsFocus) #else throw XCTSkip("Socket command policy snapshot helper is debug-only.") #endif