Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d2c9ca9
Add settings sidebar shell
lawrencecchen Apr 28, 2026
95d4ca1
Add settings shell prototype app
lawrencecchen Apr 28, 2026
f28dc75
Migrate settings to native shell layout
lawrencecchen Apr 28, 2026
30e443a
Align settings shell with prototype
lawrencecchen Apr 28, 2026
d440142
Remove settings titlebar debug hooks
lawrencecchen Apr 28, 2026
187987d
Use SwiftUI scene for settings window
lawrencecchen Apr 29, 2026
4349366
Improve settings search results
lawrencecchen Apr 29, 2026
a918a4c
Remove settings detail title
lawrencecchen Apr 29, 2026
10a792d
Tune settings window default size
lawrencecchen Apr 29, 2026
63e86f0
Highlight settings search results
lawrencecchen Apr 29, 2026
d30f8c4
Make settings search highlight persistent
lawrencecchen Apr 29, 2026
59d27a3
Fade settings search highlight
lawrencecchen Apr 29, 2026
b84f6d7
Expose settings json in settings
lawrencecchen Apr 29, 2026
aaac7df
Split settings navigation helpers
lawrencecchen Apr 29, 2026
d4bd7a9
Limit settings highlight to search clicks
lawrencecchen Apr 29, 2026
ce772ae
Make settings sidebar scroll latest-wins
lawrencecchen Apr 29, 2026
963e40a
Use wand icon for automation settings
lawrencecchen Apr 29, 2026
ec7ca8f
Move workspace colors below shortcuts
lawrencecchen Apr 29, 2026
da0f416
merge: resolve conflicts with main
lawrencecchen Apr 29, 2026
440d5cc
Address settings window review feedback
lawrencecchen Apr 29, 2026
1d697b3
Fix settings shortcut routing test cleanup
lawrencecchen Apr 29, 2026
0e607d2
Fix settings window activation warning
lawrencecchen Apr 29, 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
16 changes: 10 additions & 6 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
84E00D47E4584162AE53BC8D /* xterm-ghostty in Resources */ = {isa = PBXBuildFile; fileRef = B2E7294509CC42FE9191870E /* xterm-ghostty */; };
8C4BBF2DEF6DF93F395A9EE7 /* TerminalControllerSocketSecurityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491751CE2321474474F27DCF /* TerminalControllerSocketSecurityTests.swift */; };
A5001001 /* cmuxApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001011 /* cmuxApp.swift */; };
A50019A0 /* SettingsNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50019A1 /* SettingsNavigation.swift */; };
A5001002 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001012 /* ContentView.swift */; };
C0DE32470000000000000001 /* ContentViewIdentifierCopyCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DE32470000000000000002 /* ContentViewIdentifierCopyCommands.swift */; };
619D509BC9B1EA946CBD6A8A /* WorkspaceRuntimeSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9960CC3992F3C44489E7627C /* WorkspaceRuntimeSettings.swift */; };
Expand Down Expand Up @@ -289,6 +290,7 @@
9CEC6EF35B71AE59FA45BA69 /* VMClient.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VMClient.swift; sourceTree = "<group>"; };
A5001000 /* cmux.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cmux.app; sourceTree = BUILT_PRODUCTS_DIR; };
A5001011 /* cmuxApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = cmuxApp.swift; sourceTree = "<group>"; };
A50019A1 /* SettingsNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsNavigation.swift; sourceTree = "<group>"; };
A5001012 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
C0DE32470000000000000002 /* ContentViewIdentifierCopyCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewIdentifierCopyCommands.swift; sourceTree = "<group>"; };
9960CC3992F3C44489E7627C /* WorkspaceRuntimeSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/WorkspaceRuntimeSettings.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -583,12 +585,13 @@
A5001041 /* Sources */ = {
isa = PBXGroup;
children = (
3023B1003023B1003023B100 /* ConfigSource.swift */,
3023B1013023B1013023B101 /* ConfigSettingsView.swift */,
A5001011 /* cmuxApp.swift */,
A5001012 /* ContentView.swift */,
C0DE32470000000000000002 /* ContentViewIdentifierCopyCommands.swift */,
9960CC3992F3C44489E7627C /* WorkspaceRuntimeSettings.swift */,
3023B1003023B1003023B100 /* ConfigSource.swift */,
3023B1013023B1013023B101 /* ConfigSettingsView.swift */,
A5001011 /* cmuxApp.swift */,
A50019A1 /* SettingsNavigation.swift */,
A5001012 /* ContentView.swift */,
C0DE32470000000000000002 /* ContentViewIdentifierCopyCommands.swift */,
9960CC3992F3C44489E7627C /* WorkspaceRuntimeSettings.swift */,
243632BA1DBBA36FD46E0610 /* SidebarAppearanceSupport.swift */,
610008EA5E3744DDBE05C8E2 /* InternalTabDragConfiguration.swift */,
A080D1F6CCCBFBED8D95DD1D /* ShortcutHintPill.swift */,
Expand Down Expand Up @@ -980,6 +983,7 @@
3023A1003023A1003023A100 /* ConfigSource.swift in Sources */,
3023A1013023A1013023A101 /* ConfigSettingsView.swift in Sources */,
A5001001 /* cmuxApp.swift in Sources */,
A50019A0 /* SettingsNavigation.swift in Sources */,
A5001002 /* ContentView.swift in Sources */,
C0DE32470000000000000001 /* ContentViewIdentifierCopyCommands.swift in Sources */,
619D509BC9B1EA946CBD6A8A /* WorkspaceRuntimeSettings.swift in Sources */,
Expand Down
11 changes: 11 additions & 0 deletions Prototypes/SettingsShellLab/.codex/environments/environment.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
version = 1
name = "SettingsShellLab"

[setup]
script = ""

[[actions]]
name = "Run"
icon = "run"
command = "./script/build_and_run.sh"
2 changes: 2 additions & 0 deletions Prototypes/SettingsShellLab/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.build/
dist/
20 changes: 20 additions & 0 deletions Prototypes/SettingsShellLab/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// swift-tools-version: 5.9

import PackageDescription

let package = Package(
name: "SettingsShellLab",
platforms: [
.macOS(.v14),
],
products: [
.executable(name: "SettingsShellLab", targets: ["SettingsShellLab"]),
],
targets: [
.executableTarget(
name: "SettingsShellLab",
path: "Sources/SettingsShellLab",
resources: [.process("Resources")]
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import AppKit
import SwiftUI

@main
struct SettingsShellLabApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate

var body: some Scene {
WindowGroup(String(localized: "app.window.title", defaultValue: "Settings Shell Lab")) {
SettingsShellView()
}
.defaultSize(width: 980, height: 680)
.commands {
SidebarCommands()
}
}
}

final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import SwiftUI

enum SettingsSection: String, CaseIterable, Identifiable {
case general
case terminal
case sidebar
case browser
case keyboard
case automation
case advanced

var id: Self { self }

var title: String {
switch self {
case .general:
return String(localized: "section.general.title", defaultValue: "General")
case .terminal:
return String(localized: "section.terminal.title", defaultValue: "Terminal")
case .sidebar:
return String(localized: "section.sidebar.title", defaultValue: "Sidebar")
case .browser:
return String(localized: "section.browser.title", defaultValue: "Browser")
case .keyboard:
return String(localized: "section.keyboard.title", defaultValue: "Keyboard")
case .automation:
return String(localized: "section.automation.title", defaultValue: "Automation")
case .advanced:
return String(localized: "section.advanced.title", defaultValue: "Advanced")
}
}

var symbolName: String {
switch self {
case .general:
return "gearshape"
case .terminal:
return "terminal"
case .sidebar:
return "sidebar.left"
case .browser:
return "globe"
case .keyboard:
return "keyboard"
case .automation:
return "bolt.horizontal"
case .advanced:
return "wrench.and.screwdriver"
}
}

var detail: String {
switch self {
case .general:
return String(localized: "section.general.detail", defaultValue: "Language, appearance, updates")
case .terminal:
return String(localized: "section.terminal.detail", defaultValue: "Font, scrollback, bell")
case .sidebar:
return String(localized: "section.sidebar.detail", defaultValue: "Layout, badges, metadata")
case .browser:
return String(localized: "section.browser.detail", defaultValue: "Search, links, history")
case .keyboard:
return String(localized: "section.keyboard.detail", defaultValue: "Shortcuts and chords")
case .automation:
return String(localized: "section.automation.detail", defaultValue: "Socket, hooks, ports")
case .advanced:
return String(localized: "section.advanced.detail", defaultValue: "Diagnostics and reset")
}
}

var searchText: String {
"\(title) \(detail)"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"sourceLanguage": "en",
"version": "1.0",
"strings": {
"app.window.title": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Settings Shell" } }, "ja": { "stringUnit": { "state": "translated", "value": "設定シェル" } } } },
"detail.mode": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Mode" } }, "ja": { "stringUnit": { "state": "translated", "value": "モード" } } } },
"detail.primaryAction": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Open Related File" } }, "ja": { "stringUnit": { "state": "translated", "value": "関連ファイルを開く" } } } },
"detail.secondaryAction": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Restore Defaults" } }, "ja": { "stringUnit": { "state": "translated", "value": "デフォルトに戻す" } } } },
"detail.section": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Section" } }, "ja": { "stringUnit": { "state": "translated", "value": "セクション" } } } },
"detail.slider": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Amount" } }, "ja": { "stringUnit": { "state": "translated", "value": "量" } } } },
"detail.toggle": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Enable" } }, "ja": { "stringUnit": { "state": "translated", "value": "有効にする" } } } },
"mode.compact": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Compact" } }, "ja": { "stringUnit": { "state": "translated", "value": "コンパクト" } } } },
"mode.expanded": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Expanded" } }, "ja": { "stringUnit": { "state": "translated", "value": "拡張" } } } },
"mode.system": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "System" } }, "ja": { "stringUnit": { "state": "translated", "value": "システム" } } } },
"section.advanced.detail": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Advanced" } }, "ja": { "stringUnit": { "state": "translated", "value": "詳細" } } } },
"section.advanced.title": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Advanced" } }, "ja": { "stringUnit": { "state": "translated", "value": "詳細" } } } },
"section.automation.detail": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Automation" } }, "ja": { "stringUnit": { "state": "translated", "value": "自動化" } } } },
"section.automation.title": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Automation" } }, "ja": { "stringUnit": { "state": "translated", "value": "自動化" } } } },
"section.browser.detail": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Browser" } }, "ja": { "stringUnit": { "state": "translated", "value": "ブラウザ" } } } },
"section.browser.title": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Browser" } }, "ja": { "stringUnit": { "state": "translated", "value": "ブラウザ" } } } },
"section.general.detail": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "General" } }, "ja": { "stringUnit": { "state": "translated", "value": "一般" } } } },
"section.general.title": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "General" } }, "ja": { "stringUnit": { "state": "translated", "value": "一般" } } } },
"section.keyboard.detail": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Keyboard" } }, "ja": { "stringUnit": { "state": "translated", "value": "キーボード" } } } },
"section.keyboard.title": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Keyboard" } }, "ja": { "stringUnit": { "state": "translated", "value": "キーボード" } } } },
"section.sidebar.detail": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Sidebar" } }, "ja": { "stringUnit": { "state": "translated", "value": "サイドバー" } } } },
"section.sidebar.title": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Sidebar" } }, "ja": { "stringUnit": { "state": "translated", "value": "サイドバー" } } } },
"section.terminal.detail": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Terminal" } }, "ja": { "stringUnit": { "state": "translated", "value": "ターミナル" } } } },
"section.terminal.title": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Terminal" } }, "ja": { "stringUnit": { "state": "translated", "value": "ターミナル" } } } },
"settings.search.prompt": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Search" } }, "ja": { "stringUnit": { "state": "translated", "value": "検索" } } } },
"settings.sidebar.title": { "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Settings" } }, "ja": { "stringUnit": { "state": "translated", "value": "設定" } } } }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import SwiftUI

struct SettingsDetailView: View {
let section: SettingsSection

@State private var isEnabled = true
@State private var selectedMode = Mode.system
@State private var numericValue = 12.0

var body: some View {
Form {
Section {
LabeledContent(
String(localized: "detail.section", defaultValue: "Section"),
value: section.title
)

Toggle(
String(localized: "detail.toggle", defaultValue: "Enable"),
isOn: $isEnabled
)

Picker(String(localized: "detail.mode", defaultValue: "Mode"), selection: $selectedMode) {
ForEach(Mode.allCases) { mode in
Text(mode.title).tag(mode)
}
}
.pickerStyle(.segmented)

Slider(
value: $numericValue,
in: 0...24
) {
Text(String(localized: "detail.slider", defaultValue: "Amount"))
}
} header: {
Text(section.title)
}

Section {
Button(String(localized: "detail.primaryAction", defaultValue: "Open Related File")) {
}
.disabled(true)

Button(String(localized: "detail.secondaryAction", defaultValue: "Restore Defaults")) {
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.disabled(true)
}
}
.formStyle(.grouped)
.padding()
}
}

private enum Mode: String, CaseIterable, Identifiable {
case system
case compact
case expanded

var id: Self { self }

var title: String {
switch self {
case .system:
return String(localized: "mode.system", defaultValue: "System")
case .compact:
return String(localized: "mode.compact", defaultValue: "Compact")
case .expanded:
return String(localized: "mode.expanded", defaultValue: "Expanded")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import SwiftUI

struct SettingsShellView: View {
@SceneStorage("selectedSettingsSection") private var selectedSectionRaw = SettingsSection.general.rawValue
@State private var columnVisibility: NavigationSplitViewVisibility = .all
@State private var searchText = ""

private var selectedSection: SettingsSection {
SettingsSection(rawValue: selectedSectionRaw) ?? .general
}

private var filteredSections: [SettingsSection] {
let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
guard !query.isEmpty else { return SettingsSection.allCases }
return SettingsSection.allCases.filter { section in
section.searchText.localizedStandardContains(query)
}
}

var body: some View {
NavigationSplitView(columnVisibility: $columnVisibility) {
List(selection: $selectedSectionRaw) {
ForEach(filteredSections) { section in
Label(section.title, systemImage: section.symbolName)
.tag(section.rawValue)
}
}
.listStyle(.sidebar)
.navigationTitle(String(localized: "settings.sidebar.title", defaultValue: "Settings"))
.searchable(
text: $searchText,
placement: .sidebar,
prompt: Text(String(localized: "settings.search.prompt", defaultValue: "Search"))
)
.navigationSplitViewColumnWidth(210)
} detail: {
SettingsDetailView(section: selectedSection)
}
.navigationSplitViewStyle(.balanced)
}
}
Loading