control panel updates, vm updates

This commit is contained in:
2026-07-09 02:03:53 -07:00
parent fe7e724447
commit 02a488d68b
5 changed files with 78 additions and 5 deletions
@@ -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):