Skip to content

Add Codex Reset Credit Count and Expiry Display - #3

Open
TanjimReza wants to merge 2 commits into
kitze:mainfrom
TanjimReza:codex-reset-status
Open

TanjimReza wants to merge 2 commits into
kitze:mainfrom
TanjimReza:codex-reset-status

Conversation

@TanjimReza

@TanjimReza TanjimReza commented Jun 28, 2026 •

Copy link
Copy Markdown
  • New Features
    • Added display of Codex reset credit availability and next expiration for saved accounts.
    • Updated the menu bar status to show reset-credit information when available.
    • Added a new dashboard panel and account row details for reset-credit status.
  • Bug Fixes
    • Improved account loading so reset-credit data is shown even if one account’s lookup fails.
Screenshot_ 2026-06-28 at 15 39 16@2x

@coderabbitai

coderabbitai Bot commented Jun 28, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Codex reset credit support end-to-end: a new CodexResetCreditsFetcher calls /wham/rate-limit-reset-credits, results are decoded into CodexResetCredit/CodexResetCreditsSnapshot domain types, stored in CodexAccountUsage, and displayed in the menu bar title/tooltip, a new SummaryResetCreditsPanel dashboard widget, and per-account rows.

Changes

Codex Reset Credits Feature

Layer / File(s) Summary
Reset credits domain types and text helpers
Sources/CodexMaxx/main.swift
Introduces CodexResetCredit and CodexResetCreditsSnapshot with derived properties (availableCredits, nextExpiration, formatted summaries). Adds UsageText.fullDateTime, compactCountdown, and updates menuBarSummary to accept optional reset credits.
Reset credits fetcher
Sources/CodexMaxx/main.swift
chatGPTBaseURL visibility widened to non-private. New CodexResetCreditsFetcher.fetchResetCredits calls /wham/rate-limit-reset-credits, decodes the response, and maps entries into CodexResetCreditsSnapshot with robust date parsing.
Data model and loader integration
Sources/CodexMaxx/main.swift
CodexAccountUsage gains resetCredits and resetCreditsError fields; withActive copies them. CodexUsageLoader.load fetches reset credits per account, capturing errors in resetCreditsError.
Menu bar, dashboard, and account row UI
Sources/CodexMaxx/main.swift, README.md
render() passes activeResetCredits to renderStatusItem; menu bar title appends "R …" and tooltip shows reset availability. Dashboard conditionally shows new SummaryResetCreditsPanel. MainAccountCard and AccountRow display reset availability inline.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop hop, the credits are tracked,
Reset counts fetched, nothing lacks.
The menu bar glows with an "R",
Expiry dates shown near and far.
CodexMaxx knows when credits renew—
A bunny built this just for you! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main change: displaying Codex reset credit count and expiry information.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@TanjimReza TanjimReza changed the title Added Availabel Codex Reset Status & Expiry Added Available Codex Reset Status & Expiry Jun 28, 2026
@TanjimReza TanjimReza changed the title Added Available Codex Reset Status & Expiry Add Codex Reset Credit Count and Expiry Display Jun 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/CodexMaxx/main.swift (1)

535-544: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear stale reset-credit tooltips when reset info is not rendered.

Line 575 sets a reset tooltip only for stacked/circles layouts. Switching back to inline, losing the snapshot, or hitting the empty-rows path leaves the previous tooltip attached to the status item.

Proposed fix
         if rows.isEmpty {
             self.statusItem.length = NSStatusItem.variableLength
             self.statusItem.button?.image = nil
             self.statusItem.button?.title = "codexmaxx"
+            self.statusItem.button?.toolTip = nil
         } else {
             let activeResetCredits = rows.first(where: \.active)?.resetCredits
             let source = self.settings.source == .active
                 ? rows.first(where: \.active)?.snapshot
                 : UsageMath.combined(rows.map(\.snapshot))
@@
         guard let snapshot else {
             self.statusItem.length = NSStatusItem.variableLength
             self.statusItem.button?.image = nil
             self.statusItem.button?.title = "S ? W ?"
+            self.statusItem.button?.toolTip = nil
             return
         }
@@
         case .inline:
             self.statusItem.length = NSStatusItem.variableLength
             self.statusItem.button?.image = nil
             self.statusItem.button?.title = UsageText.menuBarSummary(snapshot, resetCredits: resetCredits, settings: self.settings)
+            self.statusItem.button?.toolTip = nil

Also applies to: 557-575

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/CodexMaxx/main.swift` around lines 535 - 544, The status item is
leaving a stale reset-credit tooltip behind when reset info is no longer being
rendered. Update the logic around the empty-rows path and the
`renderStatusItem(snapshot:resetCredits:)` flow so any previously set tooltip is
cleared whenever the layout switches away from stacked/circles or no
snapshot/reset credits are available. Make sure the tooltip state on
`statusItem.button` is reset consistently in the same places that the
title/image are updated.
🧹 Nitpick comments (1)
Sources/CodexMaxx/main.swift (1)

3262-3273: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid making reset credits block primary usage rendering.

The reset-credit call is supplemental, but this awaits it before returning the already-fetched usage snapshot. A slow reset endpoint can delay account usage display by the reset request timeout.

Proposed refactor
-            let response = try await CodexOAuthUsageFetcher.fetchUsage(
+            async let usageResponse = CodexOAuthUsageFetcher.fetchUsage(
                 accessToken: credentials.accessToken,
                 accountId: credentials.accountId,
                 env: env)
+            async let resetCreditsResult: Result<CodexResetCreditsSnapshot, Error> = {
+                do {
+                    return .success(try await CodexResetCreditsFetcher.fetchResetCredits(
+                        accessToken: credentials.accessToken,
+                        accountId: credentials.accountId,
+                        env: env))
+                } catch {
+                    return .failure(error)
+                }
+            }()
+
+            let response = try await usageResponse
             let snapshot = CodexReconciledState
                 .fromOAuth(response: response, credentials: credentials)
             let resetCredits: CodexResetCreditsSnapshot?
             let resetCreditsError: String?
-            do {
-                resetCredits = try await CodexResetCreditsFetcher.fetchResetCredits(
-                    accessToken: credentials.accessToken,
-                    accountId: credentials.accountId,
-                    env: env)
+            switch await resetCreditsResult {
+            case .success(let value):
+                resetCredits = value
                 resetCreditsError = nil
-            } catch {
+            case .failure(let error):
                 resetCredits = nil
                 resetCreditsError = error.localizedDescription
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/CodexMaxx/main.swift` around lines 3262 - 3273, The reset-credits
fetch in the usage flow is blocking the already-available usage snapshot, which
can delay primary rendering. Update the code around the
CodexResetCreditsFetcher.fetchResetCredits call in this async block to run the
reset-credits request independently of the main usage response, so account usage
can return immediately while the supplemental reset data is resolved later. Keep
the existing resetCredits and resetCreditsError handling, but move this work out
of the critical path in the main usage rendering logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/CodexMaxx/main.swift`:
- Around line 3530-3543: Update compactCountdown(to:now:) so expired or due-now
dates return the now value instead of a minimum minute; the current fallback in
compactCountdown is causing just-expired timestamps to display as 1m. Adjust the
early/terminal case in compactCountdown(to:now:) to detect date <= now and
return now, while keeping the existing day/hour formatting for future dates.
- Around line 3270-3273: The reset credit fetch failure is being stored in
`resetCreditsError` inside `CodexUsageLoader.load`, but nothing in the UI
consumes it, so the error state is never visible. Update the account/status
rendering path that reads `resetCredits` to also surface `resetCreditsError`, or
remove `resetCreditsError` from `CodexUsageLoader` if you do not intend to show
fetch failures.

---

Outside diff comments:
In `@Sources/CodexMaxx/main.swift`:
- Around line 535-544: The status item is leaving a stale reset-credit tooltip
behind when reset info is no longer being rendered. Update the logic around the
empty-rows path and the `renderStatusItem(snapshot:resetCredits:)` flow so any
previously set tooltip is cleared whenever the layout switches away from
stacked/circles or no snapshot/reset credits are available. Make sure the
tooltip state on `statusItem.button` is reset consistently in the same places
that the title/image are updated.

---

Nitpick comments:
In `@Sources/CodexMaxx/main.swift`:
- Around line 3262-3273: The reset-credits fetch in the usage flow is blocking
the already-available usage snapshot, which can delay primary rendering. Update
the code around the CodexResetCreditsFetcher.fetchResetCredits call in this
async block to run the reset-credits request independently of the main usage
response, so account usage can return immediately while the supplemental reset
data is resolved later. Keep the existing resetCredits and resetCreditsError
handling, but move this work out of the critical path in the main usage
rendering logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 03018df4-cbf4-4a1f-810a-9d6c2fbf4c01

📥 Commits

Reviewing files that changed from the base of the PR and between bdd680c and 273a1bb.

📒 Files selected for processing (2)
  • README.md
  • Sources/CodexMaxx/main.swift

Comment on lines +3270 to +3273
} catch {
resetCredits = nil
resetCreditsError = error.localizedDescription
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether resetCreditsError is rendered or otherwise consumed.
rg -n -C3 '\bresetCreditsError\b' .

Repository: kitze/codexmaxx

Length of output: 3439


🏁 Script executed:

#!/bin/bash
rg -n -C4 '\b(CodexAccountUsage|resetCredits|resetCreditsError)\b' Sources/CodexMaxx/main.swift
sed -n '1538,1570p' Sources/CodexMaxx/main.swift
sed -n '2338,2372p' Sources/CodexMaxx/main.swift

Repository: kitze/codexmaxx

Length of output: 22504


Surface resetCreditsError or drop the field. resetCreditsError is set in CodexUsageLoader.load, but the UI only reads resetCredits, so fetch failures never reach the visible account/status views. Either render the error state or remove the unused field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/CodexMaxx/main.swift` around lines 3270 - 3273, The reset credit
fetch failure is being stored in `resetCreditsError` inside
`CodexUsageLoader.load`, but nothing in the UI consumes it, so the error state
is never visible. Update the account/status rendering path that reads
`resetCredits` to also surface `resetCreditsError`, or remove
`resetCreditsError` from `CodexUsageLoader` if you do not intend to show fetch
failures.

Comment on lines +3530 to +3543
static func compactCountdown(to date: Date, now: Date = Date()) -> String {
let seconds = max(0, Int(date.timeIntervalSince(now)))
let days = seconds / 86_400
let hours = (seconds % 86_400) / 3_600
let minutes = (seconds % 3_600) / 60
if days > 0 {
return hours > 0 ? "\(days)d\(hours)h" : "\(days)d"
}
if hours > 0 {
return minutes > 0 ? "\(hours)h\(minutes)m" : "\(hours)h"
}
return "\(max(1, minutes))m"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Return now for expired compact countdowns.

For dates at or before now, Line 3541 currently returns 1m, which makes just-expired reset credits look like they still have time remaining.

Proposed fix
     static func compactCountdown(to date: Date, now: Date = Date()) -> String {
-        let seconds = max(0, Int(date.timeIntervalSince(now)))
-        let days = seconds / 86_400
-        let hours = (seconds % 86_400) / 3_600
-        let minutes = (seconds % 3_600) / 60
+        let seconds = date.timeIntervalSince(now)
+        guard seconds > 0 else { return "now" }
+
+        let totalMinutes = max(1, Int(ceil(seconds / 60)))
+        let days = totalMinutes / (24 * 60)
+        let hours = (totalMinutes / 60) % 24
+        let minutes = totalMinutes % 60
         if days > 0 {
             return hours > 0 ? "\(days)d\(hours)h" : "\(days)d"
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static func compactCountdown(to date: Date, now: Date = Date()) -> String {
let seconds = max(0, Int(date.timeIntervalSince(now)))
let days = seconds / 86_400
let hours = (seconds % 86_400) / 3_600
let minutes = (seconds % 3_600) / 60
if days > 0 {
return hours > 0 ? "\(days)d\(hours)h" : "\(days)d"
}
if hours > 0 {
return minutes > 0 ? "\(hours)h\(minutes)m" : "\(hours)h"
}
return "\(max(1, minutes))m"
}
static func compactCountdown(to date: Date, now: Date = Date()) -> String {
let seconds = date.timeIntervalSince(now)
guard seconds > 0 else { return "now" }
let totalMinutes = max(1, Int(ceil(seconds / 60)))
let days = totalMinutes / (24 * 60)
let hours = (totalMinutes / 60) % 24
let minutes = totalMinutes % 60
if days > 0 {
return hours > 0 ? "\(days)d\(hours)h" : "\(days)d"
}
if hours > 0 {
return minutes > 0 ? "\(hours)h\(minutes)m" : "\(hours)h"
}
return "\(max(1, minutes))m"
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/CodexMaxx/main.swift` around lines 3530 - 3543, Update
compactCountdown(to:now:) so expired or due-now dates return the now value
instead of a minimum minute; the current fallback in compactCountdown is causing
just-expired timestamps to display as 1m. Adjust the early/terminal case in
compactCountdown(to:now:) to detect date <= now and return now, while keeping
the existing day/hour formatting for future dates.

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