Skip to content

Commit 8479841

Browse files
authored
fix: keep settings selectors interactive on macOS 27 (#1936)
1 parent b007975 commit 8479841

4 files changed

Lines changed: 127 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Widgets: make Kimi available with Weekly, Rate Limit, and Monthly quota rows. Thanks @joeVenner!
77

88
### Fixed
9+
- Settings: keep Language, Default Terminal, and Refresh cadence selectors interactive on macOS 27.
910
- Codex menu: hide error-only optional Credits and OpenAI web setup diagnostics while keeping them visible in provider Settings.
1011
- Codex quotas: show the session quota as unavailable while an exhausted weekly limit is still binding, including menu-bar icons and widgets. Thanks @Yuxin-Qiao!
1112
- Codex cost history: reuse cached aggregate pricing and one pricing catalog across daily and project reports, carry fresh cache state across launches, and treat unpriced models as migrated, avoiding repeated row scans, filesystem work, and duplicate background scans on large local histories.

Sources/CodexBar/PreferencesGeneralPane.swift

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -82,39 +82,42 @@ struct GeneralPane: View {
8282
var body: some View {
8383
Form {
8484
Section {
85-
Picker(selection: self.$settings.appLanguage) {
86-
ForEach(AppLanguage.allCases) { option in
87-
Text(verbatim: option.label).tag(option.rawValue)
88-
}
89-
} label: {
90-
SettingsRowLabel(L("language_title"), subtitle: L("language_subtitle"))
91-
}
92-
93-
Picker(selection: self.$settings.terminalApp) {
94-
ForEach(TerminalApp.pickerOptions(selected: self.settings.terminalApp)) { option in
85+
SettingsMenuPicker(
86+
selection: self.$settings.appLanguage,
87+
options: GeneralSettingsMenuOptions.languages,
88+
label: {
89+
SettingsRowLabel(L("language_title"), subtitle: L("language_subtitle"))
90+
},
91+
optionLabel: { rawValue in
92+
Text(verbatim: AppLanguage(rawValue: rawValue)?.label ?? rawValue)
93+
})
94+
95+
SettingsMenuPicker(
96+
selection: self.$settings.terminalApp,
97+
options: GeneralSettingsMenuOptions.terminalApps(selected: self.settings.terminalApp),
98+
label: {
99+
SettingsRowLabel(L("terminal_app_title"), subtitle: L("terminal_app_subtitle"))
100+
},
101+
optionLabel: { option in
95102
HStack(spacing: 6) {
96103
if let icon = option.pickerIcon {
97104
Image(nsImage: icon)
98105
}
99106
Text(option.label)
100107
}
101-
.tag(option)
102-
}
103-
} label: {
104-
SettingsRowLabel(L("terminal_app_title"), subtitle: L("terminal_app_subtitle"))
105-
}
108+
})
106109

107110
Toggle(L("start_at_login_title"), isOn: self.$settings.launchAtLogin)
108111
} header: {
109112
Text(L("section_system"))
110113
}
111114

112115
Section {
113-
Picker(L("refresh_cadence_title"), selection: self.$settings.refreshFrequency) {
114-
ForEach(RefreshFrequency.allCases) { option in
115-
Text(option.label).tag(option)
116-
}
117-
}
116+
SettingsMenuPicker(
117+
selection: self.$settings.refreshFrequency,
118+
options: GeneralSettingsMenuOptions.refreshFrequencies,
119+
label: { Text(L("refresh_cadence_title")) },
120+
optionLabel: { option in Text(option.label) })
118121

119122
Toggle(L("refresh_on_open_title"), isOn: self.$settings.refreshAllProvidersOnMenuOpen)
120123

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import SwiftUI
2+
3+
/// Menu-backed settings selector that avoids disabled `Picker` items on macOS 27 when built with the macOS 26 SDK.
4+
struct SettingsMenuPicker<Value: Hashable, Label: View, OptionLabel: View>: View {
5+
@Binding private var selection: Value
6+
private let options: [Value]
7+
private let label: () -> Label
8+
private let optionLabel: (Value) -> OptionLabel
9+
10+
init(
11+
selection: Binding<Value>,
12+
options: [Value],
13+
@ViewBuilder label: @escaping () -> Label,
14+
@ViewBuilder optionLabel: @escaping (Value) -> OptionLabel)
15+
{
16+
self._selection = selection
17+
self.options = options
18+
self.label = label
19+
self.optionLabel = optionLabel
20+
}
21+
22+
var body: some View {
23+
LabeledContent {
24+
Menu {
25+
ForEach(self.options, id: \.self) { option in
26+
Button {
27+
self.selection = option
28+
} label: {
29+
HStack {
30+
if self.selection == option {
31+
Image(systemName: "checkmark")
32+
}
33+
self.optionLabel(option)
34+
}
35+
}
36+
}
37+
} label: {
38+
self.optionLabel(self.selection)
39+
.foregroundStyle(.primary)
40+
}
41+
.menuStyle(.button)
42+
.buttonStyle(.borderless)
43+
.fixedSize()
44+
} label: {
45+
self.label()
46+
}
47+
}
48+
}
49+
50+
enum GeneralSettingsMenuOptions {
51+
static let languages = AppLanguage.allCases.map(\.rawValue)
52+
static let refreshFrequencies = RefreshFrequency.allCases
53+
54+
static func terminalApps(selected: TerminalApp) -> [TerminalApp] {
55+
TerminalApp.pickerOptions(selected: selected)
56+
}
57+
58+
static func terminalApps(
59+
selected: TerminalApp,
60+
applicationURL: (String) -> URL?) -> [TerminalApp]
61+
{
62+
TerminalApp.pickerOptions(selected: selected, applicationURL: applicationURL)
63+
}
64+
}

Tests/CodexBarTests/PreferencesPaneSmokeTests.swift

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,40 @@ struct PreferencesPaneSmokeTests {
4848
_ = SettingsSidebarView(settings: settings, store: store, selection: .constant(.provider(.codex))).body
4949
}
5050

51+
@Test
52+
func `general menu options cover persisted settings`() {
53+
let previousLanguage = UserDefaults.standard.object(forKey: "appLanguage")
54+
let previousAppleLanguages = UserDefaults.standard.object(forKey: "AppleLanguages")
55+
defer {
56+
if let previousLanguage {
57+
UserDefaults.standard.set(previousLanguage, forKey: "appLanguage")
58+
} else {
59+
UserDefaults.standard.removeObject(forKey: "appLanguage")
60+
}
61+
if let previousAppleLanguages {
62+
UserDefaults.standard.set(previousAppleLanguages, forKey: "AppleLanguages")
63+
} else {
64+
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
65+
}
66+
}
67+
68+
#expect(GeneralSettingsMenuOptions.languages == AppLanguage.allCases.map(\.rawValue))
69+
#expect(GeneralSettingsMenuOptions.refreshFrequencies == RefreshFrequency.allCases)
70+
#expect(GeneralSettingsMenuOptions.terminalApps(selected: .terminal) { _ in nil } == [.terminal])
71+
#expect(GeneralSettingsMenuOptions.terminalApps(selected: .iTerm) { _ in nil } == [.terminal, .iTerm])
72+
73+
let suite = "PreferencesPaneSmokeTests-general-menu-persistence"
74+
let settings = Self.makeSettingsStore(suite: suite)
75+
settings.appLanguage = "ja"
76+
settings.terminalApp = .iTerm
77+
settings.refreshFrequency = .fiveMinutes
78+
79+
let reloaded = Self.makeSettingsStore(suite: suite, reset: false)
80+
#expect(reloaded.appLanguage == "ja")
81+
#expect(reloaded.terminalApp == .iTerm)
82+
#expect(reloaded.refreshFrequency == .fiveMinutes)
83+
}
84+
5185
@Test
5286
func `overview provider limit text formats numeric limit as object argument`() {
5387
let text = DisplayPane.overviewProviderLimitText(limit: 3)
@@ -180,10 +214,12 @@ struct PreferencesPaneSmokeTests {
180214
}
181215
}
182216

183-
private static func makeSettingsStore(suite: String) -> SettingsStore {
217+
private static func makeSettingsStore(suite: String, reset: Bool = true) -> SettingsStore {
184218
let defaults = UserDefaults(suiteName: suite)!
185-
defaults.removePersistentDomain(forName: suite)
186-
let configStore = testConfigStore(suiteName: suite)
219+
if reset {
220+
defaults.removePersistentDomain(forName: suite)
221+
}
222+
let configStore = testConfigStore(suiteName: suite, reset: reset)
187223

188224
return SettingsStore(
189225
userDefaults: defaults,

0 commit comments

Comments
 (0)