import AppKitimport CodexBarCoreimport SwiftUI /// Sidebar destinations of the settings window: fixed app panes plus one entry per provider.enum SettingsPane: Hashable { case general case usageSpend case notifications case menuBar case menu case advanced case hooks case about case debug case provider(UsageProvider) static let windowWidth: CGFloat = 880 static let windowHeight: CGFloat = 620 static let windowMinWidth: CGFloat = 800 static let windowMinHeight: CGFloat = 540 static let sidebarWidth: CGFloat = 260 static let detailMaxWidth: CGFloat = 780 var title: String { switch self { case .general: L("tab_general") case .usageSpend: L("tab_usage_spend") case .notifications: L("tab_notifications") case .menuBar: L("tab_menu_bar") case .menu: L("tab_menu") case .advanced: L("tab_advanced") case .hooks: L("tab_hooks") case .about: L("tab_about") case .debug: L("tab_debug") case let .provider(provider): ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName } }} @MainActorstruct PreferencesView: View { @Bindable var settings: SettingsStore @Bindable var store: UsageStore let updater: UpdaterProviding @Bindable var selection: PreferencesSelection let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator let codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator let runProviderLoginFlow: @MainActor (UsageProvider) async -> Void @Environment(\.colorScheme) private var colorScheme init( settings: SettingsStore, store: UsageStore, updater: UpdaterProviding, selection: PreferencesSelection, managedCodexAccountCoordinator: ManagedCodexAccountCoordinator = ManagedCodexAccountCoordinator(), codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator? = nil, runProviderLoginFlow: @escaping @MainActor (UsageProvider) async -> Void = { _ in }) { self.settings = settings self.store = store self.updater = updater self.selection = selection self.managedCodexAccountCoordinator = managedCodexAccountCoordinator self.codexAccountPromotionCoordinator = codexAccountPromotionCoordinator ?? CodexAccountPromotionCoordinator( settingsStore: settings, usageStore: store, managedAccountCoordinator: managedCodexAccountCoordinator) self.runProviderLoginFlow = runProviderLoginFlow } var body: some View { HStack(spacing: 0) { // Golden Gate-style sidebar: edge-to-edge material with a hairline separator, // no floating card chrome. The material ignores the safe area so it runs up // behind the transparent titlebar. SettingsSidebarView(settings: self.settings, store: self.store, selection: self.$selection.pane) .frame(width: SettingsPane.sidebarWidth) .background { SettingsSidebarMaterial() .ignoresSafeArea() } Divider() .ignoresSafeArea() self.detailView .frame( maxWidth: SettingsPane.detailMaxWidth, maxHeight: .infinity, alignment: .topLeading) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } .frame( minWidth: SettingsPane.windowMinWidth, idealWidth: SettingsPane.windowWidth, maxWidth: .infinity, minHeight: SettingsPane.windowMinHeight, idealHeight: SettingsPane.windowHeight, maxHeight: .infinity) .id(self.settings.appLanguage) .background { SettingsWindowAppearanceBridge(colorScheme: self.colorScheme, windowTitle: self.selection.pane.title) .allowsHitTesting(false) } .onAppear { self.ensureValidSelection() } .onChange(of: self.settings.debugMenuEnabled) { _, _ in self.ensureValidSelection() } .onChange(of: self.settings.shouldRequestAdaptiveActivityScanConsent) { _, shouldRequest in guard shouldRequest else { return } AdaptiveActivityConsentPresenter.presentIfNeeded(settings: self.settings) } } @ViewBuilder private var detailView: some View { switch self.selection.pane { case .general: GeneralPane(settings: self.settings) case .usageSpend: SpendDashboardPane(settings: self.settings, store: self.store) case .notifications: NotificationsPane(settings: self.settings) case .menuBar: MenuBarPane(settings: self.settings, store: self.store) case .menu: MenuPane(settings: self.settings, store: self.store) case .advanced: AdvancedPane(settings: self.settings, store: self.store) case .hooks: HooksPane(settings: self.settings) case .about: AboutPane(updater: self.updater) case .debug: DebugPane(settings: self.settings, store: self.store) case let .provider(provider): ProvidersPane( provider: provider, settings: self.settings, store: self.store, managedCodexAccountCoordinator: self.managedCodexAccountCoordinator, codexAccountPromotionCoordinator: self.codexAccountPromotionCoordinator, runProviderLoginFlow: self.runProviderLoginFlow) .id(provider) } } private func ensureValidSelection() { if !self.settings.debugMenuEnabled, self.selection.pane == .debug { self.selection.pane = .general } }} @MainActorenum SettingsWindowSizing { static func enforceMinimumSize(_ window: NSWindow) { let toolbarHeight = max(0, window.frame.height - window.contentLayoutRect.height) let minimumSize = NSSize( width: SettingsPane.windowMinWidth, height: SettingsPane.windowMinHeight + toolbarHeight) window.minSize = minimumSize if window.frame.width < minimumSize.width || window.frame.height < minimumSize.height { var frame = window.frame let repairedSize = NSSize( width: max(frame.width, minimumSize.width), height: max(frame.height, minimumSize.height)) frame.origin.y += frame.height - repairedSize.height frame.size = repairedSize window.setFrame(frame, display: true) } }} @MainActorenum SettingsWindowAppearance { typealias ResetAction = @MainActor @Sendable () -> Void typealias ResetScheduler = @MainActor @Sendable (@escaping ResetAction) -> Void static func refresh( _ window: NSWindow, application: NSApplication = NSApp, scheduleReset: ResetScheduler = Self.scheduleReset) { SettingsWindowSizing.enforceMinimumSize(window) window.appearanceSource = application // Pulse the exact effective appearance so the native toolbar redraws without // dropping inherited accessibility attributes, then restore KVO inheritance. window.appearance = application.effectiveAppearance scheduleReset { [weak window] in if let window { SettingsWindowSizing.enforceMinimumSize(window) } window?.appearance = nil window?.viewsNeedDisplay = true } } static func scheduleReset(_ action: @escaping ResetAction) { Task { @MainActor in await Task.yield() action() } }} @MainActorstruct SettingsWindowAppearanceBridge: NSViewRepresentable { let colorScheme: ColorScheme let windowTitle: String func makeNSView(context: Context) -> SettingsWindowAppearanceView { SettingsWindowAppearanceView() } func updateNSView(_ nsView: SettingsWindowAppearanceView, context: Context) { nsView.refreshWindowAppearance(for: self.colorScheme, windowTitle: self.windowTitle) }} @MainActorfinal class SettingsWindowAppearanceView: NSView { private let scheduleReset: SettingsWindowAppearance.ResetScheduler private var colorScheme: ColorScheme? private var windowTitle: String? init(scheduleReset: @escaping SettingsWindowAppearance.ResetScheduler = SettingsWindowAppearance.scheduleReset) { self.scheduleReset = scheduleReset super.init(frame: .zero) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { NotificationCenter.default.removeObserver(self) } override func viewDidMoveToWindow() { super.viewDidMoveToWindow() NotificationCenter.default.removeObserver(self, name: NSWindow.didUpdateNotification, object: nil) if let window { NotificationCenter.default.addObserver( self, selector: #selector(self.windowDidUpdate(_:)), name: NSWindow.didUpdateNotification, object: window) } self.configureWindowStyle() self.refreshWindowAppearance() } @objc private func windowDidUpdate(_ notification: Notification) { self.configureWindowStyle() } func refreshWindowAppearance(for colorScheme: ColorScheme, windowTitle: String? = nil) { let colorSchemeChanged = self.colorScheme != colorScheme let windowTitleChanged = self.windowTitle != windowTitle guard colorSchemeChanged || windowTitleChanged else { return } self.colorScheme = colorScheme self.windowTitle = windowTitle guard let window else { return } self.configureWindowStyle() if windowTitleChanged, let windowTitle { window.title = windowTitle } if colorSchemeChanged { SettingsWindowAppearance.refresh(window, scheduleReset: self.scheduleReset) } } private func refreshWindowAppearance() { guard let window else { return } self.configureWindowStyle() if let windowTitle { window.title = windowTitle } SettingsWindowAppearance.refresh(window, scheduleReset: self.scheduleReset) } override func layout() { super.layout() self.configureWindowStyle() } private func configureWindowStyle() { guard let window else { return } if !window.styleMask.contains(.resizable) { window.styleMask.insert(.resizable) } if !window.titlebarAppearsTransparent { window.titlebarAppearsTransparent = true } if window.titleVisibility != .visible { window.titleVisibility = .visible } if window.titlebarSeparatorStyle != .none { window.titlebarSeparatorStyle = .none } if window.toolbar != nil { window.toolbar = nil } // Full-size content lets the sidebar material extend behind the titlebar so the // edge-to-edge sidebar reaches the top of the window; content stays below the // titlebar via the safe area. if !window.styleMask.contains(.fullSizeContentView) { window.styleMask.insert(.fullSizeContentView) } }} @MainActorprivate struct SettingsSidebarMaterial: NSViewRepresentable { func makeNSView(context: Context) -> NSVisualEffectView { let view = NSVisualEffectView() self.configure(view) return view } func updateNSView(_ nsView: NSVisualEffectView, context: Context) { self.configure(nsView) } private func configure(_ view: NSVisualEffectView) { view.material = .sidebar view.blendingMode = .behindWindow view.state = .followsWindowActiveState }}
import AppKit
import CodexBarCore
import SwiftUI
/// Sidebar destinations of the settings window: fixed app panes plus one entry per provider.
enum SettingsPane: Hashable {
case general
case usageSpend
case notifications
case menuBar
case menu
case advanced
case hooks
case about
case debug
case provider(UsageProvider)
static let windowWidth: CGFloat = 880
static let windowHeight: CGFloat = 620
static let windowMinWidth: CGFloat = 800
static let windowMinHeight: CGFloat = 540
static let sidebarWidth: CGFloat = 260
static let detailMaxWidth: CGFloat = 780
var title: String {
switch self {
case .general: L("tab_general")
case .usageSpend: L("tab_usage_spend")
case .notifications: L("tab_notifications")
case .menuBar: L("tab_menu_bar")
case .menu: L("tab_menu")
case .advanced: L("tab_advanced")
case .hooks: L("tab_hooks")
case .about: L("tab_about")
case .debug: L("tab_debug")
case let .provider(provider):
ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName
}
}
}
@MainActor
struct PreferencesView: View {
@Bindable var settings: SettingsStore
@Bindable var store: UsageStore
let updater: UpdaterProviding
@Bindable var selection: PreferencesSelection
let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator
let codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator
let runProviderLoginFlow: @MainActor (UsageProvider) async -> Void
@Environment(\.colorScheme) private var colorScheme
init(
settings: SettingsStore,
store: UsageStore,
updater: UpdaterProviding,
selection: PreferencesSelection,
managedCodexAccountCoordinator: ManagedCodexAccountCoordinator = ManagedCodexAccountCoordinator(),
codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator? = nil,
runProviderLoginFlow: @escaping @MainActor (UsageProvider) async -> Void = { _ in })
{
self.settings = settings
self.store = store
self.updater = updater
self.selection = selection
self.managedCodexAccountCoordinator = managedCodexAccountCoordinator
self.codexAccountPromotionCoordinator = codexAccountPromotionCoordinator
?? CodexAccountPromotionCoordinator(
settingsStore: settings,
usageStore: store,
managedAccountCoordinator: managedCodexAccountCoordinator)
self.runProviderLoginFlow = runProviderLoginFlow
}
var body: some View {
HStack(spacing: 0) {
// Golden Gate-style sidebar: edge-to-edge material with a hairline separator,
// no floating card chrome. The material ignores the safe area so it runs up
// behind the transparent titlebar.
SettingsSidebarView(settings: self.settings, store: self.store, selection: self.$selection.pane)
.frame(width: SettingsPane.sidebarWidth)
.background {
SettingsSidebarMaterial()
.ignoresSafeArea()
}
Divider()
.ignoresSafeArea()
self.detailView
.frame(
maxWidth: SettingsPane.detailMaxWidth,
maxHeight: .infinity,
alignment: .topLeading)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
.frame(
minWidth: SettingsPane.windowMinWidth,
idealWidth: SettingsPane.windowWidth,
maxWidth: .infinity,
minHeight: SettingsPane.windowMinHeight,
idealHeight: SettingsPane.windowHeight,
maxHeight: .infinity)
.id(self.settings.appLanguage)
.background {
SettingsWindowAppearanceBridge(colorScheme: self.colorScheme, windowTitle: self.selection.pane.title)
.allowsHitTesting(false)
}
.onAppear {
self.ensureValidSelection()
}
.onChange(of: self.settings.debugMenuEnabled) { _, _ in
self.ensureValidSelection()
}
.onChange(of: self.settings.shouldRequestAdaptiveActivityScanConsent) { _, shouldRequest in
guard shouldRequest else { return }
AdaptiveActivityConsentPresenter.presentIfNeeded(settings: self.settings)
}
}
@ViewBuilder
private var detailView: some View {
switch self.selection.pane {
case .general:
GeneralPane(settings: self.settings)
case .usageSpend:
SpendDashboardPane(settings: self.settings, store: self.store)
case .notifications:
NotificationsPane(settings: self.settings)
case .menuBar:
MenuBarPane(settings: self.settings, store: self.store)
case .menu:
MenuPane(settings: self.settings, store: self.store)
case .advanced:
AdvancedPane(settings: self.settings, store: self.store)
case .hooks:
HooksPane(settings: self.settings)
case .about:
AboutPane(updater: self.updater)
case .debug:
DebugPane(settings: self.settings, store: self.store)
case let .provider(provider):
ProvidersPane(
provider: provider,
settings: self.settings,
store: self.store,
managedCodexAccountCoordinator: self.managedCodexAccountCoordinator,
codexAccountPromotionCoordinator: self.codexAccountPromotionCoordinator,
runProviderLoginFlow: self.runProviderLoginFlow)
.id(provider)
}
}
private func ensureValidSelection() {
if !self.settings.debugMenuEnabled, self.selection.pane == .debug {
self.selection.pane = .general
}
}
}
@MainActor
enum SettingsWindowSizing {
static func enforceMinimumSize(_ window: NSWindow) {
let toolbarHeight = max(0, window.frame.height - window.contentLayoutRect.height)
let minimumSize = NSSize(
width: SettingsPane.windowMinWidth,
height: SettingsPane.windowMinHeight + toolbarHeight)
window.minSize = minimumSize
if window.frame.width < minimumSize.width || window.frame.height < minimumSize.height {
var frame = window.frame
let repairedSize = NSSize(
width: max(frame.width, minimumSize.width),
height: max(frame.height, minimumSize.height))
frame.origin.y += frame.height - repairedSize.height
frame.size = repairedSize
window.setFrame(frame, display: true)
}
}
}
@MainActor
enum SettingsWindowAppearance {
typealias ResetAction = @MainActor @Sendable () -> Void
typealias ResetScheduler = @MainActor @Sendable (@escaping ResetAction) -> Void
static func refresh(
_ window: NSWindow,
application: NSApplication = NSApp,
scheduleReset: ResetScheduler = Self.scheduleReset)
{
SettingsWindowSizing.enforceMinimumSize(window)
window.appearanceSource = application
// Pulse the exact effective appearance so the native toolbar redraws without
// dropping inherited accessibility attributes, then restore KVO inheritance.
window.appearance = application.effectiveAppearance
scheduleReset { [weak window] in
if let window {
SettingsWindowSizing.enforceMinimumSize(window)
}
window?.appearance = nil
window?.viewsNeedDisplay = true
}
}
static func scheduleReset(_ action: @escaping ResetAction) {
Task { @MainActor in
await Task.yield()
action()
}
}
}
@MainActor
struct SettingsWindowAppearanceBridge: NSViewRepresentable {
let colorScheme: ColorScheme
let windowTitle: String
func makeNSView(context: Context) -> SettingsWindowAppearanceView {
SettingsWindowAppearanceView()
}
func updateNSView(_ nsView: SettingsWindowAppearanceView, context: Context) {
nsView.refreshWindowAppearance(for: self.colorScheme, windowTitle: self.windowTitle)
}
}
@MainActor
final class SettingsWindowAppearanceView: NSView {
private let scheduleReset: SettingsWindowAppearance.ResetScheduler
private var colorScheme: ColorScheme?
private var windowTitle: String?
init(scheduleReset: @escaping SettingsWindowAppearance.ResetScheduler = SettingsWindowAppearance.scheduleReset) {
self.scheduleReset = scheduleReset
super.init(frame: .zero)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
NotificationCenter.default.removeObserver(self, name: NSWindow.didUpdateNotification, object: nil)
if let window {
NotificationCenter.default.addObserver(
self,
selector: #selector(self.windowDidUpdate(_:)),
name: NSWindow.didUpdateNotification,
object: window)
}
self.configureWindowStyle()
self.refreshWindowAppearance()
}
@objc private func windowDidUpdate(_ notification: Notification) {
self.configureWindowStyle()
}
func refreshWindowAppearance(for colorScheme: ColorScheme, windowTitle: String? = nil) {
let colorSchemeChanged = self.colorScheme != colorScheme
let windowTitleChanged = self.windowTitle != windowTitle
guard colorSchemeChanged || windowTitleChanged else { return }
self.colorScheme = colorScheme
self.windowTitle = windowTitle
guard let window else { return }
self.configureWindowStyle()
if windowTitleChanged, let windowTitle {
window.title = windowTitle
}
if colorSchemeChanged {
SettingsWindowAppearance.refresh(window, scheduleReset: self.scheduleReset)
}
}
private func refreshWindowAppearance() {
guard let window else { return }
self.configureWindowStyle()
if let windowTitle {
window.title = windowTitle
}
SettingsWindowAppearance.refresh(window, scheduleReset: self.scheduleReset)
}
override func layout() {
super.layout()
self.configureWindowStyle()
}
private func configureWindowStyle() {
guard let window else { return }
if !window.styleMask.contains(.resizable) {
window.styleMask.insert(.resizable)
}
if !window.titlebarAppearsTransparent {
window.titlebarAppearsTransparent = true
}
if window.titleVisibility != .visible {
window.titleVisibility = .visible
}
if window.titlebarSeparatorStyle != .none {
window.titlebarSeparatorStyle = .none
}
if window.toolbar != nil {
window.toolbar = nil
}
// Full-size content lets the sidebar material extend behind the titlebar so the
// edge-to-edge sidebar reaches the top of the window; content stays below the
// titlebar via the safe area.
if !window.styleMask.contains(.fullSizeContentView) {
window.styleMask.insert(.fullSizeContentView)
}
}
}
@MainActor
private struct SettingsSidebarMaterial: NSViewRepresentable {
func makeNSView(context: Context) -> NSVisualEffectView {
let view = NSVisualEffectView()
self.configure(view)
return view
}
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
self.configure(nsView)
}
private func configure(_ view: NSVisualEffectView) {
view.material = .sidebar
view.blendingMode = .behindWindow
view.state = .followsWindowActiveState
}
}