Refresh live quota progress from menubar

This commit is contained in:
iamtoruk 2026-05-11 11:09:20 -07:00
parent ce0e1eb116
commit 33649e0a65

View file

@ -6,6 +6,7 @@ private let refreshIntervalSeconds: UInt64 = 30
private let nanosPerSecond: UInt64 = 1_000_000_000
private let refreshIntervalNanos: UInt64 = refreshIntervalSeconds * nanosPerSecond
private let forceRefreshWatchdogSeconds: TimeInterval = 90
private let interactiveQuotaRefreshFloorSeconds: TimeInterval = 30
private let statusItemWidth: CGFloat = NSStatusItem.variableLength
private let popoverWidth: CGFloat = 360
private let popoverHeight: CGFloat = 660
@ -262,7 +263,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
forceRefreshTask = Task {
async let main: Void = store.refresh(includeOptimize: false, force: true, showLoading: true)
async let today: Void = store.refreshQuietly(period: .today)
_ = await (main, today)
async let quotas: Bool = refreshLiveQuotaProgressIfDue()
_ = await (main, today, quotas)
refreshStatusButton()
await MainActor.run { [weak self] in
guard let self, self.forceRefreshGeneration == generation else { return }
@ -296,6 +298,51 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
}
fileprivate var lastSubscriptionRefreshAt: Date?
fileprivate var lastCodexRefreshAt: Date?
@discardableResult
private func refreshLiveQuotaProgressIfDue(force: Bool = false) async -> Bool {
let cadence = SubscriptionRefreshCadence.current
if !force && cadence == .manual { return false }
let now = Date()
let threshold = force ? 0 : TimeInterval(cadence.rawValue)
let shouldRefreshClaude = force || now.timeIntervalSince(lastSubscriptionRefreshAt ?? .distantPast) >= threshold
let shouldRefreshCodex = force || now.timeIntervalSince(lastCodexRefreshAt ?? .distantPast) >= threshold
guard shouldRefreshClaude || shouldRefreshCodex else { return false }
switch (shouldRefreshClaude, shouldRefreshCodex) {
case (true, true):
async let claude = store.refreshSubscriptionReportingSuccess()
async let codex = store.refreshCodexReportingSuccess()
if await claude { lastSubscriptionRefreshAt = Date() }
if await codex { lastCodexRefreshAt = Date() }
case (true, false):
if await store.refreshSubscriptionReportingSuccess() {
lastSubscriptionRefreshAt = Date()
}
case (false, true):
if await store.refreshCodexReportingSuccess() {
lastCodexRefreshAt = Date()
}
case (false, false):
break
}
return true
}
private func refreshLiveQuotaProgressForPopoverOpen() {
let now = Date()
let claudeElapsed = now.timeIntervalSince(lastSubscriptionRefreshAt ?? .distantPast)
let codexElapsed = now.timeIntervalSince(lastCodexRefreshAt ?? .distantPast)
guard claudeElapsed >= interactiveQuotaRefreshFloorSeconds ||
codexElapsed >= interactiveQuotaRefreshFloorSeconds else { return }
Task { [weak self] in
guard let self else { return }
_ = await self.refreshLiveQuotaProgressIfDue(force: true)
}
}
private func startRefreshLoop() {
refreshLoopTask?.cancel()
@ -303,10 +350,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
// Provider refreshes only run when the user has explicitly connected.
// Each refresh is a no-op until its corresponding bootstrap flag is set.
if let self {
async let claude = self.store.refreshSubscriptionReportingSuccess()
async let codex = self.store.refreshCodexReportingSuccess()
if await claude { self.lastSubscriptionRefreshAt = Date() }
if await codex { self.lastCodexRefreshAt = Date() }
await self.refreshLiveQuotaProgressIfDue(force: true)
}
while !Task.isCancelled {
guard let self else { return }
@ -332,26 +376,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
// (not last attempt) so an intermittent failure doesn't reset
// the timer. Each provider has its own anchor so a Codex 429
// doesn't delay a due Claude refresh.
let cadence = SubscriptionRefreshCadence.current
if cadence != .manual {
let claudeElapsed = Date().timeIntervalSince(self.lastSubscriptionRefreshAt ?? .distantPast)
if claudeElapsed >= TimeInterval(cadence.rawValue) {
let succeeded = await self.store.refreshSubscriptionReportingSuccess()
if succeeded { self.lastSubscriptionRefreshAt = Date() }
}
let codexElapsed = Date().timeIntervalSince(self.lastCodexRefreshAt ?? .distantPast)
if codexElapsed >= TimeInterval(cadence.rawValue) {
let succeeded = await self.store.refreshCodexReportingSuccess()
if succeeded { self.lastCodexRefreshAt = Date() }
}
}
await self.refreshLiveQuotaProgressIfDue()
try? await Task.sleep(nanoseconds: refreshIntervalNanos)
}
}
}
fileprivate var lastCodexRefreshAt: Date?
@MainActor
func refreshSubscriptionNow() {
manualRefreshTask?.cancel()
@ -376,8 +406,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
// this match reality right now."
let needsTodayTotal = self.store.selectedPeriod != .today || self.store.selectedProvider != .all
async let payload: Void = self.store.refresh(includeOptimize: false, force: true, showLoading: true)
async let claude: Bool = self.store.refreshSubscriptionReportingSuccess()
async let codex: Bool = self.store.refreshCodexReportingSuccess()
async let quotas: Bool = self.refreshLiveQuotaProgressIfDue(force: true)
if needsTodayTotal {
await self.store.refreshQuietly(period: .today)
}
@ -385,8 +414,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
guard self.manualRefreshGeneration == generation, !Task.isCancelled else { return }
self.lastRefreshTime = Date()
self.refreshStatusButton()
if await claude { self.lastSubscriptionRefreshAt = Date() }
if await codex { self.lastCodexRefreshAt = Date() }
_ = await quotas
guard self.manualRefreshGeneration == generation, !Task.isCancelled else { return }
self.manualRefreshTask = nil
if self.refreshLoopTask == nil {
@ -589,6 +617,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
window.collectionBehavior.insert(.canJoinAllSpaces)
window.makeKeyAndOrderFront(nil)
}
refreshLiveQuotaProgressForPopoverOpen()
}
}