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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Antigravity: exclude model quotas without a remaining fraction from family summaries so they no longer mask tracked usage in the automatic menu-bar metric (#1369). Thanks @Martin-Hausleitner!
- Claude: add bundled Fable 5 pricing, account for native 1-hour cache-write usage, and refresh Sonnet 4.6 full-context rates (#1368). Thanks @MoollaMore!
- Claude: show a direct claude.ai re-login action when a configured web session expires or becomes invalid (#1377). Thanks @LeoLin990405!
- Menu: reuse unchanged hosted chart submenus and precompute utilization history models to reduce expand and hover stalls (#1379). Thanks @hhh2210!
- Menu bar: defer data-refresh rebuilds until the tracked menu closes, avoiding multi-second WindowServer stalls with slower providers such as Grok (#1376). Thanks @jangisaac-dev!
- Xiaomi MiMo: import automatic session cookies from Safari, Chrome variants, Firefox, and Edge instead of limiting discovery to Chrome (#1304). Thanks @Yuxin-Qiao!

Expand Down
48 changes: 25 additions & 23 deletions Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ struct PlanUtilizationHistoryChartMenuView: View {
}

private let provider: UsageProvider
private let histories: [PlanUtilizationSeriesHistory]
private let snapshot: UsageSnapshot?
private let visibleSeries: [VisibleSeries]
private let modelsBySeriesID: [String: Model]
private let emptyModel: Model
private let width: CGFloat

@State private var selectedSeriesID: String?
Expand All @@ -78,32 +79,33 @@ struct PlanUtilizationHistoryChartMenuView: View {
width: CGFloat)
{
self.provider = provider
self.histories = histories
self.snapshot = snapshot
let visibleSeries = Self.visibleSeries(
histories: histories,
provider: provider,
snapshot: snapshot)
let referenceDate = Date()
self.visibleSeries = visibleSeries
self.modelsBySeriesID = Dictionary(uniqueKeysWithValues: visibleSeries.map {
($0.id, Self.makeModel(history: $0.history, provider: provider, referenceDate: referenceDate))
})
self.emptyModel = Self.emptyModel(provider: provider)
self.width = width
}

var body: some View {
let visibleSeries = Self.visibleSeries(
histories: self.histories,
provider: self.provider,
snapshot: self.snapshot)
let effectiveSelectedSeries = visibleSeries.first(where: { $0.id == self.selectedSeriesID }) ?? visibleSeries
.first
let model = Self.makeModel(
history: effectiveSelectedSeries?.history,
provider: self.provider,
referenceDate: Date())
let effectiveSelectedSeries = self.visibleSeries.first(where: { $0.id == self.selectedSeriesID })
?? self.visibleSeries.first
let model = effectiveSelectedSeries.flatMap { self.modelsBySeriesID[$0.id] } ?? self.emptyModel

VStack(alignment: .leading, spacing: 10) {
if visibleSeries.count > 1 {
if self.visibleSeries.count > 1 {
Picker(selection: Binding(
get: { effectiveSelectedSeries?.id ?? "" },
set: { newValue in
self.selectedSeriesID = newValue
self.selectedPointID = nil
})) {
ForEach(visibleSeries) { series in
ForEach(self.visibleSeries) { series in
Text(series.title).tag(series.id)
}
} label: {
Expand Down Expand Up @@ -172,9 +174,9 @@ struct PlanUtilizationHistoryChartMenuView: View {
.padding(.horizontal, 16)
.padding(.vertical, 10)
.frame(minWidth: self.width, maxWidth: .infinity, alignment: .topLeading)
.task(id: visibleSeries.map(\.id).joined(separator: ",")) {
guard let firstVisibleSeries = visibleSeries.first else { return }
guard !visibleSeries.contains(where: { $0.id == self.selectedSeriesID }) else { return }
.task(id: self.visibleSeries.map(\.id).joined(separator: ",")) {
guard let firstVisibleSeries = self.visibleSeries.first else { return }
guard !self.visibleSeries.contains(where: { $0.id == self.selectedSeriesID }) else { return }
self.selectedSeriesID = firstVisibleSeries.id
self.selectedPointID = nil
}
Expand Down Expand Up @@ -228,12 +230,12 @@ struct PlanUtilizationHistoryChartMenuView: View {
}
}

private nonisolated static func mergedEntries(
nonisolated static func mergedEntries(
_ entries: [PlanUtilizationHistoryEntry]) -> [PlanUtilizationHistoryEntry]
{
entries.reduce(into: []) { result, entry in
guard !result.contains(entry) else { return }
result.append(entry)
var seen: Set<PlanUtilizationHistoryEntry> = []
return entries.filter { entry in
seen.insert(entry).inserted
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/PlanUtilizationHistoryStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct PlanUtilizationSeriesName: RawRepresentable, Hashable, Codable, Expressib
}
}

struct PlanUtilizationHistoryEntry: Codable, Equatable {
struct PlanUtilizationHistoryEntry: Codable, Equatable, Hashable {
let capturedAt: Date
let usedPercent: Double
let resetsAt: Date?
Expand Down
152 changes: 143 additions & 9 deletions Sources/CodexBar/StatusItemController+HostedSubmenus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import CodexBarCore
import SwiftUI

extension StatusItemController {
private struct HostedSubviewIdentity {
let chartID: String
let provider: UsageProvider?
let providerRawValue: String?
}

func isHostedSubviewMenu(_ menu: NSMenu) -> Bool {
let ids: Set = [
Self.usageBreakdownChartID,
Expand Down Expand Up @@ -37,16 +43,21 @@ extension StatusItemController {
return submenu
}

func hydrateHostedSubviewMenuIfNeeded(_ menu: NSMenu, width requestedWidth: CGFloat? = nil) {
@discardableResult
func hydrateHostedSubviewMenuIfNeeded(_ menu: NSMenu, width requestedWidth: CGFloat? = nil) -> Bool {
guard let placeholder = menu.items.first,
menu.items.count == 1,
placeholder.view == nil,
let chartID = placeholder.representedObject as? String
else {
return
return false
}

let width = requestedWidth ?? self.renderedMenuWidth(for: menu.supermenu ?? menu)
let identity = HostedSubviewIdentity(
chartID: chartID,
provider: placeholder.toolTip.flatMap(UsageProvider.init(rawValue:)),
providerRawValue: placeholder.toolTip)
menu.removeAllItems()

let didHydrate: Bool = switch chartID {
Expand Down Expand Up @@ -90,8 +101,14 @@ extension StatusItemController {
false
}

guard !didHydrate else { return }
self.appendHostedSubviewUnavailableItem(to: menu, chartID: chartID, providerRawValue: placeholder.toolTip)
if !didHydrate {
self.appendHostedSubviewUnavailableItem(
to: menu,
chartID: chartID,
providerRawValue: placeholder.toolTip)
}
self.recordHostedSubviewRenderSignature(for: menu, identity: identity, width: width)
return true
}

func refreshHostedSubviewMenu(_ menu: NSMenu) {
Expand All @@ -100,6 +117,13 @@ extension StatusItemController {
self.refreshHostedSubviewHeights(in: menu)
return
}
let signature = self.hostedSubviewRenderSignature(identity: identity, width: width)
if self.hostedSubviewRenderSignatures.object(forKey: menu) as String? == signature {
if identity.chartID == Self.zaiHourlyUsageChartID {
self.refreshHostedSubviewHeights(in: menu)
}
return
}

menu.removeAllItems()
let didHydrate: Bool = switch identity.chartID {
Expand Down Expand Up @@ -135,29 +159,139 @@ extension StatusItemController {
false
}

if didHydrate {
self.refreshHostedSubviewHeights(in: menu)
} else {
if !didHydrate {
self.appendHostedSubviewUnavailableItem(
to: menu,
chartID: identity.chartID,
providerRawValue: identity.provider?.rawValue ?? identity.providerRawValue)
}
self.hostedSubviewRenderSignatures.setObject(signature as NSString, forKey: menu)
}

private func hostedSubviewIdentity(for menu: NSMenu)
-> (chartID: String, provider: UsageProvider?, providerRawValue: String?)? {
-> HostedSubviewIdentity? {
for item in menu.items {
guard let chartID = item.representedObject as? String else { continue }
let providerRawValue = item.toolTip
return (
return HostedSubviewIdentity(
chartID: chartID,
provider: providerRawValue.flatMap(UsageProvider.init(rawValue:)),
providerRawValue: providerRawValue)
}
return nil
}

private func recordHostedSubviewRenderSignature(
for menu: NSMenu,
identity: HostedSubviewIdentity,
width: CGFloat)
{
let signature = self.hostedSubviewRenderSignature(identity: identity, width: width)
self.hostedSubviewRenderSignatures.setObject(signature as NSString, forKey: menu)
}

private func hostedSubviewRenderSignature(
identity: HostedSubviewIdentity,
width: CGFloat) -> String
{
let contentSignature: String = switch identity.chartID {
case Self.usageBreakdownChartID:
Self.dashboardBreakdownReadinessSignature(
OpenAIDashboardDailyBreakdown.removingSkillUsageServices(
from: self.store.openAIDashboard?.usageBreakdown ?? []))
case Self.creditsHistoryChartID:
Self.dashboardBreakdownReadinessSignature(self.store.openAIDashboard?.dailyBreakdown ?? [])
case Self.costHistoryChartID:
identity.provider.map(self.costHistoryRenderSignature(for:)) ?? "missing-provider"
case Self.usageHistoryChartID:
identity.provider.map(self.usageHistoryRenderSignature(for:)) ?? "missing-provider"
case Self.storageBreakdownID:
identity.provider.map(self.storageBreakdownRenderSignature(for:)) ?? "missing-provider"
case Self.zaiHourlyUsageChartID:
identity.provider.map(self.zaiHourlyUsageRenderSignature(for:)) ?? "missing-provider"
default:
"unknown"
}
return [
identity.chartID,
identity.providerRawValue ?? "",
String(Double(width).bitPattern, radix: 16),
contentSignature,
].joined(separator: "|")
}

private func costHistoryRenderSignature(for provider: UsageProvider) -> String {
guard let snapshot = self.tokenSnapshotForCostHistorySubmenu(provider: provider) else { return "none" }
return [
snapshot.currencyCode,
"\(snapshot.historyDays)",
snapshot.historyLabel ?? "",
snapshot.last30DaysCostUSD.map { String($0.bitPattern, radix: 16) } ?? "nil",
String(reflecting: snapshot.daily),
].joined(separator: "|")
}

private func usageHistoryRenderSignature(for provider: UsageProvider) -> String {
let snapshot = self.store.snapshot(for: provider)
let selection = self.store.planUtilizationHistorySelection(for: provider)
return [
"\(self.store.planUtilizationHistoryRevision)",
"\(Int(Date().timeIntervalSince1970 / 60))",
selection.accountKey ?? "unscoped",
snapshot?.primary == nil ? "0" : "1",
snapshot?.secondary == nil ? "0" : "1",
snapshot?.tertiary == nil ? "0" : "1",
].joined(separator: "|")
}

private func storageBreakdownRenderSignature(for provider: UsageProvider) -> String {
guard let footprint = self.store.storageFootprint(for: provider) else { return "none" }
let components = footprint.components
.map { "\($0.path)=\($0.totalBytes)" }
.joined(separator: ";")
return [
"\(footprint.totalBytes)",
footprint.paths.joined(separator: ";"),
footprint.missingPaths.joined(separator: ";"),
footprint.unreadablePaths.joined(separator: ";"),
components,
String(Double(self.storageBreakdownMenuMaxHeight()).bitPattern, radix: 16),
].joined(separator: "|")
}

private func zaiHourlyUsageRenderSignature(for provider: UsageProvider) -> String {
guard let modelUsage = self.store.snapshot(for: provider)?.zaiUsage?.modelUsage else { return "none" }
return Self.zaiHourlyUsageRenderSignature(modelUsage: modelUsage, now: Date())
}

static func zaiHourlyUsageRenderSignature(modelUsage: ZaiModelUsageData, now: Date) -> String {
let models = modelUsage.modelDataList
.map { model in
let usage = model.tokensUsage
.map { $0.map(String.init) ?? "nil" }
.joined(separator: ",")
return "\(model.modelName ?? "")=\(usage)"
}
.joined(separator: ";")
let ranges: [ZaiHourlyRange] = [.today(referenceDate: now), .last24h]
let visibleBars = ranges
.map { range in
ZaiHourlyBars.from(modelData: modelUsage, range: range, now: now)
.map { bar in
let segments = bar.segments
.map { "\($0.model)=\($0.tokens)" }
.joined(separator: ",")
return "\(bar.label):\(segments)"
}
.joined(separator: ";")
}
return [
modelUsage.xTime.joined(separator: ","),
models,
visibleBars.joined(separator: "|"),
].joined(separator: "|")
}

private func appendHostedSubviewUnavailableItem(
to menu: NSMenu,
chartID: String,
Expand Down
5 changes: 3 additions & 2 deletions Sources/CodexBar/StatusItemController+Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ extension StatusItemController {
let menuTrackingWasIdle = self.openMenus.isEmpty

if self.isHostedSubviewMenu(menu) {
self.hydrateHostedSubviewMenuIfNeeded(menu)
self.refreshHostedSubviewHeights(in: menu)
if !self.hydrateHostedSubviewMenuIfNeeded(menu) {
self.refreshHostedSubviewMenu(menu)
}
if self.isMenuRefreshEnabled, self.isOpenAIWebSubviewMenu(menu) {
self.deferOpenAIDashboardRefreshUntilMenuCloses(reason: "submenu open")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ extension StatusItemController {
return parts.joined(separator: "|")
}

private static func dashboardBreakdownReadinessSignature(
static func dashboardBreakdownReadinessSignature(
_ breakdown: [OpenAIDashboardDailyBreakdown]) -> String
{
breakdown
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/StatusItemController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin
var latestRequiredMenuRebuildVersion: Int = 0
var menuVersions: [ObjectIdentifier: Int] = [:]
var menuReadinessSignatures: [ObjectIdentifier: String] = [:]
let hostedSubviewRenderSignatures = NSMapTable<NSMenu, NSString>.weakToStrongObjects()
var menuCardHeightCache: [MenuCardHeightCacheKey: CGFloat] = [:]
var measuredStandardMenuWidthCache: [String: CGFloat] = [:]
var lastMenuAdjunctReadinessSignature = ""
Expand Down
8 changes: 7 additions & 1 deletion Sources/CodexBar/UsageStore+PlanUtilization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ extension UsageStore {
}

func planUtilizationHistory(for provider: UsageProvider) -> [PlanUtilizationSeriesHistory] {
self.planUtilizationHistorySelection(for: provider).histories
}

func planUtilizationHistorySelection(for provider: UsageProvider)
-> (accountKey: String?, histories: [PlanUtilizationSeriesHistory])
{
var providerBuckets = self.planUtilizationHistory[provider] ?? PlanUtilizationHistoryBuckets()
let originalProviderBuckets = providerBuckets
let accountKey = self.resolvePlanUtilizationAccountKey(
Expand All @@ -50,7 +56,7 @@ extension UsageStore {
await self.planUtilizationPersistenceCoordinator.enqueue(snapshotToPersist)
}
}
return providerBuckets.histories(for: accountKey)
return (accountKey, providerBuckets.histories(for: accountKey))
}

func codexPlanUtilizationHistories(forVisibleAccount account: CodexVisibleAccount)
Expand Down
27 changes: 27 additions & 0 deletions Tests/CodexBarTests/PlanUtilizationHistoryChartMenuViewTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation
import Testing
@testable import CodexBar

@Suite
struct PlanUtilizationHistoryChartMenuViewTests {
@Test
func `merged entries preserve first occurrence order while removing duplicates`() {
let first = PlanUtilizationHistoryEntry(
capturedAt: Date(timeIntervalSince1970: 100),
usedPercent: 10,
resetsAt: Date(timeIntervalSince1970: 200))
let second = PlanUtilizationHistoryEntry(
capturedAt: Date(timeIntervalSince1970: 300),
usedPercent: 20,
resetsAt: nil)

let merged = PlanUtilizationHistoryChartMenuView.mergedEntries([
first,
second,
first,
second,
])

#expect(merged == [first, second])
}
}
Loading