Repository navigation
feat(alerts): courtesy-reset detection, iOS runaway alerts, and no PiP titlebar strip - #195
Conversation
…P titlebar strip Owner batch, 2026-10-08. Board 7179df4aab7a444883eb668b747b62e4. Courtesy and early resets now alert ------------------------------------ "Can't the app tell if usage suddenly jumps to almost 100% available between 2 checks that there was a reset, whether done by the system or by a reset the user was allowed to optionally use? Please alert for that since if you don't know it was reset and they do a courtesy reset then you can start using again." Two gaps, and the consequence is the same either way: the owner believes a cap still applies that does not. A reset that landed *before* the window's end was only recognised when the window end also held still, so a courtesy reset that legitimately rolled the window forward was invisible. The early-reset branch now labels a restore as mid-window when the quota went from mostly spent to nearly untouched, which is what lets it past the near-cap rule that a genuine restore should not need. And a jump to nearly-untouched before the window is due to end is now its own rule, keyed on the shape between two consecutive readings. `!hasPassed` is load-bearing there: a window that simply reached the end of its period also goes from spent to full, and the owner ruled in September that a smaller window stays quiet unless it came near its cap. Without the guard this alerted on every ordinary rollover, which is what the first attempt did -- five existing tests caught it. Runaway alerts reach iOS ------------------------ "iOS should have info about heavy anomalies in usage or runaway usage like the mac." The shared handoff had no alert data at all, so this is new plumbing rather than a toggle: Payload gains an optional `alerts` array, the Mac fills it from its live anomaly set, and the companion raises anything it has not announced. The decision is made on the Mac and shipped as data, deliberately. The rules compare against this Mac's burn-rate history -- a per-machine file of measured hourly intervals -- which the phone has no access to and could not reproduce. Recomputing there would mean the two surfaces disagreeing about whether something was anomalous, which is worse than one surface staying quiet. Optional and additive like `issues`, so a file from an older build still decodes, and an unknown `kind` degrades rather than failing the whole read. Alert ids are stable and cached on the phone, so a payload rewritten every refresh raises each alert once rather than once per poll. PiP: the blank silver strip is gone ----------------------------------- ".titled" and ".hudWindow" both make AppKit paint a titlebar strip that lives outside the SwiftUI content, so it appeared as a blank light band above the rounded panel. `titleVisibility = .hidden` and `titlebarAppearsTransparent` could not remove it because the strip belongs to the window frame, not the content view. The panel is now borderless, so the rounded surface is the whole window; `isMovableByWindowBackground` already made the body draggable, so the titlebar was never load-bearing. Tests: five courtesy-reset cases (mid-period, window-end rolled forward, moderate usage level, ordinary use never looks like one, alerts once), four payload cases (older file still decodes, round trip, id survives exactly, an unknown kind does not break decoding), and PipMetricsTests now pins that the style mask carries no `.titled`/`.hudWindow` so the strip cannot come back. swift test: 375 tests, 0 failures. Co-Authored-By: MiniMax Code <noreply@minimax.io>
Code Review Completed! 🔥The code review was successfully completed based on your current configurations. Kody Guide: Usage and ConfigurationInteracting with Kody
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
|
Single-space sentence gap in the first PR description item joins two sentences at one space ( Kody rule violation: Use two spaces between sentences in every human-facing string and agent-written paragraph |
🤔 Insufficient Task ContextI found a task linked to this PR, but it only contains minimal information (title only, no description or acceptance criteria). To perform a meaningful business rules validation, I need more details. 🔍 What I need to validate:
💡 How to improve the task context:
|
| } else if self.skipsSnapshotIOForTesting { | ||
| self.handoffError = nil | ||
| } else if self.localEnabled && local != nil { | ||
| try LocalQuotaSnapshot.write(windows: self.localWindows, issues: self.issues, customMarks: exportedCustomMarks(), now: self.now) | ||
| try LocalQuotaSnapshot.write(windows: self.localWindows, issues: self.issues, | ||
| customMarks: exportedCustomMarks(), | ||
| alerts: exportedAlerts(now: self.now), now: self.now) | ||
| } | ||
| else { try LocalQuotaSnapshot.remove() } | ||
| self.handoffError = nil |
There was a problem hiding this comment.
refreshRunawayUsageState(recordSamples:) runs after LocalQuotaSnapshot.write in refresh() (~line 1791) and rebuildLocalState() (~line 1968), so the snapshot ships the previous refresh's activeRunawayAnomalies instead of the current set. Newly-emerged runaway alerts arrive one poll cycle late on the iOS companion and resolved alerts persist an extra cycle, violating the "raise once" dedupe contract; move refreshRunawayUsageState immediately before the LocalQuotaSnapshot.write call in both locations.
Prompt for LLM
File Sources/CodeCaps/MonitorModel.swift:
Line 1788 to 1796:
refreshRunawayUsageState(recordSamples:) runs after LocalQuotaSnapshot.write in refresh() (~line 1791) and rebuildLocalState() (~line 1968), so the snapshot ships the previous refresh's activeRunawayAnomalies instead of the current set. Newly-emerged runaway alerts arrive one poll cycle late on the iOS companion and resolved alerts persist an extra cycle, violating the "raise once" dedupe contract; move refreshRunawayUsageState immediately before the LocalQuotaSnapshot.write call in both locations.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| /// | ||
| /// `id` is stable for a given anomaly so the companion can raise it once | ||
| /// rather than once per poll — this payload is rewritten on every refresh. | ||
| func exportedAlerts(now: Date = Date()) -> [LocalQuotaSnapshot.AlertPayload] { |
There was a problem hiding this comment.
exportedAlerts only emits "runaway" AlertPayload entries and ignores alarmManager.recentEvents, silently dropping the "reset" and "vendorReset" kinds that LocalQuotaSnapshot.AlertPayload.kind and WireAlert document as supported. Extend exportedAlerts to walk alarmManager.recentEvents and emit an AlertPayload with kind isMidWindow ? "vendorReset" : "reset" for each unacknowledged ResetAlarmEvent using a stable id like reset|(providerId)|(windowLabel)|(endedPeriodResetAt).
func exportedAlerts(now: Date = Date()) -> [LocalQuotaSnapshot.AlertPayload] {
var out: [LocalQuotaSnapshot.AlertPayload] = []
let formatter = ISO8601DateFormatter()
// Runaway usage currently burning far faster than the owner's own
// pattern. Only the live set, so a resolved anomaly stops being
// re-offered on the next poll.
for anomaly in activeRunawayAnomalies {
let label = AnomalyDetector.windowLabel(windowId: anomaly.windowId)
let title = displaySections.first { $0.id == anomaly.providerKey }?.title
?? anomaly.providerKey
out.append(LocalQuotaSnapshot.AlertPayload(
id: "runaway|\(anomaly.providerKey)|\(anomaly.windowId)|\(anomaly.kind.rawValue)",
kind: "runaway",
providerKey: anomaly.providerKey,
providerTitle: title,
windowLabel: label,
summary: anomaly.summary,
occurredAt: formatter.string(from: anomaly.observedAt ?? now)))
}
// Courtesy / vendor resets the tracker decided to raise (this PR).
// Without these the iOS companion would never learn a reset happened,
// which is the whole reason the Mac-side rule was added.
for event in alarmManager.recentEvents where !event.isAcknowledged {
let label = event.windowLabel
let title = displaySections.first { $0.id == event.providerId }?.title
?? event.providerId
out.append(LocalQuotaSnapshot.AlertPayload(
id: "\(event.isVendorReset ? "vendorReset" : "reset")|\(event.providerId)|\(event.windowLabel)|\(event.endedPeriodResetAt.timeIntervalSince1970)",
kind: event.isVendorReset ? "vendorReset" : "reset",
providerKey: event.providerId,
providerTitle: title,
windowLabel: label,
summary: event.summary ?? "Quota was reset.",
occurredAt: formatter.string(from: event.endedPeriodResetAt)))
}
return out
}Prompt for LLM
File Sources/CodeCaps/MonitorModel.swift:
Line 2203:
exportedAlerts only emits "runaway" AlertPayload entries and ignores alarmManager.recentEvents, silently dropping the "reset" and "vendorReset" kinds that LocalQuotaSnapshot.AlertPayload.kind and WireAlert document as supported. Extend exportedAlerts to walk alarmManager.recentEvents and emit an AlertPayload with kind isMidWindow ? "vendorReset" : "reset" for each unacknowledged ResetAlarmEvent using a stable id like reset|\(providerId)|\(windowLabel)|\(endedPeriodResetAt).
Suggested Code:
func exportedAlerts(now: Date = Date()) -> [LocalQuotaSnapshot.AlertPayload] {
var out: [LocalQuotaSnapshot.AlertPayload] = []
let formatter = ISO8601DateFormatter()
// Runaway usage currently burning far faster than the owner's own
// pattern. Only the live set, so a resolved anomaly stops being
// re-offered on the next poll.
for anomaly in activeRunawayAnomalies {
let label = AnomalyDetector.windowLabel(windowId: anomaly.windowId)
let title = displaySections.first { $0.id == anomaly.providerKey }?.title
?? anomaly.providerKey
out.append(LocalQuotaSnapshot.AlertPayload(
id: "runaway|\(anomaly.providerKey)|\(anomaly.windowId)|\(anomaly.kind.rawValue)",
kind: "runaway",
providerKey: anomaly.providerKey,
providerTitle: title,
windowLabel: label,
summary: anomaly.summary,
occurredAt: formatter.string(from: anomaly.observedAt ?? now)))
}
// Courtesy / vendor resets the tracker decided to raise (this PR).
// Without these the iOS companion would never learn a reset happened,
// which is the whole reason the Mac-side rule was added.
for event in alarmManager.recentEvents where !event.isAcknowledged {
let label = event.windowLabel
let title = displaySections.first { $0.id == event.providerId }?.title
?? event.providerId
out.append(LocalQuotaSnapshot.AlertPayload(
id: "\(event.isVendorReset ? "vendorReset" : "reset")|\(event.providerId)|\(event.windowLabel)|\(event.endedPeriodResetAt.timeIntervalSince1970)",
kind: event.isVendorReset ? "vendorReset" : "reset",
providerKey: event.providerId,
providerTitle: title,
windowLabel: label,
summary: event.summary ?? "Quota was reset.",
occurredAt: formatter.string(from: event.endedPeriodResetAt)))
}
return out
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
|
|
||
| for alert in fresh { | ||
| let content = UNMutableNotificationContent() | ||
| content.title = "Runaway Usage: \(alert.providerTitle)" | ||
| content.body = alert.summary | ||
| content.sound = AlarmSoundPlayer.notificationSound(for: alarmSound) ?? .default | ||
| // An id per alert rather than a shared one: identical bodies would | ||
| // otherwise collapse, and the owner does sometimes see the same | ||
| // wording twice for two different windows. | ||
| let request = UNNotificationRequest(identifier: "runaway-\(alert.id)", | ||
| content: content, | ||
| trigger: nil) |
There was a problem hiding this comment.
raisePendingAlerts hardcodes the notification title as "Runaway Usage: (alert.providerTitle)" and ignores alert.kind, so the upcoming "reset" and "vendorReset" AlertPayloads from the Mac will be mislabelled as runaway usage on iOS. Branch on alert.kind to emit "Runaway Usage", "Quota Reset", and "Vendor Reset" titles for the documented kinds, with the existing wording as fallback.
Prompt for LLM
File ios/CodeCapsCompanion/App/Models/CompanionQuotaModel.swift:
Line 862 to 873:
raisePendingAlerts hardcodes the notification title as "Runaway Usage: \(alert.providerTitle)" and ignores alert.kind, so the upcoming "reset" and "vendorReset" AlertPayloads from the Mac will be mislabelled as runaway usage on iOS. Branch on alert.kind to emit "Runaway Usage", "Quota Reset", and "Vendor Reset" titles for the documented kinds, with the existing wording as fallback.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| private var announcedAlertIds: Set<String> { | ||
| get { | ||
| let stored = UserDefaults.standard.stringArray(forKey: "announcedAlertIds") ?? [] | ||
| return Set(stored) | ||
| } | ||
| set { | ||
| UserDefaults.standard.set(Array(newValue).suffix(200), forKey: "announcedAlertIds") | ||
| } | ||
| } |
There was a problem hiding this comment.
The announcedAlertIds setter persists Array(newValue).suffix(200) over a Set with undefined iteration order, so the 200-element suffix is an arbitrary subset rather than the most-recently-inserted ids. With 201+ distinct ids, truncation can drop the just-inserted id and the next poll re-announces the same alert, violating the "once per alert" guarantee; store as an ordered [String] with explicit contains check and suffix(200) only at write time, or drop truncation and keep Set semantics.
private var announcedAlertIds: [String] {
get { UserDefaults.standard.stringArray(forKey: "announcedAlertIds") ?? [] }
set {
let capped = Array(newValue.suffix(200))
UserDefaults.standard.set(capped, forKey: "announcedAlertIds")
}
}Prompt for LLM
File ios/CodeCapsCompanion/App/Models/CompanionQuotaModel.swift:
Line 835 to 843:
The announcedAlertIds setter persists Array(newValue).suffix(200) over a Set<String> with undefined iteration order, so the 200-element suffix is an arbitrary subset rather than the most-recently-inserted ids. With 201+ distinct ids, truncation can drop the just-inserted id and the next poll re-announces the same alert, violating the "once per alert" guarantee; store as an ordered [String] with explicit contains check and suffix(200) only at write time, or drop truncation and keep Set<String> semantics.
Suggested Code:
private var announcedAlertIds: [String] {
get { UserDefaults.standard.stringArray(forKey: "announcedAlertIds") ?? [] }
set {
let capped = Array(newValue.suffix(200))
UserDefaults.standard.set(capped, forKey: "announcedAlertIds")
}
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
Board 7179df4aab7a444883eb668b747b62e4. Owner batch, 2026-10-08.
Courtesy / early resets now alert
Two distinct gaps, same consequence — the owner believes a cap still applies that does not.
A reset landing before the window's end was only recognised when the window end also held still, so a courtesy reset that legitimately rolled the window forward was invisible. The early-reset branch now labels a restore as mid-window when quota went from mostly spent to nearly untouched — which is what lets it past the near-cap rule a genuine restore should not need.
A jump to nearly-untouched before the window is due to end is now its own rule, keyed on the shape between two consecutive readings.
!hasPassedis load-bearing on that second rule: a window that simply reached the end of its period also goes from spent to full, and the standing rule from September is that a smaller window stays quiet unless it came near its cap. My first attempt omitted the guard and alerted on every ordinary rollover — five existing tests caught it before it went anywhere.Runaway alerts now reach iOS
This is new plumbing, not a toggle: the shared handoff carried no alert data at all.
Payloadgains an optionalalertsarray, the Mac fills it from its live anomaly set, and the companion raises anything it has not announced yet.The decision is made on the Mac and shipped as data, deliberately. The rules compare against this Mac's burn-rate history — a per-machine file of 18k measured samples the phone cannot reproduce. Recomputing on iOS would mean the two surfaces disagreeing about whether something was anomalous, which is worse than one surface staying quiet.
Optional and additive like
issues, so an older Mac's file still decodes; an unknownkinddegrades rather than failing the whole read. Alert ids are stable and cached on the phone, so a payload rewritten every refresh raises each alert once, not once per poll.PiP: the blank silver strip is gone
.titledand.hudWindowboth make AppKit paint a titlebar strip that lives outside the SwiftUI content, so it appeared as a blank light band above the rounded panel. NeithertitleVisibility = .hiddennortitlebarAppearsTransparentcould remove it, because the strip belongs to the window frame rather than the content view. The panel is now borderless, so the rounded surface is the window.isMovableByWindowBackgroundalready made the body draggable, so the titlebar was never load-bearing.Tests
kinddoes not break decoding.PipMetricsTestsnow pins that the style mask carries no.titled/.hudWindow, so the strip cannot come back.swift buildclean;swift test375 tests, 0 failures.Not in this PR
The accent complaint is about the Glance popover, not the menu bar status item — I got that wrong last round and fixed the wrong surface. That is the one item here I have not landed, because
Theme.barRemainingwas deliberately decoupled from the accent in #181 to keep the bar distinguishable from the red used segment, so making the accent reach it is a design decision rather than a plumbing fix. I would like to confirm with you what should carry the accent in that popover before changing it.Native Mac UI is verified through code review and CI per the repo convention; the PiP strip and the iOS alert are the two things worth your eyes on a real run.
Co-Authored-By: MiniMax Code noreply@minimax.io