feat(quota): show Claude plan limits (5h / weekly / weekly per model) in the island - #338
Open
mutoe wants to merge 7 commits into
Open
feat(quota): show Claude plan limits (5h / weekly / weekly per model) in the island#338mutoe wants to merge 7 commits into
mutoe wants to merge 7 commits into
Conversation
Opt-in setting reads the Claude Code OAuth login from Keychain and fetches Anthropic's subscription windows — the numbers /usage shows. Expanded panel: footer line with every window (bar, percent, reset countdown). Collapsed island: ring chip beside the session count for one window; Auto ranks by pace (used share minus elapsed share), or pin a window. Refresh is event-driven off Stop hooks: 15s coalesce, 60s throttle with a trailing fetch, 10-minute idle tick, exponential backoff, and no token refresh — an expired token just asks to run Claude Code once. Keychain reads hop to GCD so the macOS access prompt never blocks the main actor. Tests cover parsing (real response fixture + legacy fallback), the pace selector, countdown formatting, HTTP status mapping, the scheduler's debounce/throttle/backoff rules, and the monitor's fetch behaviour on a private UserDefaults suite.
Auto now defaults to the tighter of the two weekly windows and only hands the chip to the 5-hour window while that one is pressing — ahead of pace or past 70%. The chip carries its window label (5h / week / model name) before the ring so the number is never ambiguous; the collapsed bar reserves width for the label.
The collapsed chip moves next to the mascot and only shows while no tool name is displayed — a running tool is the more urgent signal, and the right wing keeps the session count alone. The chip shares the tool slot's width reserve.
SecItemCopyMatching from our own process raised the Keychain access prompt on every ad-hoc rebuild. Claude Code stores the item with /usr/bin/security, which is therefore on the item's ACL and reads it silently — the same route the ccusage Raycast extension takes — so the credential is now read through a security(1) subprocess with a timeout. The collapsed chip's reserve was a label-length estimate and clipped "Fable 53%"; the bar now measures the laid-out chip through a PreferenceKey and reserves exactly that.
…under the notch The collapsed bar was a flexible row centred on the notch with a spacer of at least the notch width between the wings. That guarantees a gap, not that the gap lines up with the cutout: whichever wing is wider pushes the gap sideways, and its tail ends up under the physical notch. Screenshots show those pixels, the display doesn't — which is how the plan-limit chip read 'Fable ◑5' on screen while every capture looked fine. Widening the total never helps either, since half of any extra goes to the far edge. Each wing now reports its ideal content width (rigid via fixedSize; the tool name still truncates at its own cap) and both wings get the same slot: the reserve, or the wider wing plus a 6pt gap to the notch. The row between them is exactly the notch. Non-notch screens keep the flexible row and just add the measured overflow. The chip's label-length estimate is gone.
…dth, trimmed right wing Sizing the wings from their content made the bar jump on every tool change: the tool name and the plan-limit chip take turns in the same slot and are different widths. Now the chip stays laid out as an invisible placeholder while a tool name shows, so that switch never moves anything; only a tool name wider than the chip widens the bar, eased in, and the width shrinks back 5s after the content got narrower. Wings are no longer forced equal: the left keeps the classic reserve or its content, the right is trimmed to its content (count plus badges), and the whole bar shifts by half the difference so the gap between the wings still lands exactly on the notch — a test checks the shift geometry, which caught the sign being inverted on the first try.
…pace The 5-hour takeover line moves from 70% to 80%. The two weekly windows (all models, current model) now compete on the same terms as the session: if either is pressing — ahead of pace or past the line — the one further ahead of pace shows; only when neither is pressing does the higher percentage decide.
nguyenvanduocit
pushed a commit
to nguyenvanduocit/CodeIsland
that referenced
this pull request
Sep 4, 2026
Upstream PR wxtsky#338 (open) adds live Claude rate-limit quota display to the island: 5h / weekly / weekly-per-model windows with progress bars, reset countdowns, and a collapsed chip showing the pressing window. Tracked as T-089 (medium priority, M effort, gate: merge). Also notes PR wxtsky#337 (configurable animation speed, watch alongside T-084). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEPtxTR2PTGXUhaXt1VEPj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Claude Code's
/usageshows three rate-limit windows — 5-hour, weekly (all models), weekly (current model) — and they are the thing that actually stops a long session. Nothing in the island tells you how close you are until the CLI refuses to continue. The existing token-usage stats are a different question (how much did I spend), not this one (how much is left before I'm cut off).Change
An opt-in Show Claude plan limits setting, off by default because it reads the Claude Code login.
Data. The Claude Code OAuth token from Keychain (
Claude Code-credentials), with~/.claude/.credentials.jsonas fallback, sent toGET /api/oauth/usagewith theoauth-2025-04-20beta header — the same call/usagemakes. The response'slimits[](session/weekly_all/weekly_scopedwithpercent,resets_at,severity, the scoped model's display name) is parsed with a fallback to the legacyfive_hour/seven_dayshape. The token is only ever read; refreshing it would invalidate the one Claude Code holds, so an expired token surfaces as "run Claude Code once".The Keychain item is read by running
security find-generic-password -s "Claude Code-credentials" -wrather thanSecItemCopyMatching. Claude Code writes the item throughsecurity, so that binary is on the item's ACL and the read is silent; an in-process read from an ad-hoc-signed build prompts on every rebuild because the designated requirement changes. (The ccusage Raycast extension does the same thing.)Expanded panel. A footer line with every window: mini bar, percent, reset countdown (
↻1h56m/↻6h06m), coloured by severity, refreshed each minute.Collapsed island. A chip in the left wing next to the mascot — window label, 9pt ring, percent (
Fable ◑58%). If the tool-name display is on and a tool is running, the tool name has priority; the chip stays laid out as an invisible placeholder so the bar doesn't change width when they swap. The chip mode is a picker: off / auto / 5h / weekly / weekly (current model).Auto mode. A window is pressing when its used share exceeds the elapsed share of the window (ahead of pace) or it is at ≥80%. The 5-hour window takes the chip while pressing; otherwise a weekly window — if either weekly is pressing, the one further ahead of pace, else the one with more used. No weekly reported → 5-hour.
Refresh policy. Event-driven on
Stophooks from local Claude sessions: 15s coalesce, at most one fetch a minute, a trailing fetch for stops that landed during the throttle. A 10-minute tick while the setting is on, a fetch on expand if the snapshot is over a minute old, exponential backoff (60s → 15m) on errors, polling stops entirely on 401/403, and nothing runs while the mascot gate says the machine is asleep. The policy is a pure struct (ClaudeQuotaScheduler) so it's tested without timers.Collapsed-bar layout fix
Putting a ~67pt chip in the left wing exposed a pre-existing layout flaw on notched screens. The collapsed bar was a flexible row centred on the notch with a
Spacer(minLength: notchW)between the wings. That guarantees a gap at least as wide as the notch, not that the gap lines up with the cutout — whichever wing is wider pushes the gap sideways and its tail ends up under the physical notch. Screenshots show those pixels; the display doesn't. A long tool name already did this; the chip made it obvious.Now each wing reports its ideal content width (rigid via
fixedSize, the tool name still truncating at its 120pt cap), the row between them is exactly the notch, and the whole bar is offset by half the wing difference so the gap sits on the cutout. The left wing keeps the classic reserve or its content; the right wing is trimmed to its content (count plus badges) so the bar stays clear of menu-bar icons. Width changes ease in and only shrink back after 5s, so a tool name that comes and goes doesn't make the bar breathe. Non-notch screens keep the flexible row and just add the measured overflow. A geometry test pins the shift (it caught the sign inverted on the first attempt).Tests
ClaudeQuotaTests: parse of a real response, legacy fallback, garbage rejection, elapsed/pace maths, auto-mode rules (weekly default, 5h takeover at pace/80%, weekly ranked by pace), fixed modes, countdown formats, severity levels, credential parse, request headers, HTTP status mapping.ClaudeQuotaSchedulerTests: coalescing, trailing fetch, remembered stops, backoff cap, rejected token, expand staleness.ClaudeQuotaMonitorTests: expand fetch, disabled never fetches, burst coalesces, unauthorized stops polling — with an injectedUserDefaultssuite so no test touches the real setting (a monitor on standard defaults would make every other test'sAppStatedo real credential reads).NotchPanelViewTests: wing slot and notch-shift geometry.Full suite green locally on macOS 26.4 (arm64): 960 tests, 2 skipped, 0 failures. Verified on a 14" notched display by measuring the rendered bar against
NSScreen.auxiliaryTopLeftArea: chip right edge 6pt clear of the cutout, bar extent identical across chip ↔ tool-name swaps, no Keychain prompt on a fresh ad-hoc build.🤖 Generated with Claude Code