Skip to content

feat(claude_usage): refresh action, local token history and API status#973

Open
jondmarien wants to merge 1 commit into
amnweb:mainfrom
jondmarien:feat/claude-usage-improvements
Open

feat(claude_usage): refresh action, local token history and API status#973
jondmarien wants to merge 1 commit into
amnweb:mainfrom
jondmarien:feat/claude-usage-improvements

Conversation

@jondmarien

@jondmarien jondmarien commented Jun 14, 2026

Copy link
Copy Markdown

Following your comment on #971, I reworked this and went back through the contributing guidelines. This closes #967, #970 and #971 and puts everything in one PR. They were stacked on top of each other and awkward to review one at a time, so I rebased onto main as separate commits, one per feature, so each reads on its own. Happy to change anything that doesn't fit the project's conventions.

Since opening this I added two more commits on top while using the widget every day: a per-model token breakdown and a pinnable popup. They're the last two commits, same off-by-default style as the rest.

Everything here is off by default and builds on the existing claude_usage widget.

1. Refresh, stale indicator, reset wording

A refresh callback plus a button in the popup header that force an immediate fetch and skip cache_ttl. Repeat fetches are still deduplicated while one is in flight, and a manual refresh rewrites the disk cache so the next scheduled tick reuses it instead of hitting the network again.

The header now uses the stat_popup.py layout you suggested (a QFrame header holding a QLabel.text title and a QPushButton), so the old ClickableLabel is gone. One thing to watch when theming: .claude-usage-menu .header is the container row now, and the title text is .header .text.

There's also a {stale} placeholder that shows a warning glyph once the OAuth token has expired. It reads the credentials' expiresAt only, never the token itself, and it's just a cue since the widget can't renew the token (running Claude Code does that). And two reset-format options, five_hour_reset_format / seven_day_reset_format with reset_show_date, which fix the broken "Resets in Sat 6:00 AM" line.

2. Local token-usage history

An optional Tokens section in the popup: a Session/Today/Week/Month/Year toggle, the total for the selected period, and an optional graph that reuses the shared GraphWidget. The same totals are on the bar through {session_tokens}, {today_tokens} and the rest.

It reads Claude Code's own session transcripts under ~/.claude/projects, so there's no API key and no network call. Only the numeric token counts, timestamps, model name and session id get read; message content never does. A transcript is re-parsed only when its size or mtime changes, and the scan runs off the UI thread in a shared service like the usage poller.

3. Claude API status

An optional dot driven by the public status page at status.claude.com (no auth). {status} colours the dot by level through .status.none / .minor / .major / .critical, {status_text} gives the description, and you can show a status line in the popup header. If a poll fails it keeps the last known value rather than blanking out.

4. Per-model token breakdown

The Tokens section can now show where the tokens actually went. Turn on token_history.show_models and a short bar list appears (Opus 4.8, Sonnet 4.6, Fable 5, whatever else is in your transcripts), sorted by usage, top five. It follows the same period toggle as the rest of the section, so switching to Week or Month re-ranks the models for that window.

Same local data as the token history; nothing new gets read and there's still no network call. Model ids are shortened for display (claude-opus-4-6 becomes Opus 4.6), with a fallback so an unrecognised id shows as-is instead of vanishing. I also moved the period toggle and running total down next to the graph, since the big number looked stranded once the model list sat between it and the chart.

5. Pinnable popup

A pin button in the popup header, the same one cpu / memory / gpu use. It reuses the shared PinnablePopup, so pinning keeps the popup open and lets you drag it anywhere. The pin_icon / unpin_icon fields default to the same Segoe Fluent Icons glyphs the other widgets use; the button sits to the right of refresh and themes with .claude-usage-menu .header .pin-btn.

Notes

Docs are updated under docs/. Ruff check and format are clean. I tested against my own install plus offscreen Qt: the refresh button and callback, the stale glyph, both reset formats, the token section across all five periods, the per-model breakdown and its re-ranking when I switch periods, the pin button, and the status dot at each level.

This also folds in the docs-only changes from #969 by @ManaphatDev (the custom <img> icon note and the README index entry), credited via Co-Authored-By. They touch the same docs this PR rewrites, and #969 was stuck on an unrelated CI failure, so it was cleaner to bring them in here.

@jondmarien

Copy link
Copy Markdown
Author

Pushed two more commits on top (the last two): a per-model token breakdown in the Tokens section, and a pinnable popup that matches the cpu/memory/gpu pin button. Both are off by default with no new deps or network calls; the breakdown reuses the transcript data the token-history scan already reads, and the pin reuses the shared PinnablePopup. The single-sample graph case is handled in the widget itself, so the shared GraphWidget is untouched. Description updated with sections 4 and 5.

@amnweb

amnweb commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Can you show me a screenshot of how this widget looks now? There have been so many changes. Also, please squash these commits into a single commit.

@jondmarien

Copy link
Copy Markdown
Author

Can you show me a screenshot of how this widget looks now? There have been so many changes. Also, please squash these commits into a single commit.

Yes, 100%. I will also squash these into a single commit. I had them separated so you could read them, and have no problem squashing them.

Here is what the widget looks like now:
Full View:
image
Hourly:
image
Weekly:
image

@amnweb

amnweb commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Is this a bug in the progress bar, or have you set padding or margin in the style? It does not fit the container, as you can see in the screenshot.
image

@jondmarien

Copy link
Copy Markdown
Author

Is this a bug in the progress bar, or have you set padding or margin in the style? It does not fit the container, as you can see in the screenshot. image

Let me confirm for you, i hadn't noticed that. Good eyes!!

@amnweb

amnweb commented Jun 15, 2026

Copy link
Copy Markdown
Owner

I just tested the Copilot widget, and everything looks fine there.

image

@jondmarien

Copy link
Copy Markdown
Author

I just tested the Copilot widget, and everything looks fine there.
image

okay yeah it must be the claude widget then. I took inspo from the copilot widget for this. Let me fix this!

@jondmarien

jondmarien commented Jun 15, 2026

Copy link
Copy Markdown
Author

this also affects the model bars, not just the hourly/weekly bars. will fix for all in this widget!

edit: I think because I implemented my own UsageBar, and did not reuse the ProgressBar from Copilot, it created some differences. fixing now.

edit2: i think its my css 🙈 let me investigate further

@jondmarien

jondmarien commented Jun 15, 2026

Copy link
Copy Markdown
Author

@amnweb could you provide your css for the copilot widget regarding the .progress/.fill/.section rules so I can confirm it is a css issue? I am almost 100% sure, but I am not the best with css.

@jondmarien jondmarien force-pushed the feat/claude-usage-improvements branch from c97294a to 87f6338 Compare June 15, 2026 21:38
@jondmarien

jondmarien commented Jun 15, 2026

Copy link
Copy Markdown
Author

Squashed the branch into a single commit, as you asked.

What's folded into it:

  • refresh action plus a {stale} indicator that fires once the OAuth token expires, and per-window reset wording that fixes the old "Resets in Sat 6:00 AM" line
  • local token-usage history (Session/Today/Week/Month/Year) read from Claude Code's own transcripts under ~/.claude/projects, scanned off-thread and incrementally; the cache is pruned on each scan (hourly past 15 days, daily past ~13 months) so it stays bounded
  • the Claude API status dot from the public status page, no auth
  • a per-model token breakdown in the Tokens section that follows the period toggle; model names are derived from the id so new models need no upkeep, and fast-mode usage gets its own row
  • the pinnable, draggable popup
  • review fixes from @ManaphatDev (credited as co-author) and two small follow-ups: the popup now resizes to fit the selected period, and the token formatter rounds correctly at the unit boundaries (999,999 reads as 1M instead of 1000K)

On the bar that didn't fit its container: see comment below

@jondmarien

Copy link
Copy Markdown
Author

@amnweb could you provide your css for the copilot widget regarding the .progress/.fill/.section rules so I can confirm it is a css issue? I am almost 100% sure, but I am not the best with css.

nevermind, i had max-height set for some reason.

looks like this now:
image

@jondmarien

jondmarien commented Jun 15, 2026

Copy link
Copy Markdown
Author

the only thing I noticed is this placement of the tokens per model usage is not inline. would you like me to fix this as well?

image

@amnweb

amnweb commented Jun 15, 2026

Copy link
Copy Markdown
Owner

But this is a problem with the progress bar. You should make it the same size, but I have no idea how because this popup window can be changed via CSS, so you can't limit size there :(

image

@jondmarien

Copy link
Copy Markdown
Author

But this is a problem with the progress bar. You should make it the same size, but I have no idea how because this popup window can be changed via CSS, so you can't limit size there :(
image

hmmm.... really good point amnweb....i will take a bit to think about this. will comment when I have a working idea.

@jondmarien jondmarien force-pushed the feat/claude-usage-improvements branch from 87f6338 to 9403117 Compare June 16, 2026 00:38
@jondmarien

Copy link
Copy Markdown
Author

Fixed...implemented a QGridLayout! Will make a final comment on this.

image

…nnable popup

Builds on the existing claude_usage widget; everything is off by default and
closes amnweb#967, amnweb#970 and amnweb#971.

- refresh action plus a header button that force an immediate fetch and skip
  cache_ttl, with in-flight dedup; a {stale} indicator that shows once the OAuth
  token has expired; and per-window reset wording (relative or absolute, with an
  optional date) that fixes the broken "Resets in Sat 6:00 AM" line
- local token-usage history: a Session/Today/Week/Month/Year section read from
  Claude Code's own transcripts under ~/.claude/projects, scanned off-thread and
  incrementally, with the cache bounded (hourly past 15 days and daily past ~13
  months are pruned on scan). No API key, no network call, content never read
- Claude API status dot driven by the public status.claude.com page (no auth),
  keeping the last known status on a failed poll
- per-model token breakdown in the Tokens section, following the same period
  toggle. Model names are derived from the id so new models need no upkeep, and
  fast-mode usage (usage.speed) is split into its own row
- pinnable, draggable popup matching the cpu/memory/gpu widgets, via a shared
  create_pin_button helper in stat_popup
- the popup resizes to fit the selected period instead of cramming or stretching
- docs and example CSS under docs/

Co-Authored-By: ManaphatDev <106569727+Gaer12TH@users.noreply.github.com>
@jondmarien jondmarien force-pushed the feat/claude-usage-improvements branch from 9403117 to 0022451 Compare June 16, 2026 00:55
@jondmarien

jondmarien commented Jun 16, 2026

Copy link
Copy Markdown
Author

Pushed the bar-alignment fix (still one commit). You were right that the bars came out different sizes (I also talked with @ManaphatDev about this); the cause was the name and total labels being content-width, so the stretchy bar column started and ended at a different x on each row. I moved the per-model rows into a QGridLayout: Qt sizes the name and total columns to their widest cell at layout time, so the bar column is identical on every row and the fills scale against the same track. No manual width math, so the font or popup width can't throw it off.

Caught one more while testing: a pinned popup snapped back to its spawn point on a refresh tick. PopupWidget.resizeEvent re-anchors to the bar on every resize, and this widget resizes when the model count changes between periods. PinnablePopup now keeps the dragged position while pinned (cpu/memory/gpu avoid this by never resizing on tick; claude_usage does, so it needed handling).

Verified locally across all five periods, on first open and after refresh, pinned and unpinned.

Final look:
image

@amnweb

amnweb commented Jun 16, 2026

Copy link
Copy Markdown
Owner

This should probably be done in CSS. Set a minimum width for the left and right labels, and let the progress bar stretch to fill the remaining space, it was more simpler...

May I ask what the reason was for creating a new create_pin_button function?

@jondmarien

Copy link
Copy Markdown
Author

This should probably be done in CSS. Set a minimum width for the left and right labels, and let the progress bar stretch to fill the remaining space, it was more simpler...

May I ask what the reason was for creating a new create_pin_button function?

sure, i can revert it. I had some issues when doing it with CSS:
image
But I'll try it again.

I created the create_pin_button function due to that being re-created multiple times over the codebase, as a helper. If you would like it to be removed, I have no issue doing so, just let me know.

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