menubar: one set of quota colour bands, 60/70/95, for the flame, popover and dock

This commit is contained in:
iamtoruk 2026-09-13 12:50:25 -07:00
parent aacd9cf675
commit 89213cb13b
4 changed files with 23 additions and 20 deletions

View file

@ -14,6 +14,7 @@
- **The Capacity Dock gauge can report the short usage window instead of the weekly one, without expanding the dock.** The resting rail shows one number per provider, and that number was always the weekly (else monthly) billing window. Clicking the provider already resting in the rail now switches its gauge to the provider's short rolling window — Claude's 5-hour limit, Codex's 5-hour or daily window, any `Hourly`, `Daily` or session row an adapter reports — and clicking again switches back. The choice is stored per provider under `CodeBurnCapacityDockGlanceWindows`, so Claude can sit on its 5-hour window while Codex stays weekly, and it survives relaunch. A per-model row such as `Weekly · Opus` is never read as a short window, a provider that reports only one window keeps the plain click-to-pin behaviour, and a stored horizon the provider stops reporting falls back to the window it does report rather than blanking the gauge to `--`. The rail's geometry is untouched, VoiceOver and keyboard users get the switch as a named action on the provider cell with the window named in the cell's value, and Escape or a click outside still unpins the dock. (#1243)
### Changed
- **Quota colours use one set of bands everywhere: green under 60%, yellow to 70%, orange to 95%, red from 95%.** The flame, the popover rows and the Capacity Dock rings each had their own thresholds; the dock now reads the same severity the flame does.
- **The Capacity Dock card and the popover quota rows no longer print a pace verdict under each window.** The "On pace" / "Lasts until reset" caption from #1314 added a third line to every window cell and made the card read as clutter; the cells are back to percent, label and countdown, up to three windows share one row again with a fourth wrapping to a second, and the pace math still feeds the tooltip and the agent-tab card.
### Fixed

View file

@ -79,17 +79,17 @@ struct QuotaSummary: Equatable {
/// to "you're over" (red) — matches what the user expects from a warning
/// indicator in the menu bar.
static func severity(for percent: Double) -> Severity {
if percent >= 0.9 { return .danger }
if percent >= 0.75 { return .critical }
if percent >= 0.5 { return .warning }
if percent >= 0.95 { return .danger }
if percent >= 0.7 { return .critical }
if percent >= 0.6 { return .warning }
return .normal
}
enum Severity {
case normal // <50% green
case warning // 50-75% yellow
case critical // 75-90% orange
case danger // >=90% red
case normal // <60% green
case warning // 60-70% yellow
case critical // 70-95% orange
case danger // >=95% red
}
/// The glance value (percent + color) for Capacity Dock. Every provider is

View file

@ -225,14 +225,16 @@ enum CapacityDockGlance {
static let todayHeight: CGFloat = sectionPadTop + captionLine + pillGap
+ todayContentHeight + sectionPadBottom
/// Four bands, matching the rail's own sense of escalation: comfortable,
/// watch it, nearly out, over. Rings start green because a ring with no
/// colour reads as broken.
/// The menu-bar flame's bands, so the rail and the flame never disagree
/// about a window. Rings start green because a ring with no colour reads
/// as broken.
static func severityColor(_ fraction: Double) -> Color {
if fraction >= 0.9 { return .red }
if fraction >= 0.8 { return .orange }
if fraction >= 0.7 { return .yellow }
return .green
switch QuotaSummary.severity(for: fraction) {
case .normal: .green
case .warning: .yellow
case .critical: .orange
case .danger: .red
}
}
/// Share of the pill the tint covers. Anything outside 0...1 is a bad

View file

@ -84,11 +84,11 @@ struct QuotaSummaryHeadlineTests {
@Test("severity steps at the 0.50 / 0.75 / 0.90 band boundaries")
func severityBoundaries() {
#expect(QuotaSummary.severity(for: 0.49) == .normal)
#expect(QuotaSummary.severity(for: 0.50) == .warning)
#expect(QuotaSummary.severity(for: 0.74) == .warning)
#expect(QuotaSummary.severity(for: 0.75) == .critical)
#expect(QuotaSummary.severity(for: 0.89) == .critical)
#expect(QuotaSummary.severity(for: 0.90) == .danger)
#expect(QuotaSummary.severity(for: 0.59) == .normal)
#expect(QuotaSummary.severity(for: 0.60) == .warning)
#expect(QuotaSummary.severity(for: 0.69) == .warning)
#expect(QuotaSummary.severity(for: 0.70) == .critical)
#expect(QuotaSummary.severity(for: 0.94) == .critical)
#expect(QuotaSummary.severity(for: 0.95) == .danger)
}
}