Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 25 additions & 27 deletions .github/swift-file-length-budget.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,39 @@
# Format: max_lines<TAB>relative path
# Reduce counts as files shrink. CI fails if tracked files exceed this budget.
33454 CLI/cmux.swift
20034 Sources/Workspace.swift
19272 Sources/ContentView.swift
18130 Sources/AppDelegate.swift
16680 Sources/GhosttyTerminalView.swift
14774 Sources/TerminalController.swift
13611 Sources/Panels/BrowserPanel.swift
19204 Sources/ContentView.swift
17913 Sources/AppDelegate.swift
14610 Sources/TerminalController.swift
13577 Sources/Panels/BrowserPanel.swift
12084 Sources/GhosttyTerminalView.swift
12046 cmuxTests/AppDelegateShortcutRoutingTests.swift
10020 Sources/TabManager.swift
12020 Sources/Workspace.swift
9345 cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
7850 Sources/Panels/BrowserPanelView.swift
7349 cmuxTests/WorkspaceUnitTests.swift
6948 cmuxTests/WorkspaceRemoteConnectionTests.swift
6555 cmuxTests/GhosttyConfigTests.swift
6332 cmuxTests/SessionPersistenceTests.swift
6299 cmuxTests/TerminalAndGhosttyTests.swift
6944 cmuxTests/WorkspaceRemoteConnectionTests.swift
6316 cmuxTests/SessionPersistenceTests.swift
6296 cmuxTests/GhosttyConfigTests.swift
6153 CLI/cmux_open.swift
6074 Sources/TabManager.swift
6074 Sources/TextBoxInput.swift
5969 cmuxTests/TerminalAndGhosttyTests.swift
5500 cmuxTests/BrowserConfigTests.swift
5487 Sources/cmuxApp.swift
4938 Packages/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
4460 Sources/Panels/FilePreviewPanel.swift
4400 cmuxTests/BrowserPanelTests.swift
4227 Sources/BrowserWindowPortal.swift
4009 cmuxTests/WindowAndDragTests.swift
3937 Sources/Feed/FeedPanelView.swift
3895 cmuxTests/WindowAndDragTests.swift
3764 cmuxTests/TabManagerUnitTests.swift
3699 cmuxTests/CLIGenericHookPersistenceTests.swift
3665 Packages/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift
3664 Packages/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift
3397 Sources/CmuxConfig.swift
3331 cmuxTests/TabManagerSessionSnapshotTests.swift
3202 Sources/Update/UpdateTitlebarAccessory.swift
2877 Sources/SessionIndexView.swift
2871 cmuxTests/CMUXOpenCommandTests.swift
2623 Sources/TerminalNotificationStore.swift
2573 Sources/KeyboardShortcutSettings.swift
2565 Sources/Panels/CmuxWebView.swift
2545 cmuxTests/WorkspaceManualUnreadTests.swift
Expand All @@ -44,10 +43,11 @@
2328 cmuxTests/CJKIMEInputTests.swift
2314 Sources/FileExplorerView.swift
2261 Sources/TerminalWindowPortal.swift
2221 Sources/SessionPersistence.swift
2236 Sources/TerminalNotificationStore.swift
2134 cmuxTests/ShortcutAndCommandPaletteTests.swift
2117 cmuxTests/CmuxConfigTests.swift
2031 Sources/KeyboardShortcutSettingsFileStore.swift
2059 Sources/SessionPersistence.swift
2034 Sources/KeyboardShortcutSettingsFileStore.swift
1949 Sources/Panels/BrowserWebAuthnSupport.swift
1860 cmuxTests/NotificationAndMenuBarTests.swift
1794 Sources/SessionIndexStore.swift
Expand All @@ -61,14 +61,14 @@
1560 cmuxTests/TextBoxMentionCompletionTests.swift
1498 cmuxTests/OmnibarAndToolsTests.swift
1496 cmuxUITests/MultiWindowNotificationsUITests.swift
1448 Sources/FileExplorerStore.swift
1446 Sources/FileExplorerStore.swift
1410 Sources/CommandPalette/CommandPaletteSearch.swift
1380 cmuxUITests/MenuKeyEquivalentRoutingUITests.swift
1376 cmuxTests/KeyboardShortcutSettingsFileStoreStartupTests.swift
1373 cmuxTests/AppDelegateIssue2907RoutingTests.swift
1366 Sources/Feed/FeedButtonStyleDebugWindowController.swift
1362 Sources/CMUXInstalledExtensionSidebarHostView.swift
1313 cmuxTests/MobileHostAuthorizationTests.swift
1312 cmuxTests/MobileHostAuthorizationTests.swift
1292 Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/Config/GhosttyConfig.swift
1285 cmuxUITests/SidebarHelpMenuUITests.swift
1270 cmuxTests/RestorableAgentSessionIndexTests.swift
Expand All @@ -81,42 +81,41 @@
1126 cmuxTests/FileExplorerStoreTests.swift
1120 cmuxTests/AgentHibernationTests.swift
1107 Sources/AppDelegate+CmuxSSHURL.swift
1096 Sources/GhosttyConfig.swift
1093 cmuxUITests/BonsplitTabDragUITests.swift
1021 cmuxUITests/TerminalCmdClickUITests.swift
1006 cmuxTests/CmuxSSHURLRequestTests.swift
1000 cmuxTests/CmuxTopSnapshotScopeTests.swift
947 Sources/TerminalNotificationPolicy.swift
945 Sources/SessionIndexRegisteredAgents.swift
944 Sources/App/ShortcutRoutingSupport.swift
941 Sources/App/TerminalDirectoryOpenSupport.swift
937 Sources/TextBoxMentionIndexStore.swift
934 Sources/App/ShortcutRoutingSupport.swift
926 Sources/DockPanelView.swift
919 Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeLifecycle.swift
917 cmuxTests/WorkspaceGroupTests.swift
913 Sources/CommandPalette/CommandPaletteSettingsToggle.swift
905 Sources/CmuxSSHURLRequest.swift
903 Sources/CommandPalette/CommandPaletteSettingsToggle.swift
897 Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
901 Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
892 Sources/WorkspaceContentView.swift
868 Sources/Panels/BrowserScreenshotSnapshotter.swift
866 Sources/Panels/TerminalPanel.swift
852 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Workspace/ControlCommandCoordinator+Workspace.swift
847 cmuxTests/AgentSessionAutoResumeSettingsTests.swift
845 cmuxTests/SSHStartupSignalLifecycleTests.swift
842 Sources/Panels/MarkdownWebRenderer.swift
841 Sources/Panels/MarkdownWebRenderer.swift
830 Sources/TaskManagerTypes.swift
810 Packages/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/SwiftViewInterpreterTests.swift
787 Sources/ClosedItemHistory.swift
774 cmuxUITests/BrowserFixtureInteractionUITests.swift
770 Sources/MainWindowFocusController.swift
762 Packages/CmuxMobileTransport/Sources/CmuxMobileTransport/CmxNetworkByteTransport.swift
760 Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentLaunchSanitizerTests.swift
757 Packages/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator.swift
756 Sources/Panels/AgentSessionWebRendererCoordinator.swift
753 Sources/TerminalController+ControlWorkspaceContext.swift
752 Sources/TerminalController+ControlWorkspaceContext.swift
752 cmuxUITests/CloseWorkspaceCmdDUITests.swift
739 Sources/App/MenuBarExtraController.swift
746 Sources/App/MenuBarExtraController.swift
738 Packages/CMUXProjectModel/Sources/CMUXProjectModel/XcodeProjectAdapter.swift
738 Packages/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator.swift
716 Sources/TaskManagerSnapshot.swift
715 Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+Input.swift
715 Sources/AppleScriptSupport.swift
Expand Down Expand Up @@ -144,7 +143,6 @@
648 Packages/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator.swift
640 cmuxTests/CommandPaletteNucleoFFITests.swift
630 Packages/CmuxSettings/Sources/CmuxSettings/Values/ShortcutWhenClause.swift
627 Sources/WorkspaceRemoteConfiguration.swift
621 cmuxTests/FinderFileDropRegressionTests.swift
621 cmuxUITests/RightSidebarChromeHeightUITests.swift
620 cmuxTests/TerminalNotificationQueueTests.swift
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ public final class DefaultsValueModel<Value: SettingCodable> {
}
}

/// Updates ``current`` after another owner has already persisted `value`.
///
/// Use this for settings whose committed write spans multiple backing keys
/// and must stay in one host-owned mutation path. Unlike ``set(_:)``, this
/// method does not write to ``store``.
public func acceptCommittedValue(_ value: Value) {
current = value
}

/// Removes the override; ``current`` updates when the stream observes
/// the reset.
public func reset() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ public protocol SettingsHostActions: AnyObject {
/// window scene so the package can't open it directly.
func openTerminalConfigWindow()

/// Persists an explicit menu-bar-only preference change in the host app.
///
/// The host pairs the visible `app.menuBarOnly` setting with any hidden
/// safety marker it needs before changing the process activation policy.
///
/// - Returns: `true` when the host handled persistence for this change.
@discardableResult
func setMenuBarOnly(_ enabled: Bool) -> Bool

/// Opens the iOS pairing window, which shows a scannable QR code for
/// pairing an iPhone with this Mac. The host owns the window so the
/// package can't open it directly.
Expand Down Expand Up @@ -142,6 +151,9 @@ public protocol SettingsHostActions: AnyObject {
public extension SettingsHostActions {
func openMobilePairingWindow() {}

/// Default no-op for package previews and tests that have no activation-policy host.
func setMenuBarOnly(_ enabled: Bool) -> Bool { false }

func browserHistoryEntryCount() -> Int? { nil }

/// Default: no status, for hosts without a live mobile service (previews/tests).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,11 @@ public struct AppSection: View {
String(localized: "settings.app.menuBarOnly", defaultValue: "Menu Bar Only"),
subtitle: String(localized: "settings.app.menuBarOnly.subtitle", defaultValue: "Hide the Dock icon and Cmd+Tab entry. Use the menu bar item to show cmux.")
) {
Toggle("", isOn: Binding(get: { menuBarOnly.current }, set: { menuBarOnly.set($0) }))
Toggle("", isOn: Binding(get: { menuBarOnly.current }, set: { enabled in
if hostActions.setMenuBarOnly(enabled) {
menuBarOnly.acceptCommittedValue(enabled)
}
}))
.labelsHidden()
.controlSize(.small)
.accessibilityIdentifier("SettingsMenuBarOnlyToggle")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,24 @@ import Testing
model.reset()
#expect(model.current == false)
}

@Test func acceptCommittedValueUpdatesCurrentWithoutStoreWrite() {
let suiteName = "defaults-value-model-committed-value"
UserDefaults(suiteName: suiteName)?.removePersistentDomain(forName: suiteName)
let store = UserDefaultsSettingsStore(
defaults: UserDefaults(suiteName: suiteName)!
)
let key = SettingCatalog().betaFeatures.extensions
let (stream, _) = AsyncStream<Bool>.makeStream()
let model = DefaultsValueModel(
store: store,
key: key,
makeStream: { stream }
)

#expect(model.current == false)
model.acceptCommittedValue(true)
#expect(model.current == true)
#expect(UserDefaults(suiteName: suiteName)?.object(forKey: key.userDefaultsKey) == nil)
}
}
13 changes: 10 additions & 3 deletions Sources/App/MenuBarExtraController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -519,13 +519,20 @@ enum MenuBarExtraSettings {

enum MenuBarOnlySettings {
static let menuBarOnlyKey = "menuBarOnly"
static let explicitEnableKey = "menuBarOnlyExplicitlyEnabled.v1"
static let defaultMenuBarOnly = false

static func isEnabled(defaults: UserDefaults = .standard) -> Bool {
if defaults.object(forKey: menuBarOnlyKey) == nil {
return defaultMenuBarOnly
guard defaults.object(forKey: menuBarOnlyKey) != nil, defaults.bool(forKey: menuBarOnlyKey) else { return defaultMenuBarOnly }
if defaults.object(forKey: explicitEnableKey) != nil {
return defaults.bool(forKey: explicitEnableKey)
}
return defaults.bool(forKey: menuBarOnlyKey)
return !legacyCommandPaletteOneShotLikelyEnabledMenuBarOnly(defaults: defaults)
}

static func setEnabled(_ enabled: Bool, defaults: UserDefaults = .standard) {
defaults.set(enabled, forKey: menuBarOnlyKey)
defaults.set(enabled, forKey: explicitEnableKey)
}

static func activationPolicy(defaults: UserDefaults = .standard) -> NSApplication.ActivationPolicy {
Expand Down
2 changes: 2 additions & 0 deletions Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
if isRunningUnderXCTest {
NSApp.setActivationPolicy(.regular)
} else {
MenuBarOnlySettings.normalizeLegacyStoredPreference()
syncActivationPolicy()
}
StartupBreadcrumbLog.append("appDelegate.didFinish.activationPolicy.synced")
Expand Down Expand Up @@ -8418,6 +8419,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
}

private func syncApplicationPresentationPreferences(defaults: UserDefaults = .standard) {
MenuBarOnlySettings.normalizeLegacyStoredPreference(defaults: defaults)
syncActivationPolicy(defaults: defaults)
syncMenuBarExtraVisibility(defaults: defaults)
}
Expand Down
32 changes: 21 additions & 11 deletions Sources/CommandPalette/CommandPaletteSettingsToggle.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import Foundation
import CmuxSettings

extension MenuBarOnlySettings {
static let legacyCommandPaletteUsageKey = "commandPalette.commandUsage.v1"
static let legacyCommandPaletteMenuBarOnlyCommandId = "palette.toggleSetting.menuBarOnly"

static func normalizeLegacyStoredPreference(defaults: UserDefaults = .standard) {
guard defaults.object(forKey: menuBarOnlyKey) != nil,
defaults.bool(forKey: menuBarOnlyKey),
defaults.object(forKey: explicitEnableKey) == nil else { return }
setEnabled(!legacyCommandPaletteOneShotLikelyEnabledMenuBarOnly(defaults: defaults), defaults: defaults)
}

static func legacyCommandPaletteOneShotLikelyEnabledMenuBarOnly(defaults: UserDefaults = .standard) -> Bool {
guard let data = defaults.data(forKey: legacyCommandPaletteUsageKey) else { return false }
guard let history = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return true }
guard history.count == 1, let entry = history[legacyCommandPaletteMenuBarOnlyCommandId] else { return false }
guard let usage = entry as? [String: Any] else { return true }
guard (usage["useCount"] as? NSNumber)?.intValue == 1 else { return false }
return ((usage["lastUsedAt"] as? NSNumber)?.doubleValue ?? 0) > 0
}
}

struct CommandPaletteSettingToggleDescriptor: Sendable {
let commandId: String
let settingsKey: String
Expand Down Expand Up @@ -268,17 +289,6 @@ enum CommandPaletteSettingsToggleCommands {
defaultValue: NotificationBadgeSettings.defaultDockBadgeEnabled,
defaultsKey: NotificationBadgeSettings.dockBadgeEnabledKey
),
CommandPaletteSettingToggleDescriptor(
commandId: commandIdPrefix + "menuBarOnly",
settingsKey: "app.menuBarOnly",
title: {
String(localized: "settings.app.menuBarOnly", defaultValue: "Menu Bar Only")
},
sectionTitle: app,
keywords: ["app.menuBarOnly", "menu", "bar", "dock", "cmd-tab", "app", "switcher"],
defaultValue: MenuBarOnlySettings.defaultMenuBarOnly,
defaultsKey: MenuBarOnlySettings.menuBarOnlyKey
),
CommandPaletteSettingToggleDescriptor(
commandId: commandIdPrefix + "showInMenuBar",
settingsKey: "notifications.showInMenuBar",
Expand Down
5 changes: 5 additions & 0 deletions Sources/HostSettingsActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ final class HostSettingsActions: SettingsHostActions {
window.orderFrontRegardless()
}

func setMenuBarOnly(_ enabled: Bool) -> Bool {
MenuBarOnlySettings.setEnabled(enabled)
return true
}

func openMobilePairingWindow() {
MobilePairingWindowController.shared.show()
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/KeyboardShortcutSettingsFileStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ final class CmuxSettingsFileStore {
}
if let value = jsonBool(section["menuBarOnly"]) {
snapshot.managedUserDefaults[MenuBarOnlySettings.menuBarOnlyKey] = .bool(value)
if value {
snapshot.managedUserDefaults[MenuBarOnlySettings.explicitEnableKey] = .bool(true)
}
}
if let raw = jsonString(section["windowTitleTemplate"]) { snapshot.managedUserDefaults[WindowTitleTemplate.userDefaultsKey] = .string(raw) } else if section.keys.contains("windowTitleTemplate") { logInvalid("app.windowTitleTemplate", sourcePath: sourcePath) }
if let raw = jsonString(section["newWorkspacePlacement"]) {
Expand Down
4 changes: 4 additions & 0 deletions cmux.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@
A50014F5 /* MarkdownWebRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50014F4 /* MarkdownWebRenderer.swift */; };
A50014F9 /* MarkdownWebSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50014F8 /* MarkdownWebSupport.swift */; };
1A8BEE693C9E4C3190CB7F20 /* MenuBarExtraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7934BB35B66491B1BCA8064 /* MenuBarExtraController.swift */; };
606500010000000000000001 /* MenuBarOnlyActivationPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606500010000000000000002 /* MenuBarOnlyActivationPolicyTests.swift */; };
3865A0033865A0033865A003 /* MenubarSearchPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3865B0033865B0033865B003 /* MenubarSearchPopover.swift */; };
E1000000A1B2C3D4E5F60718 /* MenuKeyEquivalentRoutingUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1000001A1B2C3D4E5F60718 /* MenuKeyEquivalentRoutingUITests.swift */; };
C0DE3150A00000000000001 /* MinimalModeSidebarControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DE3150A00000000000002 /* MinimalModeSidebarControls.swift */; };
Expand Down Expand Up @@ -1327,6 +1328,7 @@
A50014F4 /* MarkdownWebRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownWebRenderer.swift; sourceTree = "<group>"; };
A50014F8 /* MarkdownWebSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownWebSupport.swift; sourceTree = "<group>"; };
C7934BB35B66491B1BCA8064 /* MenuBarExtraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/MenuBarExtraController.swift; sourceTree = "<group>"; };
606500010000000000000002 /* MenuBarOnlyActivationPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarOnlyActivationPolicyTests.swift; sourceTree = "<group>"; };
3865B0033865B0033865B003 /* MenubarSearchPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Search/MenubarSearchPopover.swift; sourceTree = "<group>"; };
E1000001A1B2C3D4E5F60718 /* MenuKeyEquivalentRoutingUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuKeyEquivalentRoutingUITests.swift; sourceTree = "<group>"; };
C0DE3150A00000000000002 /* MinimalModeSidebarControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update/MinimalModeSidebarControls.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2595,6 +2597,7 @@
C3408A000000000000000004 /* RightSidebarCommandPaletteTests.swift */,
C0DE48000000000000000002 /* SidebarProviderMenuRegressionTests.swift */,
C0DEF4120000000000000002 /* CommandPaletteSettingsToggleTests.swift */,
606500010000000000000002 /* MenuBarOnlyActivationPolicyTests.swift */,
B37A0000000000000000000C /* FileExplorerStateModePersistenceTests.swift */,
BC39DE4B96D1931C52AF7D68 /* SidebarOrderingTests.swift */,
86544CEFA1CA33CA9225FB6E /* MobileWorkspaceListFidelityTests.swift */,
Expand Down Expand Up @@ -3801,6 +3804,7 @@
17C9F5BA0DD14EDC8C3E5001 /* MainWindowVisibilityControllerTests.swift in Sources */,
D6069002D6069002D6069002 /* MarkdownMermaidZoomTests.swift in Sources */,
D3664002D3664002D3664002 /* MarkdownPanelTests.swift in Sources */,
606500010000000000000001 /* MenuBarOnlyActivationPolicyTests.swift in Sources */,
6AA2F2E8BEB19ED6B8E125DB /* MobileHostAuthorizationTests.swift in Sources */,
DE71CE000000000000000006 /* MobileHostNetworkPathRefreshTests.swift in Sources */,
C0DE10510000000000000001 /* MobileHostServiceSettingsTests.swift in Sources */,
Expand Down
Loading
Loading