Skip to content

Read the Windows rings the way the Mac does: stale, and round-ended - #271

Merged
vinzdg merged 2 commits into
vinzdg:mainfrom
RawJat:feat/windows-ring-readings
Sep 19, 2026
Merged

vinzdg merged 2 commits into
vinzdg:mainfrom
RawJat:feat/windows-ring-readings

Conversation

@RawJat

@RawJat RawJat commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Four differences between how a Windows ring reads and how the Mac's does. None of them change what a reading says — only when it is dimmed and how it is drawn.

Stale at 15 minutes, not 5. staleOf dimmed anything fetched more than 5 minutes ago; the Mac's UsageStore.staleAfter is 15, and it polls at 1 and 5. At five minutes a reading dimmed between two polls of a provider that was answering perfectly well, so "stale" came to mean "fetched a little while ago" rather than "old enough not to trust". traymenu::stale_since asked the same question five minutes earlier than the page, though its own comment says both draw on the same rule, so it moves too.

A refused refresh is not a stale reading. usage.rs set status = "stale" on the first 429, though the reading behind it was seconds old — and since the endpoint is the only source Windows has, one 429 dimmed the notch for as long as the rate limit lasted. This is the case that actually bites: I hit it within a minute of testing the change above. The Mac keeps the status it had and lets age decide (UsageStore holds the previous status until staleAfter), and its back-off guards the endpoint rather than the reading. So a 429 now leaves the status alone and says what is happening in the note it already writes — "Rate limited, retrying in Ns" — while the reading ages out on its own fifteen minutes after it was really fetched. A failure for any other reason is treated the same: no reading at all is still an error, a reading we could not refresh is just a reading. A snapshot restored from disk likewise keeps the status it was saved with, since fetched_at came back with it and its age is already known.

Only the reading dims. .dim sat on the whole .ringwrap, so the spinning white arc and the yellow attention pulse faded along with the percentage. Whether an agent is running right now is current whatever the age of the number beside it — the Mac adds its ActivityArc after the opacity it gives the stale stack, and TASKS.md says it outright: "Staleness dimming applies to the usage reading only." The activity arc moves into an SVG layer of its own so the dim goes around it. The track, the weekly ring and the glyph still fade.

Round arc ends. The Mac strokes its rings with lineCap: .round; these were square. A fraction of exactly zero now draws nothing at all, because a zero-length dash paints a dot once the caps are round, where the Mac's trim(from: 0, to: 0) draws nothing.

A reading small enough that its two caps meet still draws as a dot, exactly as it does on the Mac — ProviderRing.sweep is min(max(usedFraction ?? 0, 0), 1), with no minimum. #260 is arguing for a floor on the local model's arc rather than this one, so that question belongs with #260 rather than here; if it lands, the same floor belongs on these rings too.

Testing: cargo test 98 pass, 0 fail, 2 ignored; windows/scripts/check-ui-scripts.mjs parses all three pages. Checked by hand on Windows 11: the arcs read as round-ended, staleness forced on in a throwaway build confirms the rings fade while a running agent's arc stays at full strength, and a rate-limited Claude now keeps its reading with "Rate limited, retrying in Ns" in the card instead of greying out on the first 429.

#257 also touches notch.html and usage.rs. In notch.html it is around the card and the pointer handlers, while this is the ring CSS and renderRing/svgArc. In usage.rs its nearest hunks are at ~471 and ~528 and this one sits between them, in the RateLimited and Other arms — close, but not the same lines.

Three differences, all in how a ring reads rather than what it says.

The age a reading goes stale at. Windows dimmed after 5 minutes, the Mac
after 15 (UsageStore.staleAfter), and it polls at 1 and 5. At five minutes
a reading dimmed between two polls of a provider that was answering
perfectly well, so "stale" came to mean "fetched a little while ago"
rather than "old enough not to trust". A provider that knows its own
reading is stale still says so in its status, and that still dims at once.

What dims. `.dim` sat on the whole ringwrap, so the spinning white arc and
the yellow pulse faded with the percentage — but whether an agent is
running right now is current whatever the age of the number beside it. The
Mac adds its ActivityArc after the opacity it gives the stale stack, and
TASKS.md puts it plainly: staleness dimming applies to the usage reading
only. The arc moves to a layer of its own so the dim can go around it,
while the track, the weekly ring and the glyph still fade.

The ends of the arcs. The Mac strokes its rings with lineCap .round; ours
drew them square. A fraction of exactly zero now draws nothing at all,
because a zero-length dash paints a dot once the caps are round, where the
Mac's trim(from: 0, to: 0) draws nothing. A reading small enough that its
two caps meet still draws as a dot, as it does on the Mac: ProviderRing
has no minimum sweep, and vinzdg#260 is arguing for one on the local model's arc
rather than this one. If that lands, the same floor belongs here.
The ring dimmed the moment Claude answered one 429, though the reading
behind it was seconds old — and on Windows the endpoint is the only source
there is, so the first refused refresh of a rate limit dimmed the notch for
as long as the limit lasted. The Mac keeps the status it had and lets age
decide (UsageStore keeps the previous status until staleAfter), and its
back-off guards the endpoint rather than the reading.

So a 429 now leaves the status alone and says so in the note it already
writes — "Rate limited, retrying in Ns" — and the reading goes stale on its
own fifteen minutes after it was actually fetched. A fetch that fails for
any other reason is treated the same way: no reading at all is still an
error, a reading we could not refresh is just a reading. A snapshot
restored from disk keeps the status it was saved with, for the same reason:
fetched_at came back with it, so its age is already known.

The tray menu decided the same question five minutes earlier than the page,
though its own comment says both draw on the same rule. Fifteen there too.
@RawJat
RawJat requested a review from vinzdg as a code owner September 18, 2026 19:17
@vinzdg
vinzdg merged commit 19d7f5a into vinzdg:main Sep 19, 2026
4 checks passed
@RawJat
RawJat deleted the feat/windows-ring-readings branch September 19, 2026 07:42
abhishekthakur7 pushed a commit to abhishekthakur7/codenotch that referenced this pull request Sep 28, 2026
abhishekthakur7 pushed a commit to abhishekthakur7/codenotch that referenced this pull request Sep 28, 2026
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