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
8 changes: 6 additions & 2 deletions Sources/CodeCaps/MonitorModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2668,7 +2668,11 @@ final class MonitorModel: ObservableObject {
let previous = exhaustionObservationState[key]
guard previous == nil || observedAt > previous!.observedAt else { continue }
changed = true
if remaining > 0 {
// The shared cap rule decides, so the Mac's exhausted alert fires
// at the same threshold as the "At cap" line and as the phone's:
// a reading at or below the threshold is a spent allowance, and a
// reading above it is headroom (Kody review 2026-10-21).
if !QuotaCapRule.isAtCap(remainingPercent: remaining) {
exhaustionObservationState[key] = ExhaustionObservationState(observedAt: observedAt, alert: nil)
activeExhaustionAlerts.removeValue(forKey: key)
continue
Expand All @@ -2678,7 +2682,7 @@ final class MonitorModel: ObservableObject {
continue
}
let title = "Quota Exhausted: " + window.provider
let body = window.label + " has no quota remaining."
let body = window.label + " has reached its limit."
let payload = LocalQuotaSnapshot.AlertPayload(
id: "quotaExhaustion|" + key + "|" + String(observedAt.timeIntervalSince1970),
kind: "quotaExhaustion", providerKey: window.canonicalProviderKey,
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodeCaps/SettingsViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1398,7 +1398,7 @@ struct SettingsNotificationsPage: View {
Eyebrow("ALERT DELIVERY")
} footer: {
Text("Anomalies compare usage with your average; runaway usage compares it with your peak." + sentenceGap
+ "Quota Exhausted fires at zero remaining." + sentenceGap
+ "Quota Exhausted alerts when \(QuotaCapRule.spentThreshold.formatted(.number.precision(.fractionLength(0...1))))% or less remains." + sentenceGap
+ "Provider bells choose which scheduled and vendor resets alert on this Mac.")
}
if let row = model.displaySections.first(where: {
Expand Down
36 changes: 34 additions & 2 deletions Sources/QuotaCore/QuotaModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,40 @@ public struct QuotaWindow: Codable, Equatable, Sendable {
} else if status == .unknown, bounded != nil {
copy.status = QuotaWindowStatus.derived(remainingPercent: bounded)
}
copy.skip = skip || copy.isExhausted
copy.skipReason = skipReason ?? (copy.isExhausted ? "quota exhausted" : nil)
// `skip` means "a reading that must not be believed", which is not the
// same question as "is the allowance spent". The shared cap rule
// answers the second one for any reading at or under the threshold,
// and a spent window is still a window with a reading in it: when
// `skip` followed the rule, a window at 0.3% was dropped from the
// picture in picture along with its cap line, while the menu bar still
// announced the cap, so the same window was live in one place and
// gone in the other (Kody review 2026-10-22).
//
// The provider's own exhausted word is believed only when the reading
// disagrees with it, because only the provider knows why (a rejected
// session, a suspended plan) and the reading cannot say. It is also
// the answer when there is no reading at all. An exact zero is the
// one reading that is never shown as a meter.
//
// The reading is what makes this rule safe to apply twice, which the
// display path does: the picture in picture rebuilds a snapshot from
// an already derived window, and a rule that read the derived
// `isExhausted` back as the provider's word would hide the window
// again on the second pass.
let providerWordCounts: Bool
if let bounded {
providerWordCounts = isExhausted && bounded > QuotaCapRule.spentThreshold
} else {
providerWordCounts = isExhausted
}
copy.skip = skip || bounded == 0 || providerWordCounts
// The reason explains a skip, so it is attached to a skip and never
// to a window that stays visible, and it never claims an exhaustion
// the window does not have: a provider that hides a healthy window
// without saying why gets no reason at all, because "quota exhausted"
// on a window with room left is a false claim, and a consumer that
// filters on the reason would act on it (Kody review 2026-10-26).
copy.skipReason = skipReason ?? (copy.skip && copy.isExhausted ? "quota exhausted" : nil)
let monthlyHint = copy.canonicalProviderKey == "cursor"
&& copy.label.lowercased().contains("plan")
if let rewritten = QuotaWindowVocabulary.sanitizeLabelIfNeeded(
Expand Down
9 changes: 8 additions & 1 deletion Sources/QuotaCore/QuotaPublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,14 @@ public actor QuotaPublisher {

var meta: [String: Any] = [
"bucketId": bucketId,
"isExhausted": window.isExhausted || QuotaCapRule.isAtCap(remainingPercent: clampedRemaining),
// The shared cap rule decides, as it does on every surface: a
// reading at or below the threshold is a spent allowance, and
// the provider's own exhausted word is the fallback for a
// window that reports no figure at all. A local zero test
// here left the wire disagreeing with the at-cap line the
// owner reads (Kody review 2026-10-21).
"isExhausted": QuotaCapRule.isExhausted(remainingPercent: clampedRemaining,
providerSaysExhausted: window.isExhausted),
"remainingUnknown": clampedRemaining == nil,
"scale": "percent_0_100",
"source": Self.producerId,
Expand Down
86 changes: 86 additions & 0 deletions Tests/CodeCapsTests/ExhaustionCapRuleTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import XCTest
@testable import CodeCaps
import QuotaCore

/// The exhausted alert and the at-cap line have to agree on one threshold.
///
/// The shared rule spends a window at or below half a point remaining, and the
/// interface rounds that to zero percent, so a window reading 0.4% draws
/// "At cap" on the card. The alert path used its own zero test, so the owner
/// saw "At cap" and never received the "Quota Exhausted" notification
/// (Kody review 2026-10-21).
///
/// The Mac's alert path lives inside a large `MonitorModel`, so the rule it
/// reads is pinned by reading the source, the way the phone's own companion
/// model is pinned in `CompanionModelTests`.
final class ExhaustionCapRuleTests: XCTestCase {
private static var monitorModelURL: URL? {
URL(fileURLWithPath: #filePath)
.deletingLastPathComponent() // Tests/CodeCapsTests
.deletingLastPathComponent() // Tests
.deletingLastPathComponent() // repo root
.appendingPathComponent("Sources/CodeCaps/MonitorModel.swift")
}

private static var settingsViewsURL: URL? {
URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
.appendingPathComponent("Sources/CodeCaps/SettingsViews.swift")
}

private func read(_ url: URL?) throws -> String {
guard let url, FileManager.default.fileExists(atPath: url.path) else {
throw XCTSkip("The source file is not present at \(url?.path ?? "")")
}
return try String(contentsOf: url, encoding: .utf8)
}

/// The Mac's exhausted alert fires on the shared rule.
func testTheMacAlertFiresOnTheSharedRuleRatherThanOnZero() throws {
let source = try read(Self.monitorModelURL)
XCTAssertTrue(
source.contains("if !QuotaCapRule.isAtCap(remainingPercent: remaining)"),
"the Mac's exhausted alert must read the shared cap rule, so it fires at the same threshold as the at-cap line"
)
XCTAssertFalse(
source.contains("if remaining > 0 {"),
"a local zero test here left the Mac quiet while the card said 'At cap'"
)
}

/// The settings copy names the threshold the app actually uses, so it
/// cannot drift back to "zero remaining" and describe a rule the app no
/// longer follows.
func testTheSettingsCopyNamesTheSharedThreshold() throws {
let source = try read(Self.settingsViewsURL)
XCTAssertFalse(
source.contains("Quota Exhausted fires at zero remaining"),
"the copy has to describe the threshold the app uses, not the one it used to use"
)
XCTAssertTrue(
source.contains("QuotaCapRule.spentThreshold"),
"the copy must quote the shared threshold itself, so a change to the rule cannot leave the copy behind"
)
XCTAssertTrue(
source.contains(".formatted(.number.precision(.fractionLength(0...1)))"),
"the threshold is a Double, so a plain interpolation would print 1.0% once the rule reaches a whole number"
)
}

/// The rule itself spends a window at or below the threshold and calls a
/// larger reading headroom, whichever surface asks.
func testTheSharedRuleIsTheOneBothSurfacesAsk() {
XCTAssertTrue(QuotaCapRule.isAtCap(remainingPercent: 0.4))
XCTAssertTrue(QuotaCapRule.isAtCap(remainingPercent: 0))
XCTAssertFalse(QuotaCapRule.isAtCap(remainingPercent: 0.6))
XCTAssertFalse(QuotaCapRule.isAtCap(remainingPercent: nil))
// The reading decides, and the provider's own word is the fallback
// for a window that reports no figure at all.
XCTAssertTrue(QuotaCapRule.isExhausted(remainingPercent: 0.4, providerSaysExhausted: false))
XCTAssertFalse(QuotaCapRule.isExhausted(remainingPercent: 50, providerSaysExhausted: true))
XCTAssertTrue(QuotaCapRule.isExhausted(remainingPercent: nil, providerSaysExhausted: true))
XCTAssertFalse(QuotaCapRule.isExhausted(remainingPercent: nil, providerSaysExhausted: false))
}
}
30 changes: 30 additions & 0 deletions Tests/CodeCapsTests/PipInteractionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,36 @@ final class PipInteractionTests: XCTestCase {
}
}

/// A window that is at its cap is still a window the panel shows.
///
/// The shared cap rule calls any reading at or under 0.5% spent, because
/// the interface rounds a reading to the nearest whole percent. When the
/// export also hid such a window, the picture in picture dropped it and
/// its cap line while the menu bar still announced the cap, so the same
/// window was live in one place and gone in the other.
func testANearZeroReadingStaysVisibleInThePictureInPicture() throws {
let (model, defaults, suite) = GlanceFixtures.makeModel(view: .fromMac, alarmsAll: false, fleet: false)
defer { defaults.removePersistentDomain(forName: suite) }
let window = GlanceFixtures.window("anthropic:5h", provider: "anthropic",
label: "5h", token: "5h", remaining: 0.3, resetIn: 18_000)
model.injectForTests(sections: QuotaResponse(generatedAt: "", windows: [window])
.platformSections(now: GlanceFixtures.now), now: GlanceFixtures.now)
let row = try XCTUnwrap(model.displaySections.first { $0.id == "anthropic" })

let current = PipWidgetController.currentWindows(for: row, now: model.now)
XCTAssertEqual(current.count, 1, "a window at 0.3% is at its cap, not a reading to hide")
XCTAssertEqual(current.first?.remainingPercent, 0.3)
XCTAssertNil(PipWidgetController.readingStatus(for: row, now: model.now))

// The cap line is built from the same filtered list, so it has to be
// there too: a spent window that is visible still says it is spent.
let section = QuotaPlatformSection(providerKey: row.providerKey,
providerLabel: row.section.providerLabel,
expected: row.section.expected,
windows: current)
XCTAssertNotNil(section.atCapWindow(), "the cap line still reads the shared cap rule")
}

func testBodyDragIncludesLogosBarsAndTextButNotResizeEdgesOrControls() {
let size = NSSize(width: 400, height: 180)
for point in [NSPoint(x: 18, y: 70), NSPoint(x: 160, y: 110), NSPoint(x: 310, y: 40)] {
Expand Down
97 changes: 97 additions & 0 deletions Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,103 @@ final class LocalQuotaSnapshotTests: XCTestCase {
XCTAssertEqual(QuotaWindowStatus.derived(remainingPercent: 20), .available)
}

/// A window that is at its cap is not a window that must be hidden.
///
/// The shared cap rule calls any reading at or under 0.5% spent, because
/// the interface rounds a reading to the nearest whole percent. When
/// `skip` followed that rule, a window at 0.3% was dropped from the
/// picture in picture along with its cap line, while the menu bar still
/// announced the cap, so the same window was live in one place and gone
/// in the other. `skip` keeps its own meaning: a reading that must not
/// be believed.
func testSkipDoesNotFollowTheSharedCapThreshold() {
// At the cap by the shared rule, but a real reading that is not zero.
let nearZero = QuotaWindow(id: "near-zero", provider: "openai", label: "5h",
remainingPercent: 0.3, occurredAt: "2026-10-03T23:00:00Z")
.normalizedForExport()
XCTAssertTrue(nearZero.isExhausted, "0.3% is at the cap, so the window is spent")
XCTAssertEqual(nearZero.status, .exhausted)
XCTAssertFalse(nearZero.skip, "a window at 0.3% is at its cap, not a reading to hide")
XCTAssertNil(nearZero.skipReason)

// Exactly zero is still hidden, and still says why.
let zero = QuotaWindow(id: "zero", provider: "openai", label: "5h",
remainingPercent: 0, occurredAt: "2026-10-03T23:00:00Z")
.normalizedForExport()
XCTAssertTrue(zero.isExhausted)
XCTAssertTrue(zero.skip, "an exact zero is the one reading that is not believed")
XCTAssertEqual(zero.skipReason, "quota exhausted")

// The provider's own flag still hides the window, and its own reason
// is never talked over.
let reported = QuotaWindow(id: "reported", provider: "openai", label: "5h",
remainingPercent: 15, isExhausted: true, status: .exhausted,
skip: true, skipReason: "session rejected",
occurredAt: "2026-10-03T23:00:00Z")
.normalizedForExport()
XCTAssertTrue(reported.skip)
XCTAssertEqual(reported.skipReason, "session rejected")

// A provider that calls a window exhausted while the reading says
// there is room left is believed: only the provider knows why, and
// the reading cannot say.
let contradicted = QuotaWindow(id: "contradicted", provider: "openai", label: "5h",
remainingPercent: 15, isExhausted: true,
occurredAt: "2026-10-03T23:00:00Z")
.normalizedForExport()
XCTAssertTrue(contradicted.isExhausted)
XCTAssertTrue(contradicted.skip, "a provider's word against a reading is believed")
XCTAssertEqual(contradicted.skipReason, "quota exhausted", "a hidden window says why it is hidden")

// The same word beside a reading that already agrees with it settles
// nothing: the window is spent either way, and it stays visible.
let agreed = QuotaWindow(id: "agreed", provider: "openai", label: "5h",
remainingPercent: 0.3, isExhausted: true,
occurredAt: "2026-10-03T23:00:00Z")
.normalizedForExport()
XCTAssertTrue(agreed.isExhausted)
XCTAssertFalse(agreed.skip)

// A provider that hides a window without saying why gets no reason
// invented for it: the window has room left, so "quota exhausted"
// would be a false claim for a consumer that filters on the reason.
let silent = QuotaWindow(id: "silent", provider: "openai", label: "5h",
remainingPercent: 40, skip: true,
occurredAt: "2026-10-03T23:00:00Z")
.normalizedForExport()
XCTAssertTrue(silent.skip)
XCTAssertFalse(silent.isExhausted, "40% is not a spent window")
XCTAssertNil(silent.skipReason, "a reason must never claim an exhaustion the window does not have")

// An unreadable reading is not a spent one and raises nothing.
let unreadable = QuotaWindow(id: "unreadable", provider: "cursor", label: "Included plan",
remainingUnknown: true, occurredAt: "2026-10-03T23:00:00Z")
.normalizedForExport()
XCTAssertFalse(unreadable.isExhausted)
XCTAssertFalse(unreadable.skip)
XCTAssertNil(unreadable.skipReason)

// The derivation is applied twice on the display path, the picture in
// picture rebuilds a snapshot from an already derived window, so the
// second pass has to reach the same answer as the first.
for source in [
QuotaWindow(id: "a", provider: "openai", label: "5h", remainingPercent: 0.3,
occurredAt: "2026-10-03T23:00:00Z"),
QuotaWindow(id: "b", provider: "openai", label: "5h", remainingPercent: 0,
occurredAt: "2026-10-03T23:00:00Z"),
QuotaWindow(id: "c", provider: "openai", label: "5h", remainingPercent: 15,
isExhausted: true, occurredAt: "2026-10-03T23:00:00Z"),
QuotaWindow(id: "d", provider: "openai", label: "5h", remainingPercent: 15,
isExhausted: true, skip: true, skipReason: "session rejected",
occurredAt: "2026-10-03T23:00:00Z"),
QuotaWindow(id: "e", provider: "cursor", label: "Included plan", remainingUnknown: true,
occurredAt: "2026-10-03T23:00:00Z"),
] {
let once = source.normalizedForExport()
XCTAssertEqual(once.normalizedForExport(), once, "\(source.id) is not stable under a second pass")
}
}

func testExhaustedFlagHasTheSameStatusInExportAndDisplayAtNonzeroPercent() throws {
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
defer { try? FileManager.default.removeItem(at: directory) }
Expand Down
Loading
Loading