Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Fixed
- Menu: apply the cost summary display style to every provider's menu card, so Submenu only hides inline cost rows for z.ai and other providers (#2976). Thanks @ar0nbg!
- Cost history: align x-axis date labels with their bars in status-menu charts (#2974). Thanks @Yuxin-Qiao!
- Codex: keep CLI-owned `auth.json` read-only during usage refresh, delegate stale native credentials to CLI recovery, and fail closed for stale external OAuth files (#2944). Thanks @Yuxin-Qiao!
- Usage & Spend: keep safely priced Codex totals visible after completed history scans when request-tier uncertainty leaves some days unpriced (#2948). Thanks @Atopoz for the report!
- Vertex AI: match Cloud Monitoring quota usage without a `limit_name` to its unambiguous same-metric, same-location limit, restoring quota percentages (#2958). Thanks @MachApple!
Expand Down
16 changes: 16 additions & 0 deletions Sources/CodexBar/ChartAxisLabelLayout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SwiftUI

enum ChartAxisLabelLayout {
/// Keeps the label's horizontal center on the x-axis value shared with its bar.
static let barCenteredAnchor = UnitPoint.top

static func barCenterX(slotIndex: Int, slotCount: Int, chartWidth: CGFloat) -> CGFloat? {
guard slotCount > 0, (0..<slotCount).contains(slotIndex), chartWidth >= 0 else { return nil }
let slotWidth = chartWidth / CGFloat(slotCount)
return (CGFloat(slotIndex) + 0.5) * slotWidth
}

static func labelCenterX(tickX: CGFloat, labelWidth: CGFloat, anchor: UnitPoint) -> CGFloat {
tickX + (0.5 - anchor.x) * labelWidth
}
}
41 changes: 1 addition & 40 deletions Sources/CodexBar/CostHistoryChartMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ import SwiftUI
struct CostHistoryChartMenuView: View {
typealias DailyEntry = CostUsageDailyReport.Entry

enum AxisLabelPlacement: Equatable {
case hidden
case centered
case edges
}

enum ChartMetric: CaseIterable, Hashable {
case tokens
case cost
Expand Down Expand Up @@ -187,7 +181,7 @@ struct CostHistoryChartMenuView: View {
AxisGridLine().foregroundStyle(Color.clear)
AxisTick().foregroundStyle(Color.clear)
if let date = value.as(Date.self) {
AxisValueLabel(anchor: Self.xAxisLabelAnchor(for: date, axisDates: model.axisDates)) {
AxisValueLabel(anchor: ChartAxisLabelLayout.barCenteredAnchor) {
Text(date, format: .dateTime.month(.abbreviated).day())
.font(.caption2)
.foregroundStyle(Color(nsColor: .tertiaryLabelColor))
Expand Down Expand Up @@ -614,29 +608,6 @@ struct CostHistoryChartMenuView: View {
detailRowHeight: detailLayout.rowHeight)
}

private static func axisLabelPlacement(for dates: [Date]) -> AxisLabelPlacement {
switch dates.count {
case 0: .hidden
case 1: .centered
default: .edges
}
}

private static func xAxisLabelAnchor(for date: Date, axisDates: [Date]) -> UnitPoint {
switch self.axisLabelPlacement(for: axisDates) {
case .hidden, .centered:
.top
case .edges:
if let first = axisDates.first, Calendar.current.isDate(date, inSameDayAs: first) {
.topLeading
} else if let last = axisDates.last, Calendar.current.isDate(date, inSameDayAs: last) {
.topTrailing
} else {
.top
}
}
}

private static func barColor(for provider: UsageProvider) -> Color {
let color = ProviderAccentPalette.color(for: provider)
return Color(red: color.red, green: color.green, blue: color.blue)
Expand Down Expand Up @@ -1237,16 +1208,6 @@ extension CostHistoryChartMenuView {
metric: self.defaultMetric(provider: provider, daily: daily)).axisDates
}

static func _axisLabelPlacementForTesting(
provider: UsageProvider,
daily: [DailyEntry]) -> AxisLabelPlacement
{
self.axisLabelPlacement(for: self.makeModel(
provider: provider,
daily: daily,
metric: self.defaultMetric(provider: provider, daily: daily)).axisDates)
}

static func _yAxisTickValuesForTesting(maxCostUSD: Double) -> [Double] {
self.yAxisTickValues(maxCostUSD: maxCostUSD)
}
Expand Down
21 changes: 4 additions & 17 deletions Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,13 @@ struct PlanUtilizationHistoryChartMenuView: View {
AxisMarks(values: model.axisIndexes) { value in
AxisGridLine().foregroundStyle(Color.clear)
AxisTick().foregroundStyle(Color.clear)
AxisValueLabel {
AxisValueLabel(anchor: ChartAxisLabelLayout.barCenteredAnchor) {
if let raw = value.as(Double.self) {
let index = Int(raw.rounded())
if let point = model.pointsByIndex[index] {
let isTrailingFullChartLabel = index == model.points.last?.index
&& model.points.count == Layout.maxPoints
Self.axisLabel(
for: point,
windowMinutes: effectiveSelectedSeries?.history.windowMinutes ?? 0,
isTrailingFullChartLabel: isTrailingFullChartLabel)
windowMinutes: effectiveSelectedSeries?.history.windowMinutes ?? 0)
}
}
}
Expand Down Expand Up @@ -606,23 +603,13 @@ struct PlanUtilizationHistoryChartMenuView: View {
return deduplicated.map(Double.init)
}

@ViewBuilder
private static func axisLabel(
for point: Point,
windowMinutes: Int,
isTrailingFullChartLabel: Bool) -> some View
windowMinutes: Int) -> some View
{
let label = Text(point.date.formatted(self.axisFormat(windowMinutes: windowMinutes)))
Text(point.date.formatted(self.axisFormat(windowMinutes: windowMinutes)))
.font(.caption2)
.foregroundStyle(Color(nsColor: .tertiaryLabelColor))

if isTrailingFullChartLabel {
label
.frame(width: 48, alignment: .trailing)
.offset(x: -24)
} else {
label
}
}

private nonisolated static func axisFormat(windowMinutes: Int) -> Date.FormatStyle {
Expand Down
12 changes: 1 addition & 11 deletions Sources/CodexBar/UsageBreakdownChartMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ struct UsageBreakdownChartMenuView: View {
AxisGridLine().foregroundStyle(Color.clear)
AxisTick().foregroundStyle(Color.clear)
if let date = value.as(Date.self) {
AxisValueLabel(anchor: Self.xAxisLabelAnchor(for: date, axisDates: model.axisDates)) {
AxisValueLabel(anchor: ChartAxisLabelLayout.barCenteredAnchor) {
Text(date, format: .dateTime.month(.abbreviated).day())
.font(.caption2)
.foregroundStyle(Color(nsColor: .tertiaryLabelColor))
Expand Down Expand Up @@ -323,16 +323,6 @@ struct UsageBreakdownChartMenuView: View {
return [firstDate, lastDate]
}

private static func xAxisLabelAnchor(for date: Date, axisDates: [Date]) -> UnitPoint {
if let first = axisDates.first, Calendar.current.isDate(date, inSameDayAs: first) {
return .topLeading
}
if let last = axisDates.last, Calendar.current.isDate(date, inSameDayAs: last) {
return .topTrailing
}
return .top
}

private static func dateFromDayKey(_ key: String) -> Date? {
let parts = key.split(separator: "-")
guard parts.count == 3,
Expand Down
33 changes: 33 additions & 0 deletions Tests/CodexBarTests/ChartAxisLabelLayoutTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation
import Testing
@testable import CodexBar

struct ChartAxisLabelLayoutTests {
@Test
func `first date label center matches first bar center`() throws {
let barCenter = try #require(ChartAxisLabelLayout.barCenterX(
slotIndex: 0,
slotCount: 16,
chartWidth: 480))
let labelCenter = ChartAxisLabelLayout.labelCenterX(
tickX: barCenter,
labelWidth: 44,
anchor: ChartAxisLabelLayout.barCenteredAnchor)

#expect(abs(labelCenter - barCenter) < 0.0001)
}

@Test
func `last date label center matches last bar center`() throws {
let barCenter = try #require(ChartAxisLabelLayout.barCenterX(
slotIndex: 15,
slotCount: 16,
chartWidth: 480))
let labelCenter = ChartAxisLabelLayout.labelCenterX(
tickX: barCenter,
labelWidth: 64,
anchor: ChartAxisLabelLayout.barCenteredAnchor)

#expect(abs(labelCenter - barCenter) < 0.0001)
}
}
12 changes: 0 additions & 12 deletions Tests/CodexBarTests/CostHistoryChartMenuViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,6 @@ struct CostHistoryChartMenuViewTests {
#expect(cal.component(.day, from: dates[0]) == 21)
#expect(cal.component(.month, from: dates[1]) == 6)
#expect(cal.component(.day, from: dates[1]) == 17)
#expect(
CostHistoryChartMenuView._axisLabelPlacementForTesting(
provider: .codex,
daily: daily) == .edges)
}

@Test
Expand All @@ -370,10 +366,6 @@ struct CostHistoryChartMenuViewTests {
]
let dates = CostHistoryChartMenuView._axisDatesForTesting(provider: .codex, daily: daily)
#expect(dates.count == 1)
#expect(
CostHistoryChartMenuView._axisLabelPlacementForTesting(
provider: .codex,
daily: daily) == .centered)
}

@Test
Expand All @@ -391,10 +383,6 @@ struct CostHistoryChartMenuViewTests {
]
let dates = CostHistoryChartMenuView._axisDatesForTesting(provider: .codex, daily: daily)
#expect(dates.isEmpty)
#expect(
CostHistoryChartMenuView._axisLabelPlacementForTesting(
provider: .codex,
daily: daily) == .hidden)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1713,7 +1713,7 @@ struct ProviderArchitectureGatekeeperTests {
reason: "This exact shared construct dispatches a provider-owned capability at the generic integration boundary."),
AllowedProviderConstruct(
path: "Sources/CodexBar/CostHistoryChartMenuView.swift",
line: 1135,
line: 1106,
anchor: "let projects = provider == .codex ? snapshot.projects : []",
expectedProviderIDs: ["codex"],
expectedReferenceCount: 2,
Expand Down