diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index e955658d..a2667df2 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1987,16 +1987,26 @@ struct ContentView: View { let isSelectedWorkspace = selectedWorkspaceId == tab.id let isRetiringWorkspace = retiringWorkspaceId == tab.id let shouldPrimeInBackground = tabManager.pendingBackgroundWorkspaceLoadIds.contains(tab.id) + let isRenderedVisible = isSelectedWorkspace || isRetiringWorkspace + let isWorkspaceVisibleToPanels = isRenderedVisible || shouldPrimeInBackground + let workspaceRenderOpacity: Double = { + if isRenderedVisible { + return 1 + } + if shouldPrimeInBackground { + return 0.001 + } + return 0 + }() // Keep the retiring workspace visible during handoff, but never input-active. // Allowing both selected+retiring workspaces to be input-active lets the // old workspace steal first responder (notably with WKWebView), which can // delay handoff completion and make browser returns feel laggy. let isInputActive = isSelectedWorkspace - let isVisible = isSelectedWorkspace || isRetiringWorkspace let portalPriority = isSelectedWorkspace ? 2 : (isRetiringWorkspace ? 1 : 0) WorkspaceContentView( workspace: tab, - isWorkspaceVisible: isVisible, + isWorkspaceVisible: isWorkspaceVisibleToPanels, isWorkspaceInputActive: isInputActive, workspacePortalPriority: portalPriority, onThemeRefreshRequest: { reason, eventId, source, payloadHex in @@ -2009,9 +2019,9 @@ struct ContentView: View { ) } ) - .opacity(isVisible ? 1 : 0) + .opacity(workspaceRenderOpacity) .allowsHitTesting(isSelectedWorkspace) - .accessibilityHidden(!isVisible) + .accessibilityHidden(!isRenderedVisible) .zIndex(isSelectedWorkspace ? 2 : (isRetiringWorkspace ? 1 : 0)) .task(id: shouldPrimeInBackground ? tab.id : nil) { await primeBackgroundWorkspaceIfNeeded(workspaceId: tab.id) diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index f8ff3836..f3dabff0 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -1798,6 +1798,13 @@ final class BrowserPanel: Panel, ObservableObject { private let developerToolsRestoreRetryMaxAttempts: Int = 40 private var remoteProxyEndpoint: BrowserProxyEndpoint? @Published private(set) var remoteWorkspaceStatus: BrowserRemoteWorkspaceStatus? + private let usesRemoteWorkspaceProxy: Bool + private struct PendingRemoteNavigation { + let request: URLRequest + let recordTypedNavigation: Bool + let preserveRestoredSessionHistory: Bool + } + private var pendingRemoteNavigation: PendingRemoteNavigation? private let developerToolsDetachedOpenGracePeriod: TimeInterval = 0.35 private var developerToolsDetachedOpenGraceDeadline: Date? private var developerToolsTransitionTargetVisible: Bool? @@ -2045,15 +2052,17 @@ final class BrowserPanel: Panel, ObservableObject { initialURL: URL? = nil, bypassInsecureHTTPHostOnce: String? = nil, proxyEndpoint: BrowserProxyEndpoint? = nil, - isRemoteWorkspace: Bool = false + isRemoteWorkspace: Bool = false, + remoteWebsiteDataStoreIdentifier: UUID? = nil ) { self.id = UUID() self.workspaceId = workspaceId self.insecureHTTPBypassHostOnce = BrowserInsecureHTTPSettings.normalizeHost(bypassInsecureHTTPHostOnce ?? "") self.remoteProxyEndpoint = proxyEndpoint + self.usesRemoteWorkspaceProxy = isRemoteWorkspace self.browserThemeMode = BrowserThemeSettings.mode() self.websiteDataStore = isRemoteWorkspace - ? WKWebsiteDataStore(forIdentifier: self.id) + ? WKWebsiteDataStore(forIdentifier: remoteWebsiteDataStoreIdentifier ?? workspaceId) : .default() let webView = Self.makeWebView(websiteDataStore: websiteDataStore) @@ -2143,6 +2152,7 @@ final class BrowserPanel: Panel, ObservableObject { guard remoteProxyEndpoint != endpoint else { return } remoteProxyEndpoint = endpoint applyRemoteProxyConfigurationIfAvailable() + resumePendingRemoteNavigationIfNeeded() } func setRemoteWorkspaceStatus(_ status: BrowserRemoteWorkspaceStatus?) { @@ -2785,6 +2795,46 @@ final class BrowserPanel: Panel, ObservableObject { preserveRestoredSessionHistory: Bool = false ) { guard let url = request.url else { return } + if usesRemoteWorkspaceProxy, remoteProxyEndpoint == nil { + pendingRemoteNavigation = PendingRemoteNavigation( + request: request, + recordTypedNavigation: recordTypedNavigation, + preserveRestoredSessionHistory: preserveRestoredSessionHistory + ) + shouldRenderWebView = true + currentURL = Self.remoteProxyDisplayURL(for: url) ?? url + navigationDelegate?.lastAttemptedURL = url + return + } + performNavigation( + request: request, + originalURL: url, + recordTypedNavigation: recordTypedNavigation, + preserveRestoredSessionHistory: preserveRestoredSessionHistory + ) + } + + private func resumePendingRemoteNavigationIfNeeded() { + guard remoteProxyEndpoint != nil, + let pendingRemoteNavigation else { + return + } + self.pendingRemoteNavigation = nil + guard let originalURL = pendingRemoteNavigation.request.url else { return } + performNavigation( + request: pendingRemoteNavigation.request, + originalURL: originalURL, + recordTypedNavigation: pendingRemoteNavigation.recordTypedNavigation, + preserveRestoredSessionHistory: pendingRemoteNavigation.preserveRestoredSessionHistory + ) + } + + private func performNavigation( + request: URLRequest, + originalURL: URL, + recordTypedNavigation: Bool, + preserveRestoredSessionHistory: Bool + ) { if !preserveRestoredSessionHistory { abandonRestoredSessionHistoryIfNeeded() } @@ -2793,9 +2843,9 @@ final class BrowserPanel: Panel, ObservableObject { webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent shouldRenderWebView = true if recordTypedNavigation { - BrowserHistoryStore.shared.recordTypedNavigation(url: url) + BrowserHistoryStore.shared.recordTypedNavigation(url: originalURL) } - navigationDelegate?.lastAttemptedURL = url + navigationDelegate?.lastAttemptedURL = originalURL browserLoadRequest(effectiveRequest, in: webView) } diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 65edf96f..d2340739 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -943,6 +943,9 @@ class TabManager: ObservableObject { newWorkspace.owningTabManager = self wireClosedBrowserTracking(for: newWorkspace) let insertIndex = newTabInsertIndex(snapshot: snapshot, placementOverride: placementOverride) + if eagerLoadTerminal && !select { + requestBackgroundWorkspaceLoad(for: newWorkspace.id) + } var updatedTabs = snapshot.tabs if insertIndex >= 0 && insertIndex <= updatedTabs.count { updatedTabs.insert(newWorkspace, at: insertIndex) @@ -959,7 +962,9 @@ class TabManager: ObservableObject { ) } if eagerLoadTerminal { - newWorkspace.focusedTerminalPanel?.surface.requestBackgroundSurfaceStartIfNeeded() + if select { + newWorkspace.focusedTerminalPanel?.surface.requestBackgroundSurfaceStartIfNeeded() + } } if select { #if DEBUG @@ -1169,21 +1174,33 @@ class TabManager: ObservableObject { } func requestBackgroundWorkspaceLoad(for workspaceId: UUID) { - guard pendingBackgroundWorkspaceLoadIds.insert(workspaceId).inserted else { return } + guard !pendingBackgroundWorkspaceLoadIds.contains(workspaceId) else { return } + var updated = pendingBackgroundWorkspaceLoadIds + updated.insert(workspaceId) + pendingBackgroundWorkspaceLoadIds = updated } func completeBackgroundWorkspaceLoad(for workspaceId: UUID) { - guard pendingBackgroundWorkspaceLoadIds.remove(workspaceId) != nil else { return } + guard pendingBackgroundWorkspaceLoadIds.contains(workspaceId) else { return } + var updated = pendingBackgroundWorkspaceLoadIds + updated.remove(workspaceId) + pendingBackgroundWorkspaceLoadIds = updated } func retainDebugWorkspaceLoads(for workspaceIds: Set) { guard !workspaceIds.isEmpty else { return } - debugPinnedWorkspaceLoadIds.formUnion(workspaceIds) + var updated = debugPinnedWorkspaceLoadIds + updated.formUnion(workspaceIds) + guard updated != debugPinnedWorkspaceLoadIds else { return } + debugPinnedWorkspaceLoadIds = updated } func releaseDebugWorkspaceLoads(for workspaceIds: Set) { guard !workspaceIds.isEmpty else { return } - debugPinnedWorkspaceLoadIds.subtract(workspaceIds) + var updated = debugPinnedWorkspaceLoadIds + updated.subtract(workspaceIds) + guard updated != debugPinnedWorkspaceLoadIds else { return } + debugPinnedWorkspaceLoadIds = updated } func pruneBackgroundWorkspaceLoads(existingIds: Set) { @@ -4046,11 +4063,13 @@ extension TabManager { } func sessionSnapshot(includeScrollback: Bool) -> SessionTabManagerSnapshot { - let workspaceSnapshots = tabs + let restorableTabs = tabs + .filter { !$0.isRemoteWorkspace } .prefix(SessionPersistencePolicy.maxWorkspacesPerWindow) + let workspaceSnapshots = restorableTabs .map { $0.sessionSnapshot(includeScrollback: includeScrollback) } let selectedWorkspaceIndex = selectedTabId.flatMap { selectedTabId in - tabs.firstIndex(where: { $0.id == selectedTabId }) + restorableTabs.firstIndex(where: { $0.id == selectedTabId }) } return SessionTabManagerSnapshot( selectedWorkspaceIndex: selectedWorkspaceIndex, diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index da23e155..3e1cf32c 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -6256,9 +6256,12 @@ final class Workspace: Identifiable, ObservableObject { } private func remoteTerminalStartupCommand() -> String? { - guard hasActiveRemoteTerminalSessions else { return nil } - return remoteConfiguration?.terminalStartupCommand? - .trimmingCharacters(in: .whitespacesAndNewlines) + guard let command = remoteConfiguration?.terminalStartupCommand? + .trimmingCharacters(in: .whitespacesAndNewlines), + !command.isEmpty else { + return nil + } + return command } /// Create a new browser panel split @@ -6288,7 +6291,8 @@ final class Workspace: Identifiable, ObservableObject { workspaceId: id, initialURL: url, proxyEndpoint: remoteProxyEndpoint, - isRemoteWorkspace: isRemoteWorkspace + isRemoteWorkspace: isRemoteWorkspace, + remoteWebsiteDataStoreIdentifier: isRemoteWorkspace ? id : nil ) panels[browserPanel.id] = browserPanel panelTitles[browserPanel.id] = browserPanel.displayTitle @@ -6357,7 +6361,8 @@ final class Workspace: Identifiable, ObservableObject { initialURL: url, bypassInsecureHTTPHostOnce: bypassInsecureHTTPHostOnce, proxyEndpoint: remoteProxyEndpoint, - isRemoteWorkspace: isRemoteWorkspace + isRemoteWorkspace: isRemoteWorkspace, + remoteWebsiteDataStoreIdentifier: isRemoteWorkspace ? id : nil ) panels[browserPanel.id] = browserPanel panelTitles[browserPanel.id] = browserPanel.displayTitle diff --git a/cmuxTests/GhosttyConfigTests.swift b/cmuxTests/GhosttyConfigTests.swift index 7a811d69..9841a625 100644 --- a/cmuxTests/GhosttyConfigTests.swift +++ b/cmuxTests/GhosttyConfigTests.swift @@ -47,6 +47,19 @@ final class GhosttyConfigTests: XCTestCase { let blue: Int } + private func writeAppSupportConfig( + root: URL, + bundleIdentifier: String, + name: String = "config", + contents: String = "font-size = 14\n" + ) throws -> URL { + let directory = root.appendingPathComponent(bundleIdentifier, isDirectory: true) + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + let url = directory.appendingPathComponent(name, isDirectory: false) + try contents.write(to: url, atomically: true, encoding: .utf8) + return url + } + func testResolveThemeNamePrefersLightEntryForPairedTheme() { let resolved = GhosttyConfig.resolveThemeName( from: "light:Builtin Solarized Light,dark:Builtin Solarized Dark", @@ -312,48 +325,69 @@ final class GhosttyConfigTests: XCTestCase { } func testReleaseAppSupportFallbackLoadsForDebugWhenOnlyReleaseConfigExists() { - XCTAssertTrue( - GhosttyApp.shouldLoadReleaseAppSupportGhosttyConfig( + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-release-config-\(UUID().uuidString)", isDirectory: true) + try? FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: root) } + + let releaseURL = try? writeAppSupportConfig( + root: root, + bundleIdentifier: "com.cmuxterm.app" + ) + + XCTAssertEqual( + GhosttyApp.cmuxAppSupportConfigURLs( currentBundleIdentifier: "com.cmuxterm.app.debug", - currentConfigFileSize: nil, - currentLegacyConfigFileSize: nil, - releaseConfigFileSize: 128, - releaseLegacyConfigFileSize: nil - ) + appSupportDirectory: root + ), + [releaseURL].compactMap { $0 } ) } func testReleaseAppSupportFallbackSkipsWhenDebugConfigAlreadyExists() { - XCTAssertFalse( - GhosttyApp.shouldLoadReleaseAppSupportGhosttyConfig( + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-release-config-\(UUID().uuidString)", isDirectory: true) + try? FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: root) } + + _ = try? writeAppSupportConfig(root: root, bundleIdentifier: "com.cmuxterm.app") + let debugURL = try? writeAppSupportConfig( + root: root, + bundleIdentifier: "com.cmuxterm.app.debug.issue-829", + name: "config.ghostty" + ) + + XCTAssertEqual( + GhosttyApp.cmuxAppSupportConfigURLs( currentBundleIdentifier: "com.cmuxterm.app.debug.issue-829", - currentConfigFileSize: nil, - currentLegacyConfigFileSize: 64, - releaseConfigFileSize: 128, - releaseLegacyConfigFileSize: nil - ) + appSupportDirectory: root + ), + [debugURL].compactMap { $0 } ) } func testReleaseAppSupportFallbackSkipsForNonDebugBundleOrMissingReleaseConfig() { - XCTAssertFalse( - GhosttyApp.shouldLoadReleaseAppSupportGhosttyConfig( + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-release-config-\(UUID().uuidString)", isDirectory: true) + try? FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: root) } + + _ = try? writeAppSupportConfig(root: root, bundleIdentifier: "com.cmuxterm.app") + + XCTAssertEqual( + GhosttyApp.cmuxAppSupportConfigURLs( currentBundleIdentifier: "com.cmuxterm.app", - currentConfigFileSize: nil, - currentLegacyConfigFileSize: nil, - releaseConfigFileSize: 128, - releaseLegacyConfigFileSize: nil - ) + appSupportDirectory: root + ).count, + 1 ) - XCTAssertFalse( - GhosttyApp.shouldLoadReleaseAppSupportGhosttyConfig( + XCTAssertEqual( + GhosttyApp.cmuxAppSupportConfigURLs( currentBundleIdentifier: "com.cmuxterm.app.debug", - currentConfigFileSize: nil, - currentLegacyConfigFileSize: nil, - releaseConfigFileSize: nil, - releaseLegacyConfigFileSize: 0 - ) + appSupportDirectory: root.appendingPathComponent("missing", isDirectory: true) + ), + [] ) } @@ -831,12 +865,79 @@ final class RemoteLoopbackHTTPRequestRewriterTests: XCTestCase { @MainActor final class BrowserPanelRemoteStoreTests: XCTestCase { - func testRemoteWorkspaceUsesDedicatedWebsiteDataStore() { + func testRemoteWorkspacePanelsShareWorkspaceScopedWebsiteDataStore() { let localPanel = BrowserPanel(workspaceId: UUID(), isRemoteWorkspace: false) - let remotePanel = BrowserPanel(workspaceId: UUID(), isRemoteWorkspace: true) + let remoteWorkspaceId = UUID() + let firstRemotePanel = BrowserPanel( + workspaceId: remoteWorkspaceId, + isRemoteWorkspace: true, + remoteWebsiteDataStoreIdentifier: remoteWorkspaceId + ) + let secondRemotePanel = BrowserPanel( + workspaceId: remoteWorkspaceId, + isRemoteWorkspace: true, + remoteWebsiteDataStoreIdentifier: remoteWorkspaceId + ) XCTAssertTrue(localPanel.webView.configuration.websiteDataStore === WKWebsiteDataStore.default()) - XCTAssertFalse(remotePanel.webView.configuration.websiteDataStore === WKWebsiteDataStore.default()) + XCTAssertFalse(firstRemotePanel.webView.configuration.websiteDataStore === WKWebsiteDataStore.default()) + XCTAssertTrue( + firstRemotePanel.webView.configuration.websiteDataStore === + secondRemotePanel.webView.configuration.websiteDataStore + ) + } + + func testRemoteWorkspaceDefersInitialNavigationUntilProxyEndpointIsReady() { + let remoteWorkspaceId = UUID() + let url = URL(string: "http://localhost:3000/demo")! + let panel = BrowserPanel( + workspaceId: remoteWorkspaceId, + initialURL: url, + isRemoteWorkspace: true, + remoteWebsiteDataStoreIdentifier: remoteWorkspaceId + ) + + XCTAssertEqual(panel.preferredURLStringForOmnibar(), url.absoluteString) + XCTAssertNil(panel.webView.url) + + panel.setRemoteProxyEndpoint(BrowserProxyEndpoint(host: "127.0.0.1", port: 9876)) + + let deadline = Date().addingTimeInterval(1.0) + while panel.webView.url == nil, RunLoop.main.run(mode: .default, before: deadline), Date() < deadline {} + + XCTAssertEqual(panel.preferredURLStringForOmnibar(), url.absoluteString) + XCTAssertEqual(panel.webView.url?.host, "cmux-loopback.localtest.me") + } + + func testNewTerminalSurfaceStaysRemoteWhileBrowserPanelsKeepWorkspaceRemote() throws { + let workspace = Workspace() + let paneId = try XCTUnwrap(workspace.bonsplitController.allPaneIds.first) + let initialTerminalId = try XCTUnwrap(workspace.focusedPanelId) + let configuration = WorkspaceRemoteConfiguration( + destination: "cmux-macmini", + port: nil, + identityFile: nil, + sshOptions: [], + localProxyPort: nil, + relayPort: 64000, + relayID: "relay-test", + relayToken: String(repeating: "a", count: 64), + localSocketPath: "/tmp/cmux-test.sock", + terminalStartupCommand: "ssh cmux-macmini" + ) + + workspace.configureRemoteConnection(configuration, autoConnect: false) + _ = workspace.newBrowserSurface(inPane: paneId, url: URL(string: "https://example.com"), focus: false) + + workspace.markRemoteTerminalSessionEnded(surfaceId: initialTerminalId, relayPort: configuration.relayPort) + + XCTAssertTrue(workspace.isRemoteWorkspace) + XCTAssertEqual(workspace.activeRemoteTerminalSessionCount, 0) + + _ = try XCTUnwrap(workspace.newTerminalSurface(inPane: paneId, focus: false)) + + XCTAssertTrue(workspace.isRemoteWorkspace) + XCTAssertEqual(workspace.activeRemoteTerminalSessionCount, 1) } } diff --git a/cmuxTests/TabManagerSessionSnapshotTests.swift b/cmuxTests/TabManagerSessionSnapshotTests.swift index af954ee2..b0d44856 100644 --- a/cmuxTests/TabManagerSessionSnapshotTests.swift +++ b/cmuxTests/TabManagerSessionSnapshotTests.swift @@ -46,4 +46,30 @@ final class TabManagerSessionSnapshotTests: XCTestCase { XCTAssertEqual(manager.tabs.count, 1) XCTAssertNotNil(manager.selectedTabId) } + + func testSessionSnapshotExcludesRemoteWorkspacesFromRestore() throws { + let manager = TabManager() + let remoteWorkspace = manager.addWorkspace(select: true) + let configuration = WorkspaceRemoteConfiguration( + destination: "cmux-macmini", + port: nil, + identityFile: nil, + sshOptions: [], + localProxyPort: nil, + relayPort: 64001, + relayID: "relay-test", + relayToken: String(repeating: "b", count: 64), + localSocketPath: "/tmp/cmux-test.sock", + terminalStartupCommand: "ssh cmux-macmini" + ) + remoteWorkspace.configureRemoteConnection(configuration, autoConnect: false) + let paneId = try XCTUnwrap(remoteWorkspace.bonsplitController.allPaneIds.first) + _ = remoteWorkspace.newBrowserSurface(inPane: paneId, url: URL(string: "http://localhost:3000"), focus: false) + + let snapshot = manager.sessionSnapshot(includeScrollback: false) + + XCTAssertEqual(snapshot.workspaces.count, 1) + XCTAssertNil(snapshot.selectedWorkspaceIndex) + XCTAssertFalse(snapshot.workspaces.contains { $0.processTitle == remoteWorkspace.title }) + } } diff --git a/tests_v2/test_workspace_create_background_starts_terminal.py b/tests_v2/test_workspace_create_background_starts_terminal.py new file mode 100644 index 00000000..f68c72b8 --- /dev/null +++ b/tests_v2/test_workspace_create_background_starts_terminal.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Regression: background workspace.create should start its initial terminal before selection.""" + +from __future__ import annotations + +import os +import shlex +import sys +import tempfile +import time +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from cmux import cmux, cmuxError + + +SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock") + + +def _must(cond: bool, msg: str) -> None: + if not cond: + raise cmuxError(msg) + + +def _wait_for_file_text(path: Path, needle: str, timeout_s: float = 8.0) -> str: + deadline = time.time() + timeout_s + last_text = "" + while time.time() < deadline: + if path.exists(): + last_text = path.read_text(encoding="utf-8", errors="replace") + if needle in last_text: + return last_text + time.sleep(0.1) + raise cmuxError(f"Timed out waiting for {needle!r} in background workspace file: {last_text!r}") + + +def main() -> int: + with cmux(SOCKET_PATH) as c: + baseline_workspace = c.current_workspace() + created_workspace = "" + marker_path = Path(tempfile.gettempdir()) / f"cmux-bg-start-{int(time.time() * 1000)}.txt" + try: + token = f"CMUX_BG_START_{int(time.time() * 1000)}" + initial_command = ( + "python3 -c " + + shlex.quote( + f"from pathlib import Path; Path({marker_path.as_posix()!r}).write_text({token!r}, encoding='utf-8')" + ) + ) + payload = c._call( + "workspace.create", + {"initial_command": initial_command}, + ) or {} + created_workspace = str(payload.get("workspace_id") or "") + _must(bool(created_workspace), f"workspace.create returned no workspace_id: {payload}") + _must( + c.current_workspace() == baseline_workspace, + "workspace.create should preserve selected workspace", + ) + + text = _wait_for_file_text(marker_path, token) + _must(token in text, f"Background workspace did not run its initial command: {text!r}") + _must( + c.current_workspace() == baseline_workspace, + "background eager load should not switch the selected workspace", + ) + finally: + try: + marker_path.unlink() + except FileNotFoundError: + pass + if created_workspace: + try: + c.close_workspace(created_workspace) + except Exception: + pass + + print("PASS: workspace.create eager background load starts the initial terminal without focus") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())