feat(claude): weekly Fable usage tracking across menu, CLI, and widget - #1837
feat(claude): weekly Fable usage tracking across menu, CLI, and widget#1837chrisw1005 wants to merge 1 commit into
Conversation
Add a first-class weekly "Fable" rate-limit window, parallel to the existing all-models and model-specific (Sonnet/Opus) weekly buckets, and surface it in the menu card, menu descriptor, CLI renderer, and the macOS widget, plus a dedicated plan-utilization history series. Data sources: - OAuth/Web: read the model-scoped weekly limit from the newest `limits` array via scope.model.display_name == "Fable" (the legacy top-level seven_day_* keys are null on the new payload shape); keep seven_day_fable as a fallback. - Non-Fable scoped limits now also populate the model-specific weekly slot, so Sonnet/Opus weekly is restored on the new payload shape. - CLI TTY: parse the "Current week (Fable)" panel. - CLI probe JSON: parse week_fable / week_fable_only. Plumbs a `quaternary` window through UsageSnapshot (manual Codable, replacing(), backfillingResetTimes, hasRateLimitWindows), WidgetSnapshot, ProviderMetadata (fableLabel/supportsFable, defaulted so other providers are unaffected), and the three rateWindowLabels render surfaces (extracted to a shared RateWindowLabels struct to stay within the large_tuple limit). Tests cover the real OAuth `limits` payload, the Sonnet+Fable split, the web limits fallback, the TTY panel, and the fixture contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjzeKiduXP5G2mhGPnu5cT
|
Codex review: needs real behavior proof before merge. Reviewed July 2, 2026, 6:29 AM ET / 10:29 UTC. Summary Reproducibility: yes. for the review finding: source inspection shows WebUsageData stores only fablePercentUsed and the Fable RateWindow then uses webData.weeklyResetsAt. No live provider probe was run because AGENTS.md says real provider checks must be explicitly requested. Review metrics: 3 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Carry Fable-specific reset metadata through the web parser and mapper, add a distinct-reset regression test, and require redacted after-fix proof from at least one real CodexBar surface before merge. Do we have a high-confidence way to reproduce the issue? Yes for the review finding: source inspection shows WebUsageData stores only fablePercentUsed and the Fable RateWindow then uses webData.weeklyResetsAt. No live provider probe was run because AGENTS.md says real provider checks must be explicitly requested. Is this the best way to solve the issue? No as-is: the first-class Fable lane is plausible for widget parity, but the web parser should preserve scoped reset times before the implementation is merge-ready. The PR also needs redacted real CodexBar output because tests alone do not prove the claimed runtime surface. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 450ca4d91565. Label changesLabel justifications:
Evidence reviewedWhat I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 251b385fe5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| resetsAt: webData.weeklyResetsAt, | ||
| resetDescription: webData.weeklyResetsAt.map { Self.formatResetDate($0) }) |
There was a problem hiding this comment.
Preserve the scoped Fable reset time
When the Claude web usage payload falls back to limits[] (or when seven_day_fable has its own reset), the Fable/scoped object carries its own resets_at, but WebUsageData only keeps the percent and this new window reuses the all-model weeklyResetsAt. If seven_day is null/missing or the scoped bucket resets at a different time, the menu/CLI/widget will show a blank or wrong reset for Fable even though the source payload has the correct value; carry the scoped Fable reset through the parser and use it here.
Useful? React with 👍 / 👎.
|
Updated the description with a trimmed real response from the official On the newest payload the top-level
|
|
Thanks for the broader implementation and proof, @chrisw1005. We landed the narrower shared scoped-weekly mapping in #1851 ( Closing this conflicted variant as superseded. If widget presentation still needs a dedicated adjustment after the shared snapshot propagation, that can be a focused follow-up. |
What & why
Adds a first-class weekly "Fable" rate-limit window to the Claude provider, parallel to the existing all-models and model-specific (Sonnet/Opus) weekly buckets, and surfaces it everywhere Claude usage is shown: the menu card, the menu descriptor, the CLI renderer, and the macOS widget, plus a dedicated plan-utilization history series.
Motivation: the newest Claude usage payload exposes model-scoped weekly limits in a new
limitsarray, and the current code has no way to show the Fable weekly bucket.Key correctness detail — the real payload uses
limits[], notseven_day_fableOn the newest response from the official Anthropic OAuth usage endpoint (
GET https://api.anthropic.com/api/oauth/usage), the legacy top-levelseven_day_opus/seven_day_sonnet/seven_day_fablekeys are null, and the model-scoped weekly limits live in alimitsarray, identified byscope.model.display_name:{ "kind": "weekly_scoped", "group": "weekly", "percent": 59, "resets_at": "…", "scope": { "model": { "id": null, "display_name": "Fable" } } }So this PR parses the
limitsarray (OAuth + Web) and maps:weekly_scopedwithdisplay_name == "Fable"→ the new Fable window.weekly_scopedentries → the existing model-specific (Sonnet/Opus) slot — which also fixes that slot on the new payload shape, where it currently shows nothing.The legacy
seven_day_fablekey is kept as a fallback. The CLI TTY path parses the confirmedCurrent week (Fable)panel.Evidence — trimmed real response from the official
/api/oauth/usageendpointUnreleased per-model codename keys and the
*_dollarsfields are omitted; the shape of the relevant keys is unchanged.{ "five_hour": { "utilization": 100, "resets_at": "2026-07-02T07:10:00Z" }, "seven_day": { "utilization": 34, "resets_at": "2026-07-04T11:00:00Z" }, "seven_day_opus": null, "seven_day_sonnet": null, "seven_day_fable": null, "limits": [ { "kind": "session", "group": "session", "percent": 100, "resets_at": "2026-07-02T07:10:00Z", "scope": null, "is_active": true }, { "kind": "weekly_all", "group": "weekly", "percent": 34, "resets_at": "2026-07-04T11:00:00Z", "scope": null, "is_active": false }, { "kind": "weekly_scoped", "group": "weekly", "percent": 59, "resets_at": "2026-07-04T10:59:59Z", "scope": { "model": { "id": null, "display_name": "Fable" } }, "is_active": false } ] }Here the account has no
weekly_scopedSonnet/Opus entry, so only Session / Weekly / Fable are shown — the model-specific (opus) slot correctly stays empty.Design
Plumbs a
quaternarywindow through the shared stack so it renders on all four surfaces including the widget:UsageSnapshot(manualCodable,replacing(),backfillingResetTimes,hasRateLimitWindows)WidgetSnapshot.ProviderEntry+ widget UI rowsProviderMetadata.fableLabel/supportsFable(defaulted, so the other ~40 providers compile and behave unchanged)rateWindowLabelsrender surfaces (extracted to a sharedRateWindowLabelsstruct to stay within thelarge_tuplelint limit).fableplan-utilization history seriesThe lighter
extraRateWindowsapproach was considered but does not reachWidgetSnapshot, so it can't give widget parity.When Fable data is absent the window is simply
nilat every layer (no rows emitted, widget filters thenil-percent row, history skips the sample,Codableencodesnull) — identical to how an account without an Opus/Sonnet weekly behaves today. No force-unwraps on the new field.Honesty / review notes
/api/oauth/usageendpoint (Fable at 59% vialimits[].scope.model.display_name; see the evidence block above).limitsfallback mirrors OAuth by analogy (same backend shape) but was not verified against a real web (claude.ai/api/.../usage) payload.week_fable/week_fable_onlyare CodexBar-internal (no external producer); added by analogy to the existingweek_sonnethandling.Tests
swift build,swift test,swiftformat --lint, andswiftlint --strictall pass. New tests cover the real OAuthlimitspayload, the Sonnet+Fable split, the weblimitsfallback, theCurrent week (Fable)TTY panel, and the fixture contract.🤖 Generated with Claude Code