From 68933fc6167c816ee9274adc5dd735006ec3d754 Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 19 Sep 2026 09:18:21 -0700 Subject: [PATCH 1/3] Expose Settings in the sidebar Help menu --- Sources/ContentView.swift | 31 +++++++-- cmuxUITests/SidebarHelpMenuUITests.swift | 88 ++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 5 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 624e78706212..df9e799902c6 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -15350,6 +15350,7 @@ struct SidebarFooterButtons: View { } private enum SidebarHelpMenuAction { + case settings case upgrade case importBrowserData case keyboardShortcuts @@ -15400,6 +15401,11 @@ private struct SidebarHelpMenuButton: View { #endif } + private var settingsShortcutHint: String { + let _ = keyboardShortcutSettingsObserver.revision + return KeyboardShortcutSettings.shortcut(for: .openSettings).displayString + } + private var sendFeedbackShortcutHint: String { let _ = keyboardShortcutSettingsObserver.revision return KeyboardShortcutSettings.shortcut(for: .sendFeedback).displayString @@ -15445,12 +15451,11 @@ private struct SidebarHelpMenuButton: View { ) } helpOptionButton( - title: String(localized: "sidebar.help.sendFeedback", defaultValue: "Send Feedback"), - action: .sendFeedback, - accessibilityIdentifier: "SidebarHelpMenuOptionSendFeedback", + title: String(localized: "menu.app.settings", defaultValue: "Settings…"), + action: .settings, + accessibilityIdentifier: "SidebarHelpMenuOptionSettings", isExternalLink: false, - shortcutHint: sendFeedbackShortcutHint, - trailingSystemImage: "bubble.left.and.text.bubble.right" + shortcutHint: settingsShortcutHint ) helpOptionButton( title: String(localized: "settings.section.keyboardShortcuts", defaultValue: "Keyboard Shortcuts"), @@ -15464,6 +15469,14 @@ private struct SidebarHelpMenuButton: View { accessibilityIdentifier: "SidebarHelpMenuOptionImportBrowserData", isExternalLink: false ) + helpOptionButton( + title: String(localized: "sidebar.help.sendFeedback", defaultValue: "Send Feedback"), + action: .sendFeedback, + accessibilityIdentifier: "SidebarHelpMenuOptionSendFeedback", + isExternalLink: false, + shortcutHint: sendFeedbackShortcutHint, + trailingSystemImage: "bubble.left.and.text.bubble.right" + ) if docsURL != nil { helpOptionButton( title: String(localized: "about.docs", defaultValue: "Docs"), @@ -15564,6 +15577,14 @@ private struct SidebarHelpMenuButton: View { private func perform(_ action: SidebarHelpMenuAction) { switch action { + case .settings: + Task { @MainActor in + if let appDelegate = AppDelegate.shared { + appDelegate.openPreferencesWindow(debugSource: "sidebarHelpMenu.settings") + } else { + AppDelegate.presentPreferencesWindow() + } + } case .upgrade: ProUpgradePresenter.present(source: .sidebarHelpMenu) case .importBrowserData: diff --git a/cmuxUITests/SidebarHelpMenuUITests.swift b/cmuxUITests/SidebarHelpMenuUITests.swift index 8e968c19bfd6..e9b470ac0366 100644 --- a/cmuxUITests/SidebarHelpMenuUITests.swift +++ b/cmuxUITests/SidebarHelpMenuUITests.swift @@ -123,6 +123,93 @@ final class SidebarHelpMenuUITests: XCTestCase { XCTAssertTrue(app.staticTexts["5/4000"].waitForExistence(timeout: 2.0)) } + func testHelpMenuSettingsOpensDismissesAndReopensWithShortcut() { + let app = XCUIApplication.cmuxTestApplication() + app.launchArguments += [ + "-AppleLanguages", "(en)", + "-AppleLocale", "en_US", + "-ApplePersistenceIgnoreState", "YES", + "-NSQuitAlwaysKeepsWindows", "NO", + "-menuBarOnly", "false", + ] + app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1" + app.launch() + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 10.0) { + app.state == .runningForeground || app.state == .runningBackground + }, + "App failed to launch. state=\(app.state.rawValue)" + ) + app.activate() + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 6.0) { app.state == .runningForeground }, + "App did not become foreground before interactions. state=\(app.state.rawValue)" + ) + + XCTAssertTrue(waitForWindowCount(atLeast: 1, app: app, timeout: 10.0)) + + let helpButton = requireElement( + candidates: helpButtonCandidates(in: app), + timeout: 6.0, + description: "sidebar help button" + ) + helpButton.click() + + let settingsItem = requireElement( + candidates: helpMenuItemCandidates( + in: app, + identifier: "SidebarHelpMenuOptionSettings", + title: "Settings…" + ), + timeout: 3.0, + description: "Settings help menu item" + ) + settingsItem.click() + + let settings = app.windows["Settings"] + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 6.0) { settings.exists }, + "Expected Settings to open from the sidebar Help menu" + ) + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 2.0) { + !app.buttons["SidebarHelpMenuOptionSettings"].exists + && !app.buttons["Settings…"].exists + }, + "Expected the Help popover to dismiss after opening Settings" + ) + XCTAssertEqual(app.windows.count, 2, "Expected one main window and one Settings window") + + settings.typeKey("w", modifierFlags: [.command]) + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 3.0) { app.windows.count == 1 && !settings.exists }, + "Expected Cmd+W to close Settings and return to the main window" + ) + + helpButton.click() + let reopenedSettingsItem = requireElement( + candidates: helpMenuItemCandidates( + in: app, + identifier: "SidebarHelpMenuOptionSettings", + title: "Settings…" + ), + timeout: 3.0, + description: "Settings help menu item after closing Settings" + ) + reopenedSettingsItem.click() + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 6.0) { app.windows["Settings"].exists }, + "Expected the Help menu to reopen Settings after closing it" + ) + app.typeKey(",", modifierFlags: [.command]) + XCTAssertTrue(settings.exists, "Expected Cmd+, to keep the existing Settings window open") + XCTAssertEqual( + app.windows.count, + 2, + "Expected Cmd+, to reuse the single Settings window instead of creating a duplicate" + ) + } + private func waitForWindowCount(atLeast count: Int, app: XCUIApplication, timeout: TimeInterval) -> Bool { sidebarHelpPollUntil(timeout: timeout) { app.windows.count >= count @@ -136,6 +223,7 @@ final class SidebarHelpMenuUITests: XCTestCase { app.buttons["Help"], sidebar.buttons["SidebarHelpMenuButton"], sidebar.buttons["Help"], + app.descendants(matching: .any).matching(NSPredicate(format: "label == %@", "Help")).firstMatch, ] } From 30ba17010b072e4cdcfdf9851fd923a34471b1cf Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 19 Sep 2026 09:18:26 -0700 Subject: [PATCH 2/3] test: wait for Settings shortcut transition --- cmuxUITests/SidebarHelpMenuUITests.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cmuxUITests/SidebarHelpMenuUITests.swift b/cmuxUITests/SidebarHelpMenuUITests.swift index e9b470ac0366..cdcac4941078 100644 --- a/cmuxUITests/SidebarHelpMenuUITests.swift +++ b/cmuxUITests/SidebarHelpMenuUITests.swift @@ -201,12 +201,23 @@ final class SidebarHelpMenuUITests: XCTestCase { sidebarHelpPollUntil(timeout: 6.0) { app.windows["Settings"].exists }, "Expected the Help menu to reopen Settings after closing it" ) + + settings.typeKey("w", modifierFlags: [.command]) + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 3.0) { app.windows.count == 1 && !settings.exists }, + "Expected Settings to close before exercising Cmd+," + ) + + app.typeKey(",", modifierFlags: [.command]) + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 6.0) { settings.exists && app.windows.count == 2 }, + "Expected Cmd+, to reopen the existing Settings surface" + ) app.typeKey(",", modifierFlags: [.command]) - XCTAssertTrue(settings.exists, "Expected Cmd+, to keep the existing Settings window open") XCTAssertEqual( app.windows.count, 2, - "Expected Cmd+, to reuse the single Settings window instead of creating a duplicate" + "Expected a repeated Cmd+, to reuse the single Settings window instead of creating a duplicate" ) } From 5c2098a40322be67461675e502cfaaab794da147 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 20 Sep 2026 10:34:33 -0700 Subject: [PATCH 3/3] fix: import CmuxFoundation process identities Apply upstream 968cef2005940ee167bed3f8ab01434eb8b7dc2c (#13236) to unblock native compile admission without merging unrelated main changes. --- Sources/Workspace+AgentResumeOwnership.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Workspace+AgentResumeOwnership.swift b/Sources/Workspace+AgentResumeOwnership.swift index 4c87e05244c3..12486d9f05b6 100644 --- a/Sources/Workspace+AgentResumeOwnership.swift +++ b/Sources/Workspace+AgentResumeOwnership.swift @@ -1,3 +1,4 @@ +import CmuxFoundation import CMUXAgentLaunch import CmuxFoundation import Foundation