Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5645323
feat(sidebar): add configurable workspace font size
austinywang May 26, 2026
b3adb26
Merge remote-tracking branch 'origin/main' into issue-2643-allow-cust…
austinywang May 26, 2026
cd5b056
fix(sidebar): load sidebar font size off main actor
austinywang May 26, 2026
96530e3
merge: sync with main
austinywang May 30, 2026
b29ab4a
fix: address sidebar font review feedback
austinywang May 30, 2026
3e9fab6
feat: add sidebar font size controls
austinywang May 30, 2026
98e86e4
fix: prefer host zig for helper builds
austinywang May 30, 2026
d472eb3
fix: prefer apple silicon zig on mac runners
austinywang May 30, 2026
9a5875a
fix: prebuild ghostty helper in release workflows
austinywang May 30, 2026
e47150b
fix: use homebrew zig on mac ci
austinywang May 30, 2026
1104afa
fix: keep release helper skip arch-valid
austinywang May 30, 2026
1108491
fix: address sidebar font size settings feedback
austinywang May 30, 2026
41d3a45
fix: cancel sidebar font size refresh tasks
austinywang May 30, 2026
0deaee1
test: update cli config help contract
austinywang May 30, 2026
2590b47
feat: add workspace tab bar font size control + fix font-size slider …
austinywang May 30, 2026
50ba58e
Merge remote-tracking branch 'origin/main' into issue-2643-allow-cust…
austinywang May 31, 2026
b4d9cd7
fix: render font-size sliders in CmuxSettingsUI (settings moved to pa…
austinywang May 31, 2026
aa67955
Merge remote-tracking branch 'origin/main' into tab-icon-scaling-wt
austinywang May 31, 2026
e72fe3e
fix: surface font-size save failures in Settings
austinywang Jun 1, 2026
df961f7
fix: localize font-size value label ("%@ pt")
austinywang Jun 1, 2026
bfa4262
fix: seed sidebar font scale from on-disk config at launch
austinywang Jun 1, 2026
b505523
fix: move font-size config disk I/O off the main actor
austinywang Jun 1, 2026
8f1fc6e
fix: serialize font writes; harden config path/parse + stub arch
austinywang Jun 1, 2026
d397be5
fix: strip BOM when parsing Ghostty config at runtime load
austinywang Jun 1, 2026
c1cfd4b
chore: address CodeRabbit nitpicks (host-arch dedupe, localized format)
austinywang Jun 1, 2026
c41daff
fix: render font-size value label via localizedStringWithFormat
austinywang Jun 1, 2026
fdf1876
vendor/bonsplit: scale tab + control icons with tab bar font size
austinywang Jun 1, 2026
9b85a9c
feat: cap tab bar font size at 14pt
austinywang Jun 1, 2026
22f4094
fix: cancel in-flight font-size save before starting a new one
austinywang Jun 1, 2026
04f275f
test: use in-range 14 for tab-bar font loader test (was 16, now clamped)
austinywang Jun 1, 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
173 changes: 171 additions & 2 deletions CLI/CMUXCLI+Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,34 @@ extension CMUXCLI {
switch subcommand {
case "help":
print(configUsage())
case "get":
guard args.count == 2, args[1].lowercased() == CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey else {
throw CLIError(message: "Usage: cmux config get sidebar-font-size")
}
try runConfigGetSidebarFontSize(jsonOutput: wantsJSON)
case "set":
guard args.count == 3, args[1].lowercased() == CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey else {
throw CLIError(message: "Usage: cmux config set sidebar-font-size <points>")
}
try runConfigSetSidebarFontSize(
rawValue: args[2],
socketPath: socketPath,
explicitPassword: explicitPassword,
jsonOutput: wantsJSON
)
case "sidebar-font-size":
if args.count == 1 {
try runConfigGetSidebarFontSize(jsonOutput: wantsJSON)
} else if args.count == 2 {
try runConfigSetSidebarFontSize(
rawValue: args[1],
socketPath: socketPath,
explicitPassword: explicitPassword,
jsonOutput: wantsJSON
)
} else {
throw CLIError(message: "Usage: cmux config sidebar-font-size [points]")
}
case "path", "paths":
guard args.count == 1 else {
throw CLIError(message: "Usage: cmux config path")
Expand Down Expand Up @@ -62,21 +90,30 @@ extension CMUXCLI {
func configCommandDoesNotNeedSocket(_ commandArgs: [String]) -> Bool {
let parsedArgs = docsSettingsArguments(commandArgs)
let subcommand = parsedArgs.arguments.first?.lowercased() ?? "help"
if subcommand == "get" {
return true
}
if subcommand == CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey {
return parsedArgs.arguments.count == 1
}
Comment thread
cursor[bot] marked this conversation as resolved.
return hasHelpRequest(beforeSeparator: parsedArgs.head) ||
["help", "path", "paths", "docs", "documentation", "doctor", "check", "validate"].contains(subcommand)
}

func configUsage() -> String {
return """
Usage: cmux config <doctor|check|validate|path|paths|docs|documentation|reload>
Usage: cmux config <doctor|check|validate|path|paths|docs|documentation|reload|get|set|sidebar-font-size>

Inspect cmux.json, print configuration references, or reload the running app.
Inspect cmux.json, print configuration references, update selected Ghostty config keys, or reload the running app.

Subcommands:
doctor|check|validate [--path <path>] Validate JSONC syntax for cmux config files.
path|paths Print cmux.json paths, docs URL, and schema URL.
docs|documentation Print the same output as `cmux docs settings`.
reload Reload Ghostty config + cmux.json and refresh terminals (alias for `cmux reload-config`).
get sidebar-font-size Print the current sidebar font size.
set sidebar-font-size <points> Set sidebar text size, clamped to 10-20 pt, then reload if cmux is running.
sidebar-font-size [points] Get or set sidebar text size.

Config files:
\(Self.primarySettingsDisplayPath)
Expand All @@ -89,6 +126,8 @@ extension CMUXCLI {
Examples:
cmux config doctor
cmux config doctor --path .cmux/cmux.json
cmux config set sidebar-font-size 14
cmux config sidebar-font-size 12.5
cmux config reload
"""
}
Expand Down Expand Up @@ -135,6 +174,136 @@ extension CMUXCLI {
print(" cmux reload-config")
}

private func runConfigGetSidebarFontSize(jsonOutput: Bool) throws {
let url = try cmuxGhosttyConfigURLForCLI()
let contents = (try? String(contentsOf: url, encoding: .utf8)) ?? ""
let configuredValue = CmuxGhosttyConfigSettingEditor.parsedSidebarFontSize(in: contents)
let effectiveValue = configuredValue ?? CmuxGhosttyConfigSettingEditor.defaultSidebarFontSize
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
let formattedValue = CmuxGhosttyConfigSettingEditor.formattedSidebarFontSize(effectiveValue)

if jsonOutput {
var payload: [String: Any] = [
"key": CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey,
"value": effectiveValue,
"formatted": formattedValue,
"path": url.path,
"configured": configuredValue != nil,
]
if let configuredValue {
payload["configured_value"] = configuredValue
}
print(jsonString(payload))
return
}

print("\(CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey) = \(formattedValue)")
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
print("path: \(Self.tildePath(url.path))")
}

private func runConfigSetSidebarFontSize(
rawValue: String,
socketPath: String?,
explicitPassword: String?,
jsonOutput: Bool
) throws {
guard let requestedValue = Double(rawValue), requestedValue.isFinite else {
throw CLIError(message: "sidebar-font-size requires a numeric point size")
}

let value = CmuxGhosttyConfigSettingEditor.clampedSidebarFontSize(requestedValue)
let formattedValue = CmuxGhosttyConfigSettingEditor.formattedSidebarFontSize(value)
let url = try cmuxGhosttyConfigURLForCLI()
try CmuxGhosttyConfigSettingEditor.writeSetting(
key: CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey,
value: formattedValue,
to: url
)

let reloadResult = reloadConfigAfterSidebarFontSizeSet(
socketPath: socketPath,
explicitPassword: explicitPassword
)

if jsonOutput {
var payload: [String: Any] = [
"ok": true,
"key": CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey,
"value": value,
"formatted": formattedValue,
"path": url.path,
"reload": reloadResult.status,
"clamped": value != requestedValue,
]
if let message = reloadResult.message {
payload["reload_message"] = message
}
print(jsonString(payload))
return
}

switch reloadResult.status {
case "reloaded":
print("OK \(CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey) = \(formattedValue) (reloaded)")
case "failed":
print("OK \(CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey) = \(formattedValue) (saved; reload failed)")
if let message = reloadResult.message {
print("reload: \(message)")
}
print("Run `cmux config reload` after cmux is running to apply it.")
default:
print("OK \(CmuxGhosttyConfigSettingEditor.sidebarFontSizeKey) = \(formattedValue) (saved)")
print("Run `cmux config reload` to apply it.")
}
print("path: \(Self.tildePath(url.path))")
}

private func cmuxGhosttyConfigURLForCLI() throws -> URL {
let environment = ProcessInfo.processInfo.environment
let fileManager = FileManager.default
guard let appSupportDirectory = CmuxApplicationSupportDirectories
.userDirectories(environment: environment, fileManager: fileManager)
.first else {
throw CLIError(message: "Could not resolve the user Application Support directory")
}
let bundleIdentifier = normalizedConfigValue(environment["CMUX_BUNDLE_ID"])
?? CLISocketPathResolver.currentAppBundleIdentifier()
return CmuxGhosttyConfigPathResolver.activeOrEditableConfigURL(
currentBundleIdentifier: bundleIdentifier,
appSupportDirectory: appSupportDirectory,
fileManager: fileManager
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
}

private func reloadConfigAfterSidebarFontSizeSet(
socketPath: String?,
explicitPassword: String?
) -> (status: String, message: String?) {
guard let socketPath else {
return ("skipped", nil)
}
do {
let client = try connectClient(
socketPath: socketPath,
explicitPassword: explicitPassword,
launchIfNeeded: false
)
defer { client.close() }
let response = try client.send(command: "reload_config")
if response.hasPrefix("ERROR:") {
return ("failed", response)
}
return ("reloaded", response)
} catch {
return ("failed", Self.configDoctorErrorMessage(error))
}
}

private func normalizedConfigValue(_ value: String?) -> String? {
guard let value else { return nil }
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? nil : trimmed
}

private struct ConfigDoctorOptions {
let paths: [String]
}
Expand Down
102 changes: 102 additions & 0 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -110828,6 +110828,23 @@
}
}
},
"settings.search.alias.setting.sidebarAppearance.font-size": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "sidebar-font-size sidebar font size text scale workspace title badge metadata shortcut hint"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "sidebar-font-size sidebar font size text scale workspace title badge metadata shortcut hint サイドバー フォントサイズ テキスト 拡大 ワークスペース タイトル バッジ メタデータ ショートカット ヒント"
}
}
}
},
"settings.search.alias.setting.sidebarAppearance.match-terminal": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -137177,6 +137194,91 @@
}
}
},
"settings.sidebarAppearance.fontSize": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Sidebar Font Size"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "サイドバーのフォントサイズ"
}
}
}
},
"settings.sidebarAppearance.fontSize.points": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ pt"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%@ pt"
}
}
}
},
"settings.sidebarAppearance.fontSize.reset": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Reset"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "リセット"
}
}
}
},
"settings.sidebarAppearance.fontSize.saveFailed": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Couldn't save sidebar font size (%@)."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "サイドバーのフォントサイズを保存できませんでした(%@)。"
}
}
}
},
"settings.sidebarAppearance.fontSize.subtitle": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Controls workspace titles, metadata, badges, and shortcut hints in the left sidebar."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "左サイドバーのワークスペースタイトル、メタデータ、バッジ、ショートカットヒントのサイズを調整します。"
}
}
}
},
"settings.sidebarAppearance.matchTerminalBackground": {
"extractionState": "manual",
"localizations": {
Expand Down
Loading
Loading