Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions Sources/CodexBar/MenuBarDisplayMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,20 @@ enum MenuBarDisplayMode: String, CaseIterable, Identifiable {
}
}
}

/// Controls which time window drives the percent and pace values in the menu bar.
enum MenuBarTimeWindow: String, CaseIterable, Identifiable {
case session
case weekly

var id: String {
self.rawValue
}

var label: String {
switch self {
case .session: "Session"
case .weekly: "Weekly"
}
}
}
45 changes: 45 additions & 0 deletions Sources/CodexBar/PreferencesDisplayPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,51 @@ struct DisplayPane: View {
self.settings.menuBarDisplayMode != .both)
.opacity(self.settings.menuBarShowsBrandIconWithPercent &&
self.settings.menuBarDisplayMode == .both ? 1 : 0.5)
VStack(alignment: .leading, spacing: 4) {
Text("Time windows")
.font(.body)
Text("Choose which time window drives the percent and pace values.")
.font(.footnote)
.foregroundStyle(.tertiary)
Grid(alignment: .leading, horizontalSpacing: 8, verticalSpacing: 6) {
GridRow {
Text("Percent:")
.font(.callout)
Picker(
"Percent time window",
selection: self.$settings.menuBarPercentTimeWindow)
{
ForEach(MenuBarTimeWindow.allCases) { window in
Text(window.label).tag(window)
}
}
.labelsHidden()
.pickerStyle(.segmented)
.frame(maxWidth: 160)
}
.disabled(self.settings.menuBarDisplayMode == .pace)
.opacity(self.settings.menuBarDisplayMode == .pace ? 0.5 : 1)
GridRow {
Text("Pace:")
.font(.callout)
Picker(
"Pace time window",
selection: self.$settings.menuBarPaceTimeWindow)
{
ForEach(MenuBarTimeWindow.allCases) { window in
Text(window.label).tag(window)
}
}
.labelsHidden()
.pickerStyle(.segmented)
.frame(maxWidth: 160)
}
.disabled(self.settings.menuBarDisplayMode == .percent)
.opacity(self.settings.menuBarDisplayMode == .percent ? 0.5 : 1)
}
}
.disabled(!self.settings.menuBarShowsBrandIconWithPercent)
.opacity(self.settings.menuBarShowsBrandIconWithPercent ? 1 : 0.5)
}

Divider()
Expand Down
34 changes: 34 additions & 0 deletions Sources/CodexBar/SettingsStore+Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,40 @@ extension SettingsStore {
set { self.menuBarSeparatorStyleRaw = newValue.rawValue }
}

private var menuBarPercentTimeWindowRaw: String? {
get { self.defaultsState.menuBarPercentTimeWindowRaw }
set {
self.defaultsState.menuBarPercentTimeWindowRaw = newValue
if let raw = newValue {
self.userDefaults.set(raw, forKey: "menuBarPercentTimeWindow")
} else {
self.userDefaults.removeObject(forKey: "menuBarPercentTimeWindow")
}
}
}

var menuBarPercentTimeWindow: MenuBarTimeWindow {
get { MenuBarTimeWindow(rawValue: self.menuBarPercentTimeWindowRaw ?? "") ?? .session }
set { self.menuBarPercentTimeWindowRaw = newValue.rawValue }
}

private var menuBarPaceTimeWindowRaw: String? {
get { self.defaultsState.menuBarPaceTimeWindowRaw }
set {
self.defaultsState.menuBarPaceTimeWindowRaw = newValue
if let raw = newValue {
self.userDefaults.set(raw, forKey: "menuBarPaceTimeWindow")
} else {
self.userDefaults.removeObject(forKey: "menuBarPaceTimeWindow")
}
}
}

var menuBarPaceTimeWindow: MenuBarTimeWindow {
get { MenuBarTimeWindow(rawValue: self.menuBarPaceTimeWindowRaw ?? "") ?? .weekly }
set { self.menuBarPaceTimeWindowRaw = newValue.rawValue }
}

private var kiroMenuBarDisplayModeRaw: String? {
get { self.defaultsState.kiroMenuBarDisplayModeRaw }
set {
Expand Down
2 changes: 2 additions & 0 deletions Sources/CodexBar/SettingsStore+MenuObservation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ extension SettingsStore {
_ = self.menuBarShowsHighestUsage
_ = self.menuBarDisplayMode
_ = self.menuBarSeparatorStyle
_ = self.menuBarPercentTimeWindow
_ = self.menuBarPaceTimeWindow
_ = self.kiroMenuBarDisplayMode
_ = self.historicalTrackingEnabled
_ = self.multiAccountMenuLayout
Expand Down
6 changes: 6 additions & 0 deletions Sources/CodexBar/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,10 @@ extension SettingsStore {
?? MenuBarDisplayMode.percent.rawValue
let menuBarSeparatorStyleRaw = userDefaults.string(forKey: "menuBarSeparatorStyle")
?? MenuBarSeparatorStyle.dot.rawValue
let menuBarPercentTimeWindowRaw = userDefaults.string(forKey: "menuBarPercentTimeWindow")
?? MenuBarTimeWindow.session.rawValue
let menuBarPaceTimeWindowRaw = userDefaults.string(forKey: "menuBarPaceTimeWindow")
?? MenuBarTimeWindow.weekly.rawValue
let kiroMenuBarDisplayModeRaw = userDefaults.string(forKey: "kiroMenuBarDisplayMode")
?? KiroMenuBarDisplayMode.automatic.rawValue
let historicalTrackingEnabled = userDefaults.object(forKey: "historicalTrackingEnabled") as? Bool ?? false
Expand Down Expand Up @@ -412,6 +416,8 @@ extension SettingsStore {
menuBarShowsBrandIconWithPercent: menuBarShowsBrandIconWithPercent,
menuBarDisplayModeRaw: menuBarDisplayModeRaw,
menuBarSeparatorStyleRaw: menuBarSeparatorStyleRaw,
menuBarPercentTimeWindowRaw: menuBarPercentTimeWindowRaw,
menuBarPaceTimeWindowRaw: menuBarPaceTimeWindowRaw,
kiroMenuBarDisplayModeRaw: kiroMenuBarDisplayModeRaw,
historicalTrackingEnabled: historicalTrackingEnabled,
multiAccountMenuLayoutRaw: multiAccountMenuLayoutRaw,
Expand Down
2 changes: 2 additions & 0 deletions Sources/CodexBar/SettingsStoreState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ struct SettingsDefaultsState {
var menuBarShowsBrandIconWithPercent: Bool
var menuBarDisplayModeRaw: String?
var menuBarSeparatorStyleRaw: String?
var menuBarPercentTimeWindowRaw: String?
var menuBarPaceTimeWindowRaw: String?
var kiroMenuBarDisplayModeRaw: String?
var historicalTrackingEnabled: Bool
var multiAccountMenuLayoutRaw: String
Expand Down
29 changes: 21 additions & 8 deletions Sources/CodexBar/StatusItemController+Animation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,12 @@ extension StatusItemController {
return spend
}

let percentWindow = self.menuBarPercentWindow(for: provider, snapshot: snapshot)
let percentWindow: RateWindow? = switch self.settings.menuBarPercentTimeWindow {
case .session:
self.menuBarPercentWindow(for: provider, snapshot: snapshot)
case .weekly:
snapshot?.secondary ?? self.menuBarPercentWindow(for: provider, snapshot: snapshot)
}
let mode = self.settings.menuBarDisplayMode
let now = Date()
let codexProjection = self.store.codexConsumerProjectionIfNeeded(
Expand All @@ -721,13 +726,21 @@ extension StatusItemController {
case .percent:
pace = nil
case .pace, .both:
let weeklyWindow =
codexProjection?.rateWindow(for: .weekly)
?? snapshot?.secondary
// Abacus has no secondary window; pace is computed on primary monthly credits
?? (provider == .abacus ? snapshot?.primary : nil)
pace = weeklyWindow.flatMap { window in
self.store.weeklyPace(provider: provider, window: window, now: now)
switch self.settings.menuBarPaceTimeWindow {
case .session:
let sessionWindow = snapshot?.primary ?? snapshot?.secondary
pace = sessionWindow.flatMap { window in
UsagePaceText.sessionPace(provider: provider, window: window, now: now)
}
case .weekly:
let weeklyWindow =
codexProjection?.rateWindow(for: .weekly)
?? snapshot?.secondary
// Abacus has no secondary window; pace is computed on primary monthly credits
?? (provider == .abacus ? snapshot?.primary : nil)
pace = weeklyWindow.flatMap { window in
self.store.weeklyPace(provider: provider, window: window, now: now)
}
}
}
let displayText = MenuBarDisplayText.displayText(
Expand Down
24 changes: 24 additions & 0 deletions Tests/CodexBarTests/StatusItemAnimationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1103,4 +1103,28 @@ struct StatusItemAnimationTests {
#expect(baselineAlpha < 0.01)
#expect(outputAlpha > 0.01)
}

@Test
func `menu bar time window settings round trip`() {
// These settings persist to UserDefaults.standard; clear leftovers so the defaults check is meaningful.
UserDefaults.standard.removeObject(forKey: "menuBarPercentTimeWindow")
UserDefaults.standard.removeObject(forKey: "menuBarPaceTimeWindow")

let settings = SettingsStore(
configStore: testConfigStore(suiteName: "StatusItemAnimationTests-timewindow"),
zaiTokenStore: NoopZaiTokenStore())

// Backward-compatible defaults: percent tracks the session, pace tracks the week.
#expect(settings.menuBarPercentTimeWindow == .session)
#expect(settings.menuBarPaceTimeWindow == .weekly)

settings.menuBarPercentTimeWindow = .weekly
settings.menuBarPaceTimeWindow = .session

#expect(settings.menuBarPercentTimeWindow == .weekly)
#expect(settings.menuBarPaceTimeWindow == .session)

#expect(settings.userDefaults.string(forKey: "menuBarPercentTimeWindow") == "weekly")
#expect(settings.userDefaults.string(forKey: "menuBarPaceTimeWindow") == "session")
}
}