import AppKitimport CodexBarCoreimport SwiftUI @MainActorstruct GeneralPane: View { @Bindable var settings: SettingsStore @Bindable var store: UsageStore var body: some View { ScrollView(.vertical, showsIndicators: true) { VStack(alignment: .leading, spacing: 16) { SettingsSection(contentSpacing: 12) { Text("System") .font(.caption) .foregroundStyle(.secondary) .textCase(.uppercase) PreferenceToggleRow( title: "Start at Login", subtitle: "Automatically opens CodexBar when you start your Mac.", binding: self.$settings.launchAtLogin) } Divider() SettingsSection(contentSpacing: 12) { Text("Usage") .font(.caption) .foregroundStyle(.secondary) .textCase(.uppercase) VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 4) { Toggle(isOn: self.$settings.costUsageEnabled) { Text("Show cost summary") .font(.body) } .toggleStyle(.checkbox) Text("Reads local usage logs. Shows today + last 30 days cost in the menu.") .font(.footnote) .foregroundStyle(.tertiary) .fixedSize(horizontal: false, vertical: true) if self.settings.costUsageEnabled { Text("Auto-refresh: hourly · Timeout: 10m") .font(.footnote) .foregroundStyle(.tertiary) self.costStatusLine(provider: .claude) self.costStatusLine(provider: .codex) } } } } Divider() SettingsSection(contentSpacing: 12) { Text("Status") .font(.caption) .foregroundStyle(.secondary) .textCase(.uppercase) PreferenceToggleRow( title: "Check provider status", subtitle: "Polls OpenAI/Claude status pages and Google Workspace for " + "Gemini/Antigravity, surfacing incidents in the icon and menu.", binding: self.$settings.statusChecksEnabled) } Divider() SettingsSection(contentSpacing: 12) { Text("Notifications") .font(.caption) .foregroundStyle(.secondary) .textCase(.uppercase) PreferenceToggleRow( title: "Session quota notifications", subtitle: "Notifies when the 5-hour session quota hits 0% and when it becomes " + "available again.", binding: self.$settings.sessionQuotaNotificationsEnabled) } Divider() SettingsSection(contentSpacing: 12) { HStack { Spacer() Button("Quit CodexBar") { NSApp.terminate(nil) } .buttonStyle(.borderedProminent) .controlSize(.large) } } } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 20) .padding(.vertical, 12) } } private func costStatusLine(provider: UsageProvider) -> some View { let name = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName guard provider == .claude || provider == .codex else { return Text("\(name): unsupported") .font(.footnote) .foregroundStyle(.tertiary) } if self.store.isTokenRefreshInFlight(for: provider) { let elapsed: String = { guard let startedAt = self.store.tokenLastAttemptAt(for: provider) else { return "" } let seconds = max(0, Date().timeIntervalSince(startedAt)) let formatter = DateComponentsFormatter() formatter.allowedUnits = seconds < 60 ? [.second] : [.minute, .second] formatter.unitsStyle = .abbreviated return formatter.string(from: seconds).map { " (\($0))" } ?? "" }() return Text("\(name): fetching…\(elapsed)") .font(.footnote) .foregroundStyle(.tertiary) } if let snapshot = self.store.tokenSnapshot(for: provider) { let updated = UsageFormatter.updatedString(from: snapshot.updatedAt) let cost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? "—" return Text("\(name): \(updated) · 30d \(cost)") .font(.footnote) .foregroundStyle(.tertiary) } if let error = self.store.tokenError(for: provider), !error.isEmpty { let truncated = UsageFormatter.truncatedSingleLine(error, max: 120) return Text("\(name): \(truncated)") .font(.footnote) .foregroundStyle(.tertiary) } if let lastAttempt = self.store.tokenLastAttemptAt(for: provider) { let rel = RelativeDateTimeFormatter() rel.unitsStyle = .abbreviated let when = rel.localizedString(for: lastAttempt, relativeTo: Date()) return Text("\(name): last attempt \(when)") .font(.footnote) .foregroundStyle(.tertiary) } return Text("\(name): no data yet") .font(.footnote) .foregroundStyle(.tertiary) }}
import AppKit
import CodexBarCore
import SwiftUI
@MainActor
struct GeneralPane: View {
@Bindable var settings: SettingsStore
@Bindable var store: UsageStore
var body: some View {
ScrollView(.vertical, showsIndicators: true) {
VStack(alignment: .leading, spacing: 16) {
SettingsSection(contentSpacing: 12) {
Text("System")
.font(.caption)
.foregroundStyle(.secondary)
.textCase(.uppercase)
PreferenceToggleRow(
title: "Start at Login",
subtitle: "Automatically opens CodexBar when you start your Mac.",
binding: self.$settings.launchAtLogin)
}
Divider()
SettingsSection(contentSpacing: 12) {
Text("Usage")
.font(.caption)
.foregroundStyle(.secondary)
.textCase(.uppercase)
VStack(alignment: .leading, spacing: 10) {
VStack(alignment: .leading, spacing: 4) {
Toggle(isOn: self.$settings.costUsageEnabled) {
Text("Show cost summary")
.font(.body)
}
.toggleStyle(.checkbox)
Text("Reads local usage logs. Shows today + last 30 days cost in the menu.")
.font(.footnote)
.foregroundStyle(.tertiary)
.fixedSize(horizontal: false, vertical: true)
if self.settings.costUsageEnabled {
Text("Auto-refresh: hourly · Timeout: 10m")
.font(.footnote)
.foregroundStyle(.tertiary)
self.costStatusLine(provider: .claude)
self.costStatusLine(provider: .codex)
}
}
}
}
Divider()
SettingsSection(contentSpacing: 12) {
Text("Status")
.font(.caption)
.foregroundStyle(.secondary)
.textCase(.uppercase)
PreferenceToggleRow(
title: "Check provider status",
subtitle: "Polls OpenAI/Claude status pages and Google Workspace for " +
"Gemini/Antigravity, surfacing incidents in the icon and menu.",
binding: self.$settings.statusChecksEnabled)
}
Divider()
SettingsSection(contentSpacing: 12) {
Text("Notifications")
.font(.caption)
.foregroundStyle(.secondary)
.textCase(.uppercase)
PreferenceToggleRow(
title: "Session quota notifications",
subtitle: "Notifies when the 5-hour session quota hits 0% and when it becomes " +
"available again.",
binding: self.$settings.sessionQuotaNotificationsEnabled)
}
Divider()
SettingsSection(contentSpacing: 12) {
HStack {
Spacer()
Button("Quit CodexBar") { NSApp.terminate(nil) }
.buttonStyle(.borderedProminent)
.controlSize(.large)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
.padding(.vertical, 12)
}
}
private func costStatusLine(provider: UsageProvider) -> some View {
let name = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName
guard provider == .claude || provider == .codex else {
return Text("\(name): unsupported")
.font(.footnote)
.foregroundStyle(.tertiary)
}
if self.store.isTokenRefreshInFlight(for: provider) {
let elapsed: String = {
guard let startedAt = self.store.tokenLastAttemptAt(for: provider) else { return "" }
let seconds = max(0, Date().timeIntervalSince(startedAt))
let formatter = DateComponentsFormatter()
formatter.allowedUnits = seconds < 60 ? [.second] : [.minute, .second]
formatter.unitsStyle = .abbreviated
return formatter.string(from: seconds).map { " (\($0))" } ?? ""
}()
return Text("\(name): fetching…\(elapsed)")
.font(.footnote)
.foregroundStyle(.tertiary)
}
if let snapshot = self.store.tokenSnapshot(for: provider) {
let updated = UsageFormatter.updatedString(from: snapshot.updatedAt)
let cost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? "—"
return Text("\(name): \(updated) · 30d \(cost)")
.font(.footnote)
.foregroundStyle(.tertiary)
}
if let error = self.store.tokenError(for: provider), !error.isEmpty {
let truncated = UsageFormatter.truncatedSingleLine(error, max: 120)
return Text("\(name): \(truncated)")
.font(.footnote)
.foregroundStyle(.tertiary)
}
if let lastAttempt = self.store.tokenLastAttemptAt(for: provider) {
let rel = RelativeDateTimeFormatter()
rel.unitsStyle = .abbreviated
let when = rel.localizedString(for: lastAttempt, relativeTo: Date())
return Text("\(name): last attempt \(when)")
.font(.footnote)
.foregroundStyle(.tertiary)
}
return Text("\(name): no data yet")
.font(.footnote)
.foregroundStyle(.tertiary)
}
}