Skip to content

Commit 0bb8749

Browse files
authored
perf: match cost history fingerprint to visible content (#2067)
Project the hosted cost-history fingerprint onto visible chart, model, project, and source fields; preserve raw daily availability transitions. Follow-up to #2063. Changelog retains contributor credit to @Yuxin-Qiao.
1 parent 300ddee commit 0bb8749

4 files changed

Lines changed: 287 additions & 45 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Settings: split provider pane "Settings" sections into "Menu bar" and "Connection" so metric pickers and auth/cookie/source controls are grouped by topic.
77

88
### Fixed
9+
- Codex cost history: keep opening and refreshing the submenu fast as project history grows by comparing only the content it renders. Thanks @Yuxin-Qiao!
910
- Codex cost history: keep model-less token events explicitly unattributed instead of pricing them as GPT-5 while preserving current turn model attribution. Thanks @hhh2210!
1011
- Gemini: recover expired Workspace and education OAuth sessions when current CLI packages omit `oauth2.js`, with explicit credential and install-path discovery fallbacks. Thanks @Yuxin-Qiao!
1112
- Codex accounts: confirm apparent weekly resets before publishing them and isolate reset detection by stable account ownership, preventing transient full gauges and confetti across same-email workspaces (#2054). Thanks @Yuxin-Qiao!

Sources/CodexBar/CostHistoryChartMenuView.swift

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -363,8 +363,7 @@ struct CostHistoryChartMenuView: View {
363363
var maxDetailRows = 0
364364
var hasModeDetails = false
365365
for entry in sorted {
366-
guard let costUSD = entry.costUSD, costUSD >= 0 else { continue }
367-
guard let date = self.dateFromDayKey(entry.date) else { continue }
366+
guard let (costUSD, date) = self.chartPointInput(for: entry) else { continue }
368367
let point = Point(
369368
date: date,
370369
costUSD: costUSD,
@@ -455,6 +454,12 @@ struct CostHistoryChartMenuView: View {
455454
return comps.date
456455
}
457456

457+
private static func chartPointInput(for entry: DailyEntry) -> (costUSD: Double, date: Date)? {
458+
guard let costUSD = entry.costUSD, costUSD >= 0 else { return nil }
459+
guard let date = self.dateFromDayKey(entry.date) else { return nil }
460+
return (costUSD, date)
461+
}
462+
458463
private static func peakPoint(model: Model) -> Point? {
459464
guard let key = model.peakKey else { return nil }
460465
return model.pointsByDateKey[key]
@@ -769,10 +774,29 @@ extension CostHistoryChartMenuView {
769774
let historyDays: Int
770775
let windowLabel: String?
771776
let totalCostBitPattern: UInt64?
772-
let daily: [DailyEntry]
777+
let hasDailyEntries: Bool
778+
let daily: [VisibleDailyFingerprint]
773779
let projects: [VisibleProjectFingerprint]
774780
}
775781

782+
struct VisibleDailyFingerprint: Equatable {
783+
let date: String
784+
let totalTokens: Int?
785+
let requestCount: Int?
786+
let costBitPattern: UInt64?
787+
let modelBreakdowns: [VisibleModelBreakdownFingerprint]
788+
}
789+
790+
struct VisibleModelBreakdownFingerprint: Equatable {
791+
let modelName: String
792+
let costBitPattern: UInt64?
793+
let totalTokens: Int?
794+
let standardCostBitPattern: UInt64?
795+
let priorityCostBitPattern: UInt64?
796+
let standardTokens: Int?
797+
let priorityTokens: Int?
798+
}
799+
776800
struct VisibleProjectFingerprint: Equatable {
777801
let name: String
778802
let path: String?
@@ -789,33 +813,22 @@ extension CostHistoryChartMenuView {
789813
let totalCostBitPattern: UInt64?
790814
}
791815

792-
static func renderFingerprint(from snapshot: CostUsageTokenSnapshot) -> RenderFingerprint {
793-
self.makeRenderFingerprint(RenderFingerprintInputs(
816+
static func renderFingerprint(
817+
from snapshot: CostUsageTokenSnapshot,
818+
provider: UsageProvider) -> RenderFingerprint
819+
{
820+
let projects = provider == .codex ? snapshot.projects : []
821+
return RenderFingerprint(
794822
currencyCode: snapshot.currencyCode,
795823
historyDays: snapshot.historyDays,
796824
windowLabel: snapshot.historyLabel,
797-
totalCostUSD: snapshot.last30DaysCostUSD,
798-
daily: snapshot.daily,
799-
projects: snapshot.projects))
800-
}
801-
802-
private struct RenderFingerprintInputs {
803-
let currencyCode: String
804-
let historyDays: Int
805-
let windowLabel: String?
806-
let totalCostUSD: Double?
807-
let daily: [DailyEntry]
808-
let projects: [CostUsageProjectBreakdown]
809-
}
810-
811-
private static func makeRenderFingerprint(_ inputs: RenderFingerprintInputs) -> RenderFingerprint {
812-
RenderFingerprint(
813-
currencyCode: inputs.currencyCode,
814-
historyDays: inputs.historyDays,
815-
windowLabel: inputs.windowLabel,
816-
totalCostBitPattern: inputs.totalCostUSD.map(\.bitPattern),
817-
daily: inputs.daily,
818-
projects: Array(inputs.projects.prefix(self.maxVisibleProjectRows)).map { project in
825+
totalCostBitPattern: snapshot.last30DaysCostUSD.map(\.bitPattern),
826+
hasDailyEntries: !snapshot.daily.isEmpty,
827+
daily: snapshot.daily
828+
.filter { self.chartPointInput(for: $0) != nil }
829+
.sorted { $0.date < $1.date }
830+
.map(self.visibleDailyFingerprint),
831+
projects: Array(projects.prefix(self.maxVisibleProjectRows)).map { project in
819832
let visibleSources = self.visibleProjectSources(project)
820833
return VisibleProjectFingerprint(
821834
name: project.name,
@@ -833,6 +846,24 @@ extension CostHistoryChartMenuView {
833846
})
834847
}
835848

849+
private static func visibleDailyFingerprint(_ entry: DailyEntry) -> VisibleDailyFingerprint {
850+
VisibleDailyFingerprint(
851+
date: entry.date,
852+
totalTokens: entry.totalTokens,
853+
requestCount: entry.requestCount,
854+
costBitPattern: entry.costUSD.map(\.bitPattern),
855+
modelBreakdowns: self.orderedBreakdownItems(entry.modelBreakdowns ?? []).map { item in
856+
VisibleModelBreakdownFingerprint(
857+
modelName: item.modelName,
858+
costBitPattern: item.costUSD.map(\.bitPattern),
859+
totalTokens: item.totalTokens,
860+
standardCostBitPattern: item.standardCostUSD.map(\.bitPattern),
861+
priorityCostBitPattern: item.priorityCostUSD.map(\.bitPattern),
862+
standardTokens: item.standardCostUSD == nil ? nil : item.standardTokens,
863+
priorityTokens: item.priorityCostUSD == nil ? nil : item.priorityTokens)
864+
})
865+
}
866+
836867
static func _defaultSelectedDateKeyForTesting(provider: UsageProvider, daily: [DailyEntry]) -> String? {
837868
self.defaultSelectedDateKey(model: self.makeModel(provider: provider, daily: daily))
838869
}

Sources/CodexBar/StatusItemController+HostedSubmenus.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ extension StatusItemController {
306306
guard let snapshot = self.tokenSnapshotForCostHistorySubmenu(provider: provider) else {
307307
return .text("none")
308308
}
309-
return .costHistory(CostHistoryChartMenuView.renderFingerprint(from: snapshot))
309+
return .costHistory(CostHistoryChartMenuView.renderFingerprint(from: snapshot, provider: provider))
310310
}
311311

312312
private func usageHistoryRenderSignature(for provider: UsageProvider) -> String {

0 commit comments

Comments
 (0)