Skip to content

feat(alerts): courtesy-reset detection, iOS runaway alerts, and no PiP titlebar strip - #195

Merged
jaywedgeworth22 merged 1 commit into
mainfrom
mm/reset-and-ios-alerts
Oct 9, 2026
Merged

jaywedgeworth22 merged 1 commit into
mainfrom
mm/reset-and-ios-alerts

Conversation

@jaywedgeworth22

Copy link
Copy Markdown
Collaborator

Board 7179df4aab7a444883eb668b747b62e4. Owner batch, 2026-10-08.

Courtesy / 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 distinct gaps, same consequence — the owner believes a cap still applies that does not.

  1. 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.

  2. 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 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

"iOS should have info about heavy anomalies in usage or runaway usage like the mac."

This is new plumbing, not a toggle: the shared handoff carried no alert data at all. Payload gains an optional alerts array, 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 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, not once per poll.

PiP: the blank silver strip is gone

"it needs to not have the blank silver part on top for no reason"

.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. Neither titleVisibility = .hidden nor titlebarAppearsTransparent could 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. isMovableByWindowBackground already made the body draggable, so the titlebar was never load-bearing.

Tests

  • 5 courtesy-reset cases: mid-period, window-end rolled forward, moderate usage level, ordinary use never looks like one, alerts once.
  • 4 payload cases: older file still decodes, round trip, id survives byte-for-byte, an unknown kind does not break decoding.
  • PipMetricsTests now pins that the style mask carries no .titled/.hudWindow, so the strip cannot come back.
  • swift build clean; swift test 375 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.barRemaining was 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

…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>
@jaywedgeworth22
jaywedgeworth22 merged commit 3d01c7c into main Oct 9, 2026
4 checks passed
@kody-ai

kody-ai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ✅

Access your configuration settings here.

​

@kody-ai

kody-ai Bot commented Oct 9, 2026

Copy link
Copy Markdown

kody code-review Kody Rules medium

Single-space sentence gap in the first PR description item joins two sentences at one space (invisible. The early-reset), violating the owner rule requiring two spaces between sentences. GitHub-flavored markdown collapses two literal spaces in rendered PR bodies, so emit the doubled gap as the literal HTML entity &nbsp; followed by a normal space (invisible.&nbsp; The early-reset).

Kody rule violation: Use two spaces between sentences in every human-facing string and agent-written paragraph

@kody-ai

kody-ai Bot commented Oct 9, 2026

Copy link
Copy Markdown

kody code-review Business Logic medium

🤔 Insufficient Task Context

I 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:

  • Business requirements and acceptance criteria
  • Expected behavior and business rules
  • Edge cases and constraints to consider

💡 How to improve the task context:

  • Add a description to the linked ticket
  • Include acceptance criteria or business rules
  • Describe the expected behavior after the change

⚠️ Important:

A task title alone is not sufficient to determine whether the implementation is correct or complete.


💡 This validation runs automatically only on the first review of a pull request. To run it again, comment @kody -v business-logic.

Comment on lines 1788 to 1796
} 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

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] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

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.

​

​

Comment on lines +862 to +873

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug medium

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.

​

​

Comment on lines +835 to +843
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")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug medium

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.

​

​

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant