mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-04 20:13:18 +00:00
Codex quota in the menu bar came only from frozen local session logs, so it
showed stale data ("older session") that no refresh could update. Fetch it live
from the same source the dashboard uses, under the existing Claude-style safety
controls (10-min TTL, in-flight coalescing, 429 backoff, circuit breaker,
serve-stale), falling back to local logs when offline or rate-limited. A success
with no usable 5h/weekly window keeps the local reading rather than caching a
contentless row. The footer refresh and a new inline button on the Codex card
force a live pull past the open debounce, and a forced /summary re-pulls native
rows while serving the last-known cached rows if the live pull overruns its
budget, so the Claude/Codex cards never blank mid-refresh.
The spend strip gains a Today / 7d / 30d selector (default 7d), a taller chart,
and bottom time-axis labels (local hours for today, weekday for 7d, dates for
30d). The analytics endpoint now exposes a 24-bucket hourly series for today,
converted from the pipeline's UTC hour keys to the user's local clock so the
intra-day chart matches the dashboard.
65 lines
2.1 KiB
Swift
65 lines
2.1 KiB
Swift
import Foundation
|
|
|
|
// MARK: - SpendChartStyle
|
|
|
|
/// User-selectable render style for the spend sparkline in the dropdown.
|
|
///
|
|
/// `.bars` (default) draws the existing RoundedRectangle bar chart.
|
|
/// `.line` draws a Path-based line graph with a faint area fill, which is
|
|
/// better for spotting trend direction across the 30-day window.
|
|
///
|
|
/// Sendable + CaseIterable so the harness can iterate all cases and the value
|
|
/// can cross actor boundaries safely.
|
|
public enum SpendChartStyle: String, CaseIterable, Sendable {
|
|
case bars
|
|
case line
|
|
}
|
|
|
|
// MARK: - SpendChartStyleStore
|
|
|
|
/// Persists the chosen spend-chart style. Mirrors the BarAppearanceStore pattern:
|
|
/// a UserDefaults key, a static load, and a static save. The `?? .bars` fallback
|
|
/// on a nil/unrecognized raw value is the sole source of the default.
|
|
public enum SpendChartStyleStore {
|
|
public static let defaultsKey = "ccsbar.spendChartStyle"
|
|
|
|
public static func load() -> SpendChartStyle {
|
|
let raw = UserDefaults.standard.string(forKey: defaultsKey)
|
|
?? SpendChartStyle.bars.rawValue
|
|
return SpendChartStyle(rawValue: raw) ?? .bars
|
|
}
|
|
|
|
public static func save(_ style: SpendChartStyle) {
|
|
UserDefaults.standard.set(style.rawValue, forKey: defaultsKey)
|
|
}
|
|
}
|
|
|
|
// MARK: - SpendPeriod
|
|
|
|
/// Time window for the spend sparkline selector: today (hourly), last 7 days,
|
|
/// or last 30 days. Mirrors the SpendChartStyle pattern: CaseIterable + Sendable.
|
|
public enum SpendPeriod: String, CaseIterable, Sendable {
|
|
case today
|
|
case last7d
|
|
case last30d
|
|
}
|
|
|
|
// MARK: - SpendPeriodStore
|
|
|
|
/// Persists the chosen spend period. Mirrors SpendChartStyleStore exactly:
|
|
/// a UserDefaults key, a static load, and a static save. The `?? .last7d`
|
|
/// fallback is the sole source of the default.
|
|
public enum SpendPeriodStore {
|
|
public static let defaultsKey = "ccsbar.spendPeriod"
|
|
|
|
public static func load() -> SpendPeriod {
|
|
let raw = UserDefaults.standard.string(forKey: defaultsKey)
|
|
?? SpendPeriod.last7d.rawValue
|
|
return SpendPeriod(rawValue: raw) ?? .last7d
|
|
}
|
|
|
|
public static func save(_ period: SpendPeriod) {
|
|
UserDefaults.standard.set(period.rawValue, forKey: defaultsKey)
|
|
}
|
|
}
|