Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1b2ae13
Add configurable Dock max width
austinywang May 19, 2026
b9357d0
Address Dock max width review feedback
austinywang May 19, 2026
4c4f0e7
Add Dock max width settings UI
austinywang May 19, 2026
15f37ab
Localize Dock max width settings
austinywang May 19, 2026
1af444a
merge: resolve conflicts with main
austinywang May 26, 2026
3320119
Localize Dock width settings in Khmer
austinywang May 26, 2026
006d9b1
Fix Dock max width warning budget
austinywang May 26, 2026
8592831
Merge origin/main into issue-4375-dock-max-width-config
austinywang Jun 6, 2026
68895bf
Fix right sidebar width CGFloat conversion
austinywang Jun 6, 2026
164ca31
Expose Dock width policy through app module
austinywang Jun 6, 2026
69320e3
Preserve remembered Dock max width
austinywang Jun 6, 2026
7e63912
Clamp Dock max width policy consistently
austinywang Jun 6, 2026
c6a3fc8
Share Dock max width default cap
austinywang Jun 6, 2026
d13636c
Merge remote-tracking branch 'origin/main' into issue-4375-dock-max-w…
austinywang Jun 14, 2026
51ad5da
fix: make right sidebar width policy instantiable
austinywang Jun 14, 2026
de69fea
Merge remote-tracking branch 'origin/main' into issue-4375-dock-max-w…
austinywang Jun 14, 2026
c51e77d
fix: align settings json mappings with catalog keys
austinywang Jun 14, 2026
042b0f5
fix: bound sidebar metadata block rendering
austinywang Jun 14, 2026
854860f
fix: use catalog key for sidebar branch layout
austinywang Jun 14, 2026
93ab5f6
fix: bound sidebar workspace row text
austinywang Jun 14, 2026
4c35a93
Merge remote-tracking branch 'origin/main' into issue-4375-dock-max-w…
austinywang Jun 14, 2026
6c3bcaf
Merge remote-tracking branch 'origin/main' into issue-4375-dock-max-w…
austinywang Jun 14, 2026
e0e5250
Merge remote-tracking branch 'origin/main' into issue-4375-dock-max-w…
austinywang Jun 15, 2026
f964f62
Merge remote-tracking branch 'origin/main' into issue-4375-dock-max-w…
austinywang Jun 15, 2026
8e023d8
Merge remote-tracking branch 'origin/main' into issue-4375-dock-max-w…
austinywang Jun 15, 2026
dd08243
Merge remote-tracking branch 'origin/main' into issue-4375-dock-max-w…
austinywang Jun 15, 2026
c4b84a2
fix: use BrowserSearchSettingsStore after main rename (merge fixup)
austinywang Jun 15, 2026
c61fe60
merge: resolve automation settings parsing against #5547 (keep data-d…
austinywang Jun 15, 2026
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
2 changes: 1 addition & 1 deletion .claude/scheduled_tasks.lock
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"sessionId":"5c2a62d0-99c2-42c0-9452-f67b6fbaacda","pid":75304,"procStart":"Thu Jun 4 23:34:54 2026","acquiredAt":1780616564932}
{"sessionId":"4311956b-e43f-405e-8b49-c3990850966d","pid":43365,"procStart":"Sun Jun 14 21:55:11 2026","acquiredAt":1781485012958}
8 changes: 4 additions & 4 deletions .github/swift-file-length-budget.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Reduce counts as files shrink. CI fails if tracked files exceed this budget.
34202 CLI/cmux.swift
17772 Sources/AppDelegate.swift
16698 Sources/ContentView.swift
16814 Sources/ContentView.swift
14785 Sources/TerminalController.swift
13358 Sources/Panels/BrowserPanel.swift
12292 Sources/Workspace.swift
Expand All @@ -13,8 +13,8 @@
7912 Sources/Panels/BrowserPanelView.swift
7355 cmuxTests/WorkspaceUnitTests.swift
7221 cmuxTests/WorkspaceRemoteConnectionTests.swift
6363 cmuxTests/GhosttyConfigTests.swift
6317 cmuxTests/SessionPersistenceTests.swift
6299 cmuxTests/GhosttyConfigTests.swift
6153 CLI/cmux_open.swift
6096 Sources/TabManager.swift
6074 Sources/TextBoxInput.swift
Expand Down Expand Up @@ -47,8 +47,8 @@
2117 cmuxTests/CmuxConfigTests.swift
2092 cmuxTests/ShortcutAndCommandPaletteTests.swift
2070 Sources/SessionPersistence.swift
2065 Sources/KeyboardShortcutSettingsFileStore.swift
1949 Sources/Panels/BrowserWebAuthnSupport.swift
1936 Sources/KeyboardShortcutSettingsFileStore.swift
1860 cmuxTests/NotificationAndMenuBarTests.swift
1794 Sources/SessionIndexStore.swift
1777 Sources/RestorableAgentSession.swift
Expand Down Expand Up @@ -152,8 +152,8 @@
614 cmuxTests/SessionIndexViewTests.swift
611 Sources/TerminalController+ControlPaneContext.swift
608 Packages/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceGroupCoordinator.swift
605 Sources/SettingsNavigation.swift
604 Packages/CmuxCommandPalette/Tests/CmuxCommandPaletteTests/CommandPaletteNucleoFFITests.swift
603 Sources/SettingsNavigation.swift
599 Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchSanitizerPrimaryPolicies.swift
596 cmuxTests/CmuxEventBusTests.swift
594 Sources/SessionIndexModels.swift
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ public struct SidebarCatalogSection: SettingCatalogSection {
userDefaultsKey: "sidebarShowStatusPills"
)

public let rightMaxWidth = DefaultsKey<Double>(
id: "sidebar.rightMaxWidth",
defaultValue: RightSidebarWidthSettings.noOverrideValue,
userDefaultsKey: RightSidebarWidthSettings.maxWidthKey
)

public let rememberedRightMaxWidth = DefaultsKey<Double>(
id: "sidebar.rightMaxWidth.remembered",
defaultValue: RightSidebarWidthSettings.defaultConfiguredMaximumWidth,
userDefaultsKey: RightSidebarWidthSettings.rememberedMaxWidthKey
)

public let activeTabIndicatorStyle = DefaultsKey<String>(
id: "sidebar.activeTabIndicatorStyle",
defaultValue: "leftRail",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import Foundation

/// Policy values and normalization helpers for the right sidebar width override.
public struct RightSidebarWidthSettings: Sendable {
/// Creates a stateless right sidebar width policy value.
public init() {}

/// The `cmux.json` key under `sidebar` that stores the right sidebar maximum width.
public static let jsonKey = "rightMaxWidth"

/// The dotted settings path for the right sidebar maximum width override.
public static let settingsPath = "sidebar.rightMaxWidth"

/// The `UserDefaults` key that stores the active right sidebar maximum width override.
public static let maxWidthKey = "rightSidebarMaxWidth"

/// The `UserDefaults` key that remembers the previous right sidebar maximum width override.
public static let rememberedMaxWidthKey = "rightSidebarRememberedMaxWidth"

/// The stored sentinel value that means the built-in dynamic width cap is active.
public static let noOverrideValue = -1.0

/// The smallest allowed right sidebar width, in points.
public static let minimumWidth = 276.0

/// The built-in right sidebar maximum width, in points, used when no override is active.
public static let builtInMaximumWidth = 1200.0

/// The width restored when enabling the override without a remembered value.
public static let defaultConfiguredMaximumWidth = builtInMaximumWidth

/// The largest width accepted by settings editors and imported `cmux.json` values.
public static let settingsEditorMaximumWidth = 4096.0

/// Returns the effective configured maximum width for a stored value.
///
/// - Parameter storedValue: The persisted width override value.
/// - Returns: A clamped width when the stored value enables the override, or `nil`.
public func configuredMaximumWidth(from storedValue: Double) -> Double? {
guard storedValue.isFinite, storedValue > 0 else {
return nil
}
return clampedSettingsEditorMaximumWidth(storedValue)
}

/// Clamps a settings-editor width to the supported range.
///
/// - Parameter value: The requested width in points.
/// - Returns: A finite rounded width within the settings editor bounds.
public func clampedSettingsEditorMaximumWidth(_ value: Double) -> Double {
guard value.isFinite else {
return Self.defaultConfiguredMaximumWidth
}
return min(Self.settingsEditorMaximumWidth, max(Self.minimumWidth, value.rounded()))
}

/// Returns the remembered width to restore for a persisted value.
///
/// - Parameter storedValue: The stored remembered width.
/// - Returns: A clamped remembered width, or the default configured width.
public func rememberedMaximumWidth(from storedValue: Double) -> Double {
guard let configuredMaximumWidth = configuredMaximumWidth(from: storedValue) else {
return Self.defaultConfiguredMaximumWidth
}
return clampedSettingsEditorMaximumWidth(configuredMaximumWidth)
}

/// Returns the width displayed in settings for the active and remembered values.
///
/// - Parameters:
/// - activeStoredValue: The currently active stored override value.
/// - rememberedStoredValue: The remembered override value used when inactive.
/// - Returns: The clamped editor width to show.
public func editorMaximumWidth(activeStoredValue: Double, rememberedStoredValue: Double) -> Double {
if let configuredMaximumWidth = configuredMaximumWidth(from: activeStoredValue) {
return clampedSettingsEditorMaximumWidth(configuredMaximumWidth)
}
return rememberedMaximumWidth(from: rememberedStoredValue)
}

/// Returns the stored value to write when enabling the override.
///
/// - Parameter rememberedStoredValue: The remembered override value.
/// - Returns: The clamped override width to store as active.
public func storedMaximumWidthWhenEnabling(rememberedStoredValue: Double) -> Double {
rememberedMaximumWidth(from: rememberedStoredValue)
Comment thread
cursor[bot] marked this conversation as resolved.
}

/// Returns the remembered value to preserve before disabling the override.
///
/// - Parameters:
/// - activeStoredValue: The currently active stored override value.
/// - rememberedStoredValue: The remembered override value used as fallback.
/// - Returns: The clamped width to store as remembered.
public func storedRememberedMaximumWidth(activeStoredValue: Double, rememberedStoredValue: Double) -> Double {
editorMaximumWidth(activeStoredValue: activeStoredValue, rememberedStoredValue: rememberedStoredValue)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Testing
@testable import CmuxSettings

@Suite("RightSidebarWidthSettings")
struct RightSidebarWidthSettingsTests {
private let settings = RightSidebarWidthSettings()

@Test func disabledOverrideRestoresRememberedCustomMaximumWhenEnabledAgain() {
let restored = settings.storedMaximumWidthWhenEnabling(
rememberedStoredValue: 1_234
)

#expect(restored == 1_234)
}

@Test func invalidRememberedMaximumFallsBackToDefaultWhenEnabled() {
let restored = settings.storedMaximumWidthWhenEnabling(
rememberedStoredValue: RightSidebarWidthSettings.noOverrideValue
)

#expect(restored == RightSidebarWidthSettings.defaultConfiguredMaximumWidth)
}

@Test func activeCustomMaximumWinsOverRememberedValueForEditor() {
let editorValue = settings.editorMaximumWidth(
activeStoredValue: 1_500,
rememberedStoredValue: 900
)

#expect(editorValue == 1_500)
}

@Test func configuredMaximumWidthIsClampedToEditorRange() throws {
let configured = try #require(
settings.configuredMaximumWidth(from: 10_000)
)

#expect(configured == RightSidebarWidthSettings.settingsEditorMaximumWidth)
}

@Test func rememberedCustomMaximumIsClampedToEditorRange() {
let restored = settings.storedMaximumWidthWhenEnabling(
rememberedStoredValue: 10_000
)

#expect(restored == RightSidebarWidthSettings.settingsEditorMaximumWidth)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ extension Array where Element == CuratedSettingEntry {
.init(section: .sidebarAppearance, id: "show-log", title: "Show Latest Log in Sidebar", synonyms: "sidebar.showLog log status latest message imperative"),
.init(section: .sidebarAppearance, id: "show-progress", title: "Show Progress in Sidebar", synonyms: "sidebar.showProgress progress bar percent status set_progress"),
.init(section: .sidebarAppearance, id: "show-metadata", title: "Show Custom Metadata in Sidebar", synonyms: "sidebar.showCustomMetadata metadata meta report_meta status custom block"),
.init(section: .sidebarAppearance, id: "right-max-width", title: "Dock Max Width", synonyms: "sidebar.rightMaxWidth dock right sidebar max width terminal reservation cap logs lazygit"),

// Mobile
.init(section: .mobile, id: "pairDevice", title: "Pair a Device", synonyms: "pair pairing add device qr qr code scan iphone ipad ios mobile tailscale connect onboarding sign in"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI
public struct SidebarSection: View {
private let catalog: SettingCatalog
private let hostActions: SettingsHostActions
private let rightSidebarWidthSettings = RightSidebarWidthSettings()

@State private var sidebarFont: SettingsFontSize
@State private var fontSaveFailed = false
Expand All @@ -33,6 +34,8 @@ public struct SidebarSection: View {
@State private var showLog: DefaultsValueModel<Bool>
@State private var showProgress: DefaultsValueModel<Bool>
@State private var showMetadata: DefaultsValueModel<Bool>
@State private var rightMaxWidth: DefaultsValueModel<Double>
@State private var rememberedRightMaxWidth: DefaultsValueModel<Double>

public init(defaultsStore: UserDefaultsSettingsStore, catalog: SettingCatalog, hostActions: SettingsHostActions) {
self.catalog = catalog
Expand All @@ -57,6 +60,8 @@ public struct SidebarSection: View {
_showLog = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.sidebar.showLog))
_showProgress = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.sidebar.showProgress))
_showMetadata = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.sidebar.showCustomMetadata))
_rightMaxWidth = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.sidebar.rightMaxWidth))
_rememberedRightMaxWidth = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.sidebar.rememberedRightMaxWidth))
}

public var body: some View {
Expand All @@ -77,6 +82,62 @@ public struct SidebarSection: View {
}
}

private var rightMaxWidthOverrideEnabled: Bool {
rightMaxWidth.current.isFinite && rightMaxWidth.current > 0
}

private var rightMaxWidthOverrideBinding: Binding<Bool> {
Binding(
get: { rightMaxWidthOverrideEnabled },
set: { enabled in
if enabled {
let restored = rightSidebarWidthSettings.storedMaximumWidthWhenEnabling(
rememberedStoredValue: rememberedRightMaxWidth.current
)
rememberedRightMaxWidth.set(restored)
rightMaxWidth.set(restored)
} else {
rememberedRightMaxWidth.set(
rightSidebarWidthSettings.storedRememberedMaximumWidth(
activeStoredValue: rightMaxWidth.current,
rememberedStoredValue: rememberedRightMaxWidth.current
)
)
rightMaxWidth.set(RightSidebarWidthSettings.noOverrideValue)
}
}
)
Comment thread
cursor[bot] marked this conversation as resolved.
}

private var rightMaxWidthEditorBinding: Binding<Double> {
Binding(
get: {
rightSidebarWidthSettings.editorMaximumWidth(
activeStoredValue: rightMaxWidth.current,
rememberedStoredValue: rememberedRightMaxWidth.current
)
},
set: {
let clamped = clampedRightMaxWidth($0)
rememberedRightMaxWidth.set(clamped)
if rightMaxWidthOverrideEnabled {
rightMaxWidth.set(clamped)
}
}
)
}

private var rightMaxWidthSubtitle: String {
if rightMaxWidthOverrideEnabled {
return String(localized: "settings.sidebar.rightMaxWidth.subtitleOn", defaultValue: "The Dock can grow past the built-in width cap while preserving terminal space.")
}
return String(localized: "settings.sidebar.rightMaxWidth.subtitleOff", defaultValue: "Use the built-in dynamic cap that keeps extra terminal space reserved.")
}

private func clampedRightMaxWidth(_ value: Double) -> Double {
rightSidebarWidthSettings.clampedSettingsEditorMaximumWidth(value)
}

@ViewBuilder
private var mainCard: some View {
SettingsCard {
Expand Down Expand Up @@ -135,6 +196,31 @@ public struct SidebarSection: View {
}
SettingsCardDivider()

SettingsCardRow(
configurationReview: .json("sidebar.rightMaxWidth"),
String(localized: "settings.sidebar.rightMaxWidth", defaultValue: "Dock Max Width"),
subtitle: rightMaxWidthSubtitle,
controlWidth: 250
) {
HStack(spacing: 8) {
Toggle("", isOn: rightMaxWidthOverrideBinding)
.labelsHidden()
.toggleStyle(.switch)
.controlSize(.small)
.accessibilityLabel(String(localized: "settings.sidebar.rightMaxWidth.toggle", defaultValue: "Use custom Dock max width"))

TextField("", value: rightMaxWidthEditorBinding, format: .number)
.textFieldStyle(.roundedBorder)
.frame(width: 72)
.disabled(!rightMaxWidthOverrideEnabled)
.accessibilityLabel(String(localized: "settings.sidebar.rightMaxWidth", defaultValue: "Dock Max Width"))

Text(String(localized: "settings.sidebar.rightMaxWidth.unit", defaultValue: "pt"))
.foregroundStyle(.secondary)
}
}
SettingsCardDivider()

SettingsCardRow(
configurationReview: .json("sidebar.hideAllDetails"),
String(localized: "settings.app.hideAllSidebarDetails", defaultValue: "Hide All Sidebar Details"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ struct SettingsRowAnchorResolutionTests {
"sidebar.openPortLinksInCmuxBrowser",
"sidebar.openPullRequestLinksInCmuxBrowser",
"sidebar.pathLastSegmentOnly",
"sidebar.rightMaxWidth",
"sidebar.showBranchDirectory",
"sidebar.showCustomMetadata",
"sidebar.showLog",
Expand Down
Loading
Loading