diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index bf968ea2..93bd41ac 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -1142,6 +1142,48 @@ class TabManager: ObservableObject { focusedBrowserPanel?.hideFind() } + func makeWorkspaceForCreation( + title: String, + workingDirectory: String?, + portOrdinal: Int, + configTemplate: ghostty_surface_config_s?, + initialTerminalCommand: String?, + initialTerminalEnvironment: [String: String] + ) -> Workspace { + Workspace( + title: title, + workingDirectory: workingDirectory, + portOrdinal: portOrdinal, + configTemplate: configTemplate, + initialTerminalCommand: initialTerminalCommand, + initialTerminalEnvironment: initialTerminalEnvironment + ) + } + +#if DEBUG + private func maybeMutateSelectionDuringWorkspaceCreationForDev( + snapshot: WorkspaceCreationSnapshot + ) { + let env = ProcessInfo.processInfo.environment + let isEnabled: Bool = { + if let raw = env["CMUX_DEV_MUTATE_WORKSPACE_SELECTION_DURING_CREATION"] { + return raw == "1" || raw.caseInsensitiveCompare("true") == .orderedSame + } + return UserDefaults.standard.bool(forKey: "cmuxDevMutateWorkspaceSelectionDuringCreation") + }() + guard isEnabled, + let selectedTabId = snapshot.selectedTabId, + let target = snapshot.tabs.first(where: { $0.id != selectedTabId }) else { + return + } + dlog( + "workspace.create.devSelectionMutation from=\(selectedTabId.uuidString.prefix(5)) " + + "to=\(target.id.uuidString.prefix(5))" + ) + self.selectedTabId = target.id + } +#endif + @discardableResult func addWorkspace( workingDirectory overrideWorkingDirectory: String? = nil, @@ -1155,6 +1197,9 @@ class TabManager: ObservableObject { // Snapshot current published state once so workspace creation doesn't repeatedly // bounce through Combine-backed accessors while we're preparing the new workspace. let snapshot = workspaceCreationSnapshot() +#if DEBUG + maybeMutateSelectionDuringWorkspaceCreationForDev(snapshot: snapshot) +#endif let nextTabCount = snapshot.tabs.count + 1 sentryBreadcrumb("workspace.create", data: ["tabCount": nextTabCount]) let explicitWorkingDirectory = normalizedWorkingDirectory(overrideWorkingDirectory) @@ -1166,7 +1211,7 @@ class TabManager: ObservableObject { let insertIndex = newTabInsertIndex(snapshot: snapshot, placementOverride: placementOverride) let ordinal = Self.nextPortOrdinal Self.nextPortOrdinal += 1 - let newWorkspace = Workspace( + let newWorkspace = makeWorkspaceForCreation( title: "Terminal \(nextTabCount)", workingDirectory: workingDirectory, portOrdinal: ordinal, diff --git a/cmuxTests/WorkspaceUnitTests.swift b/cmuxTests/WorkspaceUnitTests.swift index efe4f768..ddd253a0 100644 --- a/cmuxTests/WorkspaceUnitTests.swift +++ b/cmuxTests/WorkspaceUnitTests.swift @@ -294,6 +294,29 @@ final class WorkspacePlacementSettingsTests: XCTestCase { @MainActor final class WorkspaceCreationPlacementTests: XCTestCase { + private final class SnapshotMutatingTabManager: TabManager { + var beforeCreateWorkspace: (() -> Void)? + + override func makeWorkspaceForCreation( + title: String, + workingDirectory: String?, + portOrdinal: Int, + configTemplate: ghostty_surface_config_s?, + initialTerminalCommand: String?, + initialTerminalEnvironment: [String: String] + ) -> Workspace { + beforeCreateWorkspace?() + return super.makeWorkspaceForCreation( + title: title, + workingDirectory: workingDirectory, + portOrdinal: portOrdinal, + configTemplate: configTemplate, + initialTerminalCommand: initialTerminalCommand, + initialTerminalEnvironment: initialTerminalEnvironment + ) + } + } + func testAddWorkspaceDefaultPlacementMatchesCurrentSetting() { let currentPlacement = WorkspacePlacementSettings.current() @@ -352,6 +375,30 @@ final class WorkspaceCreationPlacementTests: XCTestCase { XCTAssertEqual(manager.tabs.last?.id, inserted.id) } + func testAddWorkspaceAfterCurrentUsesPrecreationSnapshotWhenSelectionMutatesDuringBootstrap() { + let manager = SnapshotMutatingTabManager() + guard let first = manager.tabs.first else { + XCTFail("Expected initial workspace") + return + } + + manager.setPinned(first, pinned: true) + let second = manager.addWorkspace() + let third = manager.addWorkspace() + manager.selectWorkspace(third) + + let baselineOrder = manager.tabs.map(\.id) + manager.beforeCreateWorkspace = { + manager.selectWorkspace(first) + } + + let inserted = manager.addWorkspace(placementOverride: .afterCurrent) + + XCTAssertEqual(manager.tabs.map(\.id).filter { $0 != inserted.id }, baselineOrder) + XCTAssertEqual(manager.tabs.map(\.id), [first.id, second.id, third.id, inserted.id]) + XCTAssertEqual(manager.selectedTabId, inserted.id) + } + private func makeManagerWithThreeWorkspaces() -> TabManager { let manager = TabManager() _ = manager.addWorkspace()