Skip to content

Offer the weekly limit as the main ring - #336

Merged
vinzdg merged 2 commits into
vinzdg:mainfrom
Splinter0000:paulo/weekly-headline
Sep 24, 2026
Merged

vinzdg merged 2 commits into
vinzdg:mainfrom
Splinter0000:paulo/weekly-headline

Conversation

@Splinter0000

Copy link
Copy Markdown
Contributor

Offer the weekly limit as the main ring

For a provider with a weekly limit beside a shorter one, the big ring is the
short window. On the machine this was built on, a Claude account read 1% on
the notch while its week stood at 94% — and the week is the limit that
actually runs out, the one that decides when to switch account. It sat in the
hover card, or in the optional thin ring.

A new switch in Appearance, Weekly limit as the main ring, off by default,
leads with the week instead: the big ring becomes the weekly window, the thin
ring (where one is on) the shorter one, and nothing leaves the card. Independent
of #321 and #331.

Decided by length, not by name

A provider's weeklyID is whatever it draws as the second ring, and that is not
always a week — Grok's is its credits, and Codex can report the week as its
primary window with the five hours second (your own
testStillReadsACountdownIfABuildEmitsOne). So the swap happens only when the
second window runs about a week (6–8 days) and is longer than the one
leading. Anything else is returned exactly as the vendor sent it: a Codex plan
that reports only the week, Cursor's monthly cycle, Grok's credits.

I know weeklyWindow's doc prefers declared ids over durations. The check here
is a guard on top of the declared id, not a replacement for it: the swap changes
what the big ring means, so it wants proof the window is a week, and a
provider that declares one without a duration is simply left alone.

Rings only

Like DailyPace, it is laid over the snapshots on the way out, in one place
(AppDelegate.drawn), applied first so Claude's daily pace ring still leads
where both are on. It reaches the notch and the phone only.

The menu bar and the alert watchers keep the vendor's own order, on purpose — my
first version fed them the swapped snapshots too, and review found:

  • the menu bar's weekly mini-ring and its "Weekly Limit" label would have drawn
    the session after a swap;
  • UsageResetWatcher and ThresholdNotifier track one headline per provider
    and have no notion of which window it was, so flipping the switch re-fired
    threshold alerts and could announce a weekly reset that had not happened
    (stale session deadline vs. the week's);
  • UsageLimitWatcher tells session from week by which one leads, so it had to
    be fed the unswapped order anyway — which left a spent session announced with
    no "available again" to follow it.

So alerts and the menu bar are byte-for-byte what they were. (The same
stale-state weakness exists today when the daily pace ring is flipped on — out of
scope here, happy to look at it separately.)

Copy

Two new strings through L10n.t, with pt-BR added to Localizable.xcstrings.
The catalog was re-serialized with its own formatting, so the diff is the new
entries only.

Tests

8 in WeeklyHeadlineTests (in DailyPaceTests.swift, same concern): the week
leads and the session moves to the thin ring; off leaves every snapshot as sent;
a week already leading is left alone; a second window that is absent, is not a
week, or is monthly is left alone; a provider with no second window is
unchanged; the daily pace still leads where both are on; the preference is off
by default and remembered. Full suite 1742 / 0 failures on this branch alone.


🤖 Generated with Claude Code

For a provider with a weekly limit beside a shorter one, the big ring was
the short window: a Claude account read 1% on the notch while its week
stood at 94%, and the week -- the limit that actually runs out, and the
one that decides when to switch account -- sat in the hover card or a
thin second ring. A new "Weekly limit as the main ring" switch, off by
default, leads with the week instead: the big ring becomes the weekly
window, the thin ring (where one is on) the shorter one, and nothing
leaves the card.

It is decided by how long the window runs, not by what it is called. A
provider's weeklyID is whatever it draws as the second ring, and that is
not always a week: Grok's is its credits, and Codex can report the week
as its primary window with the five hours second. So the swap happens
only when the second window runs about a week and is longer than the one
leading; anything else is returned as the vendor sent it.

Like DailyPace it is laid over the snapshots on the way out
(AppDelegate.drawn), and applied first, so Claude's daily pace ring still
leads where both are on. It reaches the rings only -- the notch and the
phone. The menu bar and the alert watchers keep the vendor's own order:
the menu bar already shows the week beside the short window under a
"Weekly Limit" label that would name the session after a swap, and the
watchers track one headline per provider and tell session from week by
which window leads, so a swap under them re-fired thresholds, announced
a weekly reset that had not happened, and left a spent session with no
"available again" to follow it. Alerts are exactly what they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vinzdg
vinzdg merged commit 5d47c48 into vinzdg:main Sep 24, 2026
2 checks passed
@vinzdg

vinzdg commented Sep 24, 2026

Copy link
Copy Markdown
Owner

Merged as d2b9e54. Thanks @Splinter0000.

The thing that made this easy to take: it's off by default, so nobody's rings move unless they ask. And the selection is by duration rather than by name (WeeklyHeadline.swift:36-40), which handles the awkward cases correctly — Cursor has no second window, Grok's credits window is the weekly one, Qianwen has headlineID == weeklyID, and a Codex plan already leading with the week is skipped by the headlineLength >= weeklyLength guard. All four are tested.

The part I appreciated most is the scoping: AppDelegate.drawn feeds only the notch and PhoneLink, while the menu bar, ThresholdNotifier, resetWatcher and limitWatcher keep reading the vendor's own order from store.$snapshots. Swapping the rings without re-firing thresholds or mislabelling the menu bar line is exactly the distinction that would have caused a bug later.

One follow-up worth doing: the Windows port is now divergent. windows/codenotch/ui/notch.html has its own headlineOf/weeklyOf and no weeklyHeadline preference, so the same person gets different rings on the two platforms. Either port the swap behind the same config key, or we record the divergence deliberately — I'd prefer the former if you're willing.

Nit: WeeklyHeadline.swift:47 sets led.weeklyID = headline?.id; if headlineID names a window that isn't in windows, the swap still happens and the thin ring silently vanishes. Worth a guard or a comment.

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.

2 participants