diff --git a/mac/Sources/CodeBurnMenubar/Views/HeatmapSection.swift b/mac/Sources/CodeBurnMenubar/Views/HeatmapSection.swift index d29da8b52..615ffb255 100644 --- a/mac/Sources/CodeBurnMenubar/Views/HeatmapSection.swift +++ b/mac/Sources/CodeBurnMenubar/Views/HeatmapSection.swift @@ -138,6 +138,7 @@ private struct InsightPillSwitcher: View { private struct TrendInsight: View { let days: [DailyHistoryEntry] let period: Period + @AppStorage("codeburn.trendShowsTokens") private var showTokens = false private var trendDayCount: Int { switch period { @@ -156,10 +157,9 @@ private struct TrendInsight: View { let dayCount = trendDayCount let bars = buildTrendBars(from: days, dayCount: dayCount) let stats = computeTrendStats(bars: bars, allDays: days, dayCount: dayCount) - // Tokens are real for the .all-providers view; per-provider history doesn't carry - // token breakdown yet, so fall back to $ when no tokens are present. + // Per-provider history doesn't carry a token breakdown yet, so tokens mode falls back to $. let totalTokens = bars.reduce(0.0) { $0 + $1.tokens } - let useTokens = totalTokens > 0 + let useTokens = showTokens && totalTokens > 0 let metric: (TrendBar) -> Double = useTokens ? { $0.tokens } : { $0.cost } let maxValue = max(bars.map(metric).max() ?? 1, 0.01) let avgValue = bars.isEmpty ? 0 : bars.map(metric).reduce(0, +) / Double(bars.count) @@ -178,15 +178,20 @@ private struct TrendInsight: View { .foregroundStyle(.primary) } Spacer() - if let delta = stats.deltaPercent { - HStack(spacing: 3) { - Image(systemName: delta >= 0 ? "arrow.up.right" : "arrow.down.right") - .font(.system(size: 9, weight: .bold)) - Text(L("%1$@%% vs prior %2$lldd", (delta >= 0 ? "+" : "") + String(format: "%.0f", delta), dayCount)) - .font(.system(size: 10.5)) - .monospacedDigit() + VStack(alignment: .trailing, spacing: 4) { + if totalTokens > 0 { + TrendMetricToggle(showTokens: $showTokens) + } + if let delta = stats.deltaPercent { + HStack(spacing: 3) { + Image(systemName: delta >= 0 ? "arrow.up.right" : "arrow.down.right") + .font(.system(size: 9, weight: .bold)) + Text(L("%1$@%% vs prior %2$lldd", (delta >= 0 ? "+" : "") + String(format: "%.0f", delta), dayCount)) + .font(.system(size: 10.5)) + .monospacedDigit() + } + .foregroundStyle(Theme.brandAccent) } - .foregroundStyle(Theme.brandAccent) } } @@ -234,6 +239,33 @@ private struct TrendInsight: View { } } +private struct TrendMetricToggle: View { + @Binding var showTokens: Bool + + var body: some View { + HStack(spacing: 2) { + pill(L("Cost"), selected: !showTokens) { showTokens = false } + pill(L("Tokens"), selected: showTokens) { showTokens = true } + } + } + + private func pill(_ label: String, selected: Bool, action: @escaping () -> Void) -> some View { + Button(action: action) { + Text(label) + .font(.system(size: 10, weight: .medium)) + .fixedSize() + .foregroundStyle(selected ? AnyShapeStyle(.white) : AnyShapeStyle(.secondary)) + .padding(.horizontal, 7) + .padding(.vertical, 2) + .background( + RoundedRectangle(cornerRadius: 5) + .fill(selected ? AnyShapeStyle(Theme.brandAccent) : AnyShapeStyle(Color.secondary.opacity(0.10))) + ) + } + .buttonStyle(.plain) + } +} + private struct TrendChart: View { let bars: [TrendBar] let maxValue: Double