codeburn/mac/Sources/CodeBurnMenubar/Views/HeroSection.swift
Resham Joshi 7cea9efb31
Add Optimize tab, token display modes, daily budget alerts, and project drill-down (#349)
* Add CodeBurn Pro Mac App Store app

SwiftUI MenuBarExtra with litellm-snapshot pricing, Claude/Codex/Copilot
parsers, session discovery, auto-refresh timer, and dashboard UI matching
the real menubar design.

* Add appstore/ to .gitignore

Private Mac App Store build, not for the public repo.

* Add Optimize tab, token display modes, daily budget alerts, and project drill-down

- New Optimize insight tab with Retry Tax and Routing Waste computations
  (pure math from session data, no LLM required)
- Retry tax: shows money wasted on failed edit retries, per-model breakdown
- Routing waste: counterfactual savings vs cheapest reliable model, per-model
- Token display modes: Cost ($), Tokens (up/down split), Total Tokens
- Daily budget alert: configurable threshold, flame turns yellow when exceeded
- Project drill-down: click project rows to see per-session cost, tokens, models
- Period-aware top sessions: 30-day view now shows 30-day costliest sessions
- Friendly project names: show directory name instead of full path, Home for ~
- Session cache TTL bumped to 180s to prevent re-parsing on non-today periods
- Audit fixes: array mutation, percentage rounding, ForEach ID collision,
  baseline minimum threshold, editTurns scoping, first-of-month edge case

* Fix model badge ForEach ID collision and budget warning provider filter

- SessionDetailsList model badges used id: \.name which silently drops
  duplicate model entries. Switched to enumerated offset-based ID.
- Hero budget warning compared against provider-filtered payload instead
  of todayPayload (all providers). Now matches the menubar flame tint.
2026-05-18 14:51:15 -07:00

106 lines
4.4 KiB
Swift

import SwiftUI
struct HeroSection: View {
@Environment(AppStore.self) private var store
var body: some View {
VStack(alignment: .leading, spacing: 8) {
SectionCaption(text: caption)
HStack(alignment: .firstTextBaseline) {
Text(heroText)
.font(.system(size: 32, weight: .semibold, design: .rounded))
.monospacedDigit()
.tracking(-1)
.foregroundStyle(
LinearGradient(
colors: [Theme.brandAccent, Theme.brandAccentDeep],
startPoint: .top,
endPoint: .bottom
)
)
Spacer()
VStack(alignment: .trailing, spacing: 2) {
if store.displayMetric == .tokens {
HStack(spacing: 2) {
Image(systemName: "arrow.up")
.font(.system(size: 9, weight: .semibold))
Text(formatTokens(Double(store.payload.current.outputTokens)))
}
.font(.system(size: 11))
.monospacedDigit()
.foregroundStyle(.secondary)
HStack(spacing: 2) {
Image(systemName: "arrow.down")
.font(.system(size: 9, weight: .semibold))
Text(formatTokens(Double(store.payload.current.inputTokens)))
}
.font(.system(size: 10.5))
.monospacedDigit()
.foregroundStyle(.tertiary)
} else {
Text("\(store.payload.current.calls.asThousandsSeparated()) calls")
.font(.system(size: 11))
.monospacedDigit()
.foregroundStyle(.secondary)
Text("\(store.payload.current.sessions) sessions")
.font(.system(size: 10.5))
.monospacedDigit()
.foregroundStyle(.tertiary)
}
}
}
if store.selectedPeriod == .today,
store.dailyBudget > 0,
let todayCost = store.todayPayload?.current.cost,
todayCost >= store.dailyBudget {
HStack(spacing: 4) {
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 10))
Text("Daily budget of \(store.dailyBudget.asCurrency()) exceeded")
.font(.system(size: 11, weight: .medium))
}
.foregroundStyle(.orange)
.padding(.top, 2)
}
}
.padding(.horizontal, 14)
.padding(.top, 10)
.padding(.bottom, 12)
}
private var heroText: String {
if store.displayMetric == .tokens || store.displayMetric == .totalTokens {
let total = Double(store.payload.current.inputTokens + store.payload.current.outputTokens)
if total >= 1_000_000_000 { return String(format: "%.2fB tok", total / 1_000_000_000) }
if total >= 1_000_000 { return String(format: "%.1fM tok", total / 1_000_000) }
if total >= 1_000 { return String(format: "%.0fK tok", total / 1_000) }
return String(format: "%.0f tok", total)
}
return store.payload.current.cost.asCurrency()
}
private func formatTokens(_ n: Double) -> String {
if n >= 1_000_000_000 { return String(format: "%.1fB", n / 1_000_000_000) }
if n >= 1_000_000 { return String(format: "%.1fM", n / 1_000_000) }
if n >= 1_000 { return String(format: "%.0fK", n / 1_000) }
return String(format: "%.0f", n)
}
private var caption: String {
let label = store.payload.current.label.isEmpty ? store.selectedPeriod.rawValue : store.payload.current.label
if store.selectedPeriod == .today {
return "\(label) · \(todayDate)"
}
return label
}
private var todayDate: String {
let formatter = DateFormatter()
formatter.dateFormat = "EEE MMM d"
return formatter.string(from: Date())
}
}