Skip to content

Per-chat context-window monitoring + four on-device view modes - #129

Closed
grabbly wants to merge 4 commits into
HermannBjorgvin:mainfrom
grabbly:feat/context-window-reporting
Closed

Per-chat context-window monitoring + four on-device view modes#129
grabbly wants to merge 4 commits into
HermannBjorgvin:mainfrom
grabbly:feat/context-window-reporting

Conversation

@grabbly

@grabbly grabbly commented Jul 25, 2026

Copy link
Copy Markdown

This PR adds live context-window monitoring of your local Claude Code chats to Clawdmeter, plus a set of new on-device view modes to show it. Everything is verified on real hardware (AMOLED-2.16) and all six build envs compile.

What it does

The daemon scans <config_dir>/projects/*/*.jsonl transcript tails and computes each chat's context fill from the last main-chain assistant message's usage (input + cache_read + cache_creation). Chats stay listed while active in the last 15 minutes; the most recent chat is always kept. The payload gains "cc":[{n,p,k,l,a}, …] (project name, percent, tokens-used in k, window size in k, minutes since last activity), pushed within ~5 s of any change — much faster than the 60 s API poll, so the gauge moves while you work.

Four view modes (tap or top buttons to cycle, choice persists in NVS)

Focus Chats Classic Rings
focus chats classic rings
  • Focus — slim 5h/7d rows + a big context card for the current chat, with colored dots warning about background chats filling up.
  • Chats — every active chat on one screen with fill bars, token counts, and age.
  • Classic — the original two panels, plus a slim context strip at the bottom.
  • Rings — a watch-face of concentric context / 5h / 7d rings.

Also in here

  • Token-activity screensaver: the panel turns off after 10 min without token movement (clock/reset ticks don't count) and wakes by itself the moment tokens flow again — or on tap/button. Works on battery and USB.
  • BLE MTU 517 (NimBLEDevice::setMTU) so the larger payload fits one write-without-response; the daemon also trims trailing cc entries to the negotiated MTU, so it stays compatible with older firmware (which simply ignores "cc"). Compatibility holds both ways: new firmware with an old daemon just shows the empty context states.
  • macOS daemon token fix: a ~/.claude/.credentials.json that contains only mcpOAuth entries (no Claude OAuth token) no longer shadows the Keychain fallback, and the regex miner can no longer pick up an unrelated MCP server's token. This bit me on a fresh machine; happy to split it into its own PR if you prefer.
  • Window size detection: transcripts don't record the context-window size, so by default the daemon guesses conservatively (200k, [1m] marker → 1M, snap up when usage exceeds the guess). Users whose plan runs a known window on every chat can pin it in the config: context_window_k = 1000.

Breaking changes to discuss

I made two opinionated choices that change existing behavior — flagging them explicitly so you can push back:

  1. The top buttons now switch view modes instead of sending HID Space / Shift+Tab. The HID keyboard service itself is untouched (pairing/bond behavior identical). If you want the keys back, I'd suggest a config/long-press scheme — happy to implement whichever you prefer.
  2. Tap cycles modes instead of toggling the splash; the splash is boot-only now (it auto-exits when the first payload lands). The idle sleep is also token-driven rather than touch-driven, and sleeps on USB power too.

Testing

  • Daemon: unit-style checks for the scanner (active-window filter, keep-newest fallback, age field, config override paths) + live runs on macOS (launchd).
  • Firmware: every mode iterated against real hardware via the screenshot serial command (the PNGs above are actual device captures); empty states, enterprise variants, and the pair/idle overlays covered; all six PlatformIO envs build clean.

🤖 Generated with Claude Code

grabbly and others added 4 commits July 25, 2026 18:54
- read_token_for: a credentials file that yields no usable token (e.g. one
  holding only mcpOAuth entries) no longer shadows the macOS Keychain
  fallback; the regex miner only runs on non-JSON blobs so it can't grab
  an unrelated MCP server's token.
- Scan <config_dir>/projects/*/*.jsonl transcript tails for each chat's
  context-window fill (last main-chain assistant usage) and ship it as
  "cc":[{n,p,k,l,a}] in the BLE payload; cc-only updates push every TICK
  when content changes, between the 60s API polls.
- Chats count as active for 15 min after their last token movement; the
  most recently active chat is always reported. Window size is fixed at
  CONTEXT_WINDOW_TOKENS (1M) — transcripts carry no window-size info.
- Payloads trim trailing cc entries to fit the negotiated BLE MTU so
  writes never fail on firmware with the default 255-byte MTU.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…TU 517

- Four cycling view modes replace the single usage screen: Focus (slim 5h/7d
  rows + big current-chat context card with background-chat leak dots),
  Chats (all active chats on one screen with age lines), Classic (the
  original panels + a bottom context strip), Rings (concentric ctx/5h/7d
  watch face). Splash is boot-only and auto-exits on first data.
- Navigation: tap = next mode; top buttons = prev/next (HID keystrokes
  removed — the HID service itself stays for pairing compatibility). The
  active mode persists in NVS. Swipe navigation removed.
- Screensaver driven by token movement instead of touch: no change in
  session/weekly/context numbers for 10 min turns the panel off (USB
  included); it wakes on new tokens, tap, or any button.
- parse_json reads per-chat "a" (minutes since last activity); ChatCtx
  gains age_min.
- NimBLE negotiates MTU 517 so full 4-chat payloads fit a single
  write-without-response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CONTEXT_WINDOW_TOKENS was a hardcoded 1M constant that only fit the
original author's all-1M-window setup; upstream users on standard
200k windows would have every chat mis-reported as 1M.

Default (upstream, context_window_k unset): per-chat heuristic, since
transcripts don't record the actual window size. Assume 1M if the
assistant message's "model" string carries a "[1m]" marker, else 200k;
if usage ever exceeds that guess, snap the assumed limit up to 1M (and
beyond, to the next 1M multiple) so the gauge keeps tracking progress
instead of pegging at 100% on a wrong guess.

Override (context_window_k = N in the config file, thousands of
tokens): every chat's limit is N * 1000, no guessing or snap-up — a
user-declared window is taken as fact. E.g. context_window_k = 1000
reproduces the old hardcoded 1M-everywhere behavior.

Added read_config_value(key) as a shared single-key reader for the
config file (key = value, # comments), and refactored read_config_dirs,
read_chime_setting, and read_clock_setting to use it instead of each
duplicating the same parsing loop. context_window_k is re-read once per
scan_chat_contexts() call so it takes effect without a daemon restart;
the per-file context cache now keys on (mtime, context_window_k) so a
config change invalidates cached entries for unchanged transcripts too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@HermannBjorgvin

Copy link
Copy Markdown
Owner

"Token-activity screensaver" already an existing functionality which turns the screen off after 30m

The changes in the controls are not ideal. Suggest you use the touch screen tap to cycle between screens instead, or a swipe gesture or something other than taking over the useful physical button controls for something cosmetic like the screen selection.

Please keep any bugfix in a separate PR

Don't worry about backwards compatibility, there is no auto update for the daemon we assume everyone using the platform has the latest firmware and daemon for the time being at least.

@HermannBjorgvin

HermannBjorgvin commented Jul 26, 2026

Copy link
Copy Markdown
Owner

I appreciate the UX concepts but I'd like to challenge you to think of the context for each of them and if we can make the experience more seamless for users

For example you have a multi chat UX, we know how many chats a user has active so perhaps we don't need to show that screen unless we see more than one chat that is active, defined by some 30m window perhaps

The idea to show more info on the current session is something I like but it needs to be limited to one screen or possibly two screens if we want to show the state of multiple chats.

Idk how old your fork is btw but the latest firmware also shows the current time instead of "Clawdmeter". I'd like to avoid displaying "Clawdmeter" anywhere in the UX since it just detracts from the experience of using it in my opinion and doesn't add value.

Try ditching the concentric rings screen, focus on the single/multi chat view where we show the current session limits and the chat contexts and auto switch between the single and multi view depending on how many chats are active.

Scenario idea

The classic view remains the default

User starts a chat and we switch to displaying info on that session

User starts another chat and we switch to the multiple chat view

The user pauses and the chats remain inactive for ~10m so we switch back to the classic view.

Normally with pull requests this large it's hard to see the value in them since they contain so many things but I appreciated your UX concepts a lot and I think this is close to being workable as a new feature.

Thanks for opening the PR!

@HermannBjorgvin

Copy link
Copy Markdown
Owner

Closing in favor of #136 which consolidates this and a couple of other pull requests and feature requests I've gotten on this topic. Would appreciate any help on testing that one since it's a large change so any help validating it would be appreciated.

HermannBjorgvin added a commit that referenced this pull request Aug 1, 2026
The 5h/7d strip uses #129's measured layout — styrene_24 percentages
in fixed right-aligned columns, styrene_20 dim tags, a 30px vertically
centered band — and its vertical rhythm: list top at 148, 10px card
gaps (90px pitch, keeping the mid-card clip affordance). Card
internals unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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