Merge branch 'dev' into canary
This commit is contained in:
@@ -1828,7 +1828,8 @@ final class RemoteStore: ObservableObject {
|
||||
private func demoMutateTodos(_ transform: ([WireTodo]) -> [WireTodo]) {
|
||||
dashboard = DashboardSnapshot(
|
||||
counts: dashboard.counts, activity: dashboard.activity, projects: dashboard.projects,
|
||||
todos: transform(dashboard.todos), usage: dashboard.usage, statusFeeds: dashboard.statusFeeds)
|
||||
todos: transform(dashboard.todos), usage: dashboard.usage,
|
||||
codexUsage: dashboard.codexUsage, statusFeeds: dashboard.statusFeeds)
|
||||
}
|
||||
|
||||
|
||||
@@ -1910,6 +1911,7 @@ extension DashboardSnapshot {
|
||||
tokens: snaps.reduce(0) { $0 + $1.counts.tokens })
|
||||
|
||||
let usage = snaps.compactMap(\.usage).first
|
||||
let codexUsage = snaps.compactMap(\.codexUsage).first
|
||||
|
||||
var seenProviders = Set<String>()
|
||||
var statusFeeds: [WireStatusFeed] = []
|
||||
@@ -1921,7 +1923,7 @@ extension DashboardSnapshot {
|
||||
|
||||
return DashboardSnapshot(
|
||||
counts: counts, activity: activity, projects: projects,
|
||||
todos: todos, usage: usage, statusFeeds: statusFeeds)
|
||||
todos: todos, usage: usage, codexUsage: codexUsage, statusFeeds: statusFeeds)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,8 +82,9 @@ struct HomeView: View {
|
||||
statCards
|
||||
|
||||
// The Mac's usage gauges (5-hour / weekly windows); hidden when the host
|
||||
// doesn't project usage.
|
||||
// doesn't project usage. Codex's card follows, shown once a Codex chat reports.
|
||||
QuotaCard(usage: store.dashboard.usage)
|
||||
CodexQuotaCard(usage: store.dashboard.codexUsage)
|
||||
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text("Activity").font(.headline)
|
||||
|
||||
@@ -26,7 +26,11 @@ enum QuotaFormat {
|
||||
}
|
||||
|
||||
static func resetCaption(_ window: WireUsageWindow, now: Date) -> String? {
|
||||
guard let resetsAt = window.resetsAt else { return nil }
|
||||
resetCaption(window.resetsAt, now: now)
|
||||
}
|
||||
|
||||
static func resetCaption(_ resetsAt: Date?, now: Date) -> String? {
|
||||
guard let resetsAt else { return nil }
|
||||
let remaining = resetsAt.timeIntervalSince(now)
|
||||
guard remaining > 0 else { return "resetting…" }
|
||||
return "resets in \(duration(remaining))"
|
||||
@@ -101,6 +105,70 @@ struct QuotaCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Codex quota card (the Mac's CodexUsageCard, dashboard form)
|
||||
|
||||
/// The Codex counterpart to `QuotaCard`, rendered from `DashboardSnapshot.codexUsage` — the
|
||||
/// rolling windows the host's `codex` agent reported (`primary` ≈ 5-hour, `secondary` ≈ weekly).
|
||||
/// Renders nothing until a Codex chat has reported usage, so Claude-only users never carry an
|
||||
/// empty card. Same thresholds, icons, and copy as the Claude card.
|
||||
struct CodexQuotaCard: View {
|
||||
let usage: CodexUsage?
|
||||
|
||||
var body: some View {
|
||||
if let usage, usage.hasData {
|
||||
TimelineView(.periodic(from: .now, by: 30)) { context in
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "gauge.with.dots.needle.67percent").foregroundStyle(.secondary)
|
||||
Text("Codex").font(.headline)
|
||||
}
|
||||
windowRow(usage.primary, fallbackLabel: "5-hour limit",
|
||||
icon: QuotaFormat.fiveHourIcon, now: context.date)
|
||||
windowRow(usage.secondary, fallbackLabel: "Weekly limit",
|
||||
icon: QuotaFormat.weeklyIcon, now: context.date)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.card()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func windowRow(
|
||||
_ window: CodexUsageWindow?, fallbackLabel: String, icon: String, now: Date
|
||||
) -> some View {
|
||||
if let window {
|
||||
let utilization = window.utilization(at: now)
|
||||
let color = QuotaFormat.color(forPercent: utilization)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 5) {
|
||||
Image(systemName: icon).font(.caption).foregroundStyle(.secondary)
|
||||
Text(window.label ?? fallbackLabel).font(.subheadline)
|
||||
Spacer()
|
||||
Text(QuotaFormat.percent(utilization))
|
||||
.font(.subheadline.monospacedDigit().weight(.semibold))
|
||||
.foregroundStyle(color)
|
||||
}
|
||||
bar(fraction: utilization / 100, color: color)
|
||||
if let caption = QuotaFormat.resetCaption(window.resetsAt, now: now) {
|
||||
Text(caption).font(.caption2).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func bar(fraction: Double, color: Color) -> some View {
|
||||
GeometryReader { geo in
|
||||
ZStack(alignment: .leading) {
|
||||
Capsule().fill(Color.secondary.opacity(0.18))
|
||||
Capsule().fill(color)
|
||||
.frame(width: max(3, geo.size.width * min(1, max(0, fraction))))
|
||||
}
|
||||
}
|
||||
.frame(height: 6)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Status pill (the Mac's StatusFeedIndicator)
|
||||
|
||||
/// A compact service-status pill: a calm green check while every watched provider is
|
||||
|
||||
@@ -263,7 +263,7 @@ final class IncrementalTranscriptProjection {
|
||||
if seenToolItem.insert(c.toolCallID).inserted { creations[r] = .tool }
|
||||
case .toolResult(let result):
|
||||
spans[result.toolCallID]?.resultSeen = true
|
||||
case .toolCallInputDelta, .fileChange, .approvalResolved:
|
||||
case .toolCallInputDelta, .fileChange, .approvalResolved, .codexUsage:
|
||||
break // refs (if any) tracked above; creates nothing
|
||||
case .turnCompleted, .runFinished:
|
||||
creations[r] = .separator
|
||||
|
||||
@@ -478,6 +478,8 @@ enum TranscriptProjection {
|
||||
items.append(.init(id: "usage-\(event.seq)", seq: event.seq, kind: .usage(usage)))
|
||||
case .rateLimit(let limit):
|
||||
items.append(.init(id: "rate-\(event.seq)", seq: event.seq, kind: .rateLimit(limit)))
|
||||
case .codexUsage:
|
||||
break // account-wide quota telemetry, folded into the dashboard — not the transcript
|
||||
case .turnCompleted:
|
||||
items.append(.init(id: "turn-\(event.seq)", seq: event.seq, kind: .turnBoundary))
|
||||
case .approvalRequested(let req):
|
||||
|
||||
Reference in New Issue
Block a user