10 KiB
10 KiB
memory — claude-usage-widget
Durable memory for this project. Read at session start, update before session end. Date format: YYYY-MM-DD.
Decisions & rationale
- Tauri 2 (Rust + Svelte 5 + Vite + TS) over Electron — smaller binary (~10 MB vs ~150 MB), native Windows transparency, real always-on-top z-order. Chose Svelte over React because the widget has only three SVG primitives; React boilerplate isn't worth it.
- Inline SVG, no chart library —
BlockRingis one ring,WeeklyBaris two stacked progress bars,ModelStackis a single segmented bar. Adding Chart.js / ECharts / uPlot for ~80 lines of SVG would balloon the bundle for nothing. - Subscription %s come from PTY-driving
claude /usage(NOT JSONL estimates, NOT the Anthropic API). The widget spawnsclaudeviaportable-pty, sends/usage, parses the three rendered bars (Current session / Current week all / Current week Sonnet), and shows those exact numbers. This is the same data Anthropic shows you in the CLI — no API key, no admin scope, no reverse-engineering of their backend. The trade-off is ~3-5 s per refresh and brittleness if Anthropic changes the rendered output format. Refresh every 5 min by default; manual refresh button on the title bar. - Per-model breakdown still comes from local JSONL — the CLI's
/usagedoesn't break out Opus/Sonnet/Haiku, but our token-summing does. ModelStack remains. - Widget runs on Windows host, not in WSLg — needs to pin to the Windows desktop, autostart on login, and share the always-on-top z-order with native Windows apps. WSLg windows can't do that. JSONL transcripts read via
\\wsl$\<distro>\home\<user>\.claude\projects\UNC mount; the PTY-drivenclaudeis invoked viawsl.exe -d Ubuntu bash -lc claude(default on Windows when wsl.exe is on PATH). notifywatcher + 60s tokio poll fallback —ReadDirectoryChangesWon the WSL 9P mount is unreliable; the poll backstops it.- All filesystem reads happen Rust-side — the JS
capabilities/default.jsondoes NOT granttauri-plugin-fs. Keeps the webview sandbox tight. - Block algorithm (still in code, used for ModelStack only) —
block_start = floor_to_hour(first_ts_of_block),block_end = block_start + 5h, new block on ≥5h gap OR when previous block ends. ccusage-equivalent. - DROPPED: caps + tier-detection UI. Replaced by real CLI percentages. Caps struct still exists in code as a deprecated fallback but the Settings panel no longer exposes it.
Open questions / TODOs
- Watcher does not re-bind on settings change. If user changes WSL distro override in Settings,
set_settingscallsrefresh_and_emitand updatesstate.roots, but thenotifywatcher is still pinned to the old roots. v0 workaround: restart the widget after changing distro. /usageparser is fragile to output format changes. If Anthropic changes the rendered text (relabels sections, adds new ones, changes "X% used" pattern), the bars stop parsing silently. Settings panel exposes the raw output for debugging when this happens./usagespawn cost is ~3-5s on Windows. That's per refresh; default refresh is 300s so net overhead is fine. Title-bar refresh button gives user control. Consider caching to disk so cold start has something before the first PTY drive completes.- Autostart toggle fails in dev builds (target\debug\ exe path is unstable). Currently swallowed as a warning; needs proper testing once we ship the NSIS bundle.
- The default WSL-on-Windows command assumes Ubuntu. Auto-detect could iterate
wsl.exe -l -qfor any distro that hasclaudeon its login PATH, instead of hardcoding-d Ubuntu. - Decide whether to expose a tray icon for relaunch after
quit_app(currently the widget can only be reopened via Start Menu / autostart). - Window background is too transparent — files behind it bleed through visibly. Bump
--bgopacity from 0.78 to ~0.92. - Replace placeholder Tauri icons in
src-tauri/icons/before release (pnpm tauri icon source.png). - Caps struct + Caps::default() are dead code now — delete after a few releases of stability.
Session log
2026-05-08 / 2026-05-09
- Planned the project (approved plan at
~/.claude/plans/snug-mapping-milner.md) and built the full scaffold in one session that crossed midnight UTC. - Authored Tauri config (frameless, transparent, alwaysOnTop, skipTaskbar, 280×360), Cargo.toml, capabilities/default.json.
- Authored Rust modules:
state.rs,settings.rs,paths.rs,jsonl.rs,usage.rs(with unit tests for block boundaries),watch.rs,commands.rs,lib.rs,main.rs. - Authored Svelte 5 frontend:
App,TitleBar,BlockRing,WeeklyBar,ModelStack,Settingscomponents plusipc.ts,types.ts,format.ts,styles.css. - Wrote
scripts/seed-fake-jsonl.ps1verification helper andREADME.mdbuild instructions. - Pushed to Forgejo at
https://git.rdx4.com/megaproxy/claude-usage-widget. (Note: had to rename localmaster→mainafter the fact —git initran before the globalinit.defaultBranch=mainwas set.) - First successful run on Windows host (Doug's machine). Took 4 incremental fixes to get there:
- pnpm 11 default-deny on postinstall scripts → declared
pnpm.onlyBuiltDependencies: ["esbuild"]inpackage.json. - Missing icons → user generated placeholder via PowerShell
System.Drawing+pnpm tauri icon. notify::Watchertrait not in scope (E0599) andtokio::JoinHandle≠tauri::async_runtime::JoinHandle(E0308 ×2) inwatch.rs— fixed in commitab75ca9.tauri-plugin-autostartdoesn't accept a{"args":[…]}block intauri.conf.json— args go through the Rustinit()call only. Removed the JSON entry in commit8c25b01.
- pnpm 11 default-deny on postinstall scripts → declared
- Pivoted from cap estimation to real
/usagedata. First version showed 999% red ring because the placeholder caps (200k/2M) were wildly under what a Max user actually does. Tier-detection from.claude.jsonimproved defaults but still wasn't right; user pointed out they'd previously had an app showing real subscription %. Investigated and found:/usageslash command output isn't reachable viaclaude --print(LLM intercepts the literal string).- The data isn't cached anywhere on disk between invocations.
- The OAuth credentials at
~/.claude/.credentials.jsonwork, but reusing them to call an undocumented endpoint felt fragile. - Solution: PTY-drive
claudeitself. Newsrc-tauri/src/cli_usage.rsspawns claude viaportable-pty, sends/usage, parses the three rendered bars (Current session / Current week all / Current week Sonnet). 5-min refresh + manual button. ~3-5s per fetch. (commitdb9a10a)
- Bring-up gotchas while wiring the PTY drive on Windows:
- Settings UI was non-interactive earlier because of two compounding bugs — a) Save was silently failing because the autostart plugin threw "OS error 2" in dev builds (target\debug exe path is unstable) and we let it abort the save; b) my PowerShell
mouse_eventclicks weren't reaching WebView2 (legacy API on a transparent borderless host), making me think the user's clicks were broken too. Real-mouse clicks worked once Save stopped getting blocked. Fix: best-effort autostart toggle (commit9786437). paths::resolve_rootswas canonicalizing UNC paths to\\?\UNC\…form, which brokePath::parent()and made tier detection silently fail. Stopped canonicalizing (commitc5c38d1).- Default
wsl.exe -- claudeinvokes a non-interactive non-login shell with no PATH; resolved by defaulting towsl.exe -d Ubuntu bash -lc claudeon Windows when wsl.exe is detected (commit7504990). The-d Ubuntumatters because user's default WSL distro wasdocker-desktop(Alpine; no claude, no bash). - Title bar buttons were inside
data-tauri-drag-region; needed explicitdata-tauri-drag-region="false"per button so clicks don't get interpreted as drag-start.
- Settings UI was non-interactive earlier because of two compounding bugs — a) Save was silently failing because the autostart plugin threw "OS error 2" in dev builds (target\debug exe path is unstable) and we let it abort the save; b) my PowerShell
- Final UX polish (this session): widget made resizable (220×240 min, 300×320 default), inline-SVG ring scales via viewBox, background opacity bumped to 93% so files behind don't bleed through, scrollbar bug from
border + width: 100vwoverflow killed viabox-sizing: border-boxreset +body { overflow: hidden }(commitsf90bb3b,c38d895). - Status: widget is live on Windows showing real subscription percentages (72% session at end of session). 18 commits on
main, all pushed. User is happy.
2026-05-09 — v0.1.0 release
- Polish pass for shipping to friends:
- Robust claude command auto-detect: tries native
claude, then enumerates WSL distros viawsl.exe -l -qand probes each viabash -lc 'command -v claude'. No more hardcoded-d Ubuntu. - Clear empty-state UI when no claude is reachable (Anthropic install link).
- Real icon:
scripts/make-icon.pyproduces a 1024×1024 ring-on-dark monogram. Pillow's multi-res ICO writer was flaky so user re-ranpnpm tauri iconto regenerate proper multi-resolution icons. - System tray icon (left-click = restore window, right-click menu Show/Hide/Refresh/Quit). Solves "lost off-screen" recovery.
- End-user-focused README; build instructions moved to bottom.
- Robust claude command auto-detect: tries native
pnpm tauri buildproducedClaude Usage Widget_0.1.0_x64-setup.exe(1.7 MB NSIS user-scope installer).- Created Forgejo release v0.1.0 with the .exe attached.
- Repo flipped to public (per user's explicit request via AskUserQuestion + by trying to do it themselves) so anonymous downloads work.
- Public download URL: https://git.rdx4.com/megaproxy/claude-usage-widget/releases/tag/v0.1.0
- Toolchain (rust/node/pnpm) NOT installed in this WSL environment — that's expected; the build runs on the Windows host.
cargo check/pnpm installnot run from here.
External references
- Forgejo repo: https://git.rdx4.com/megaproxy/claude-usage-widget
- Approved plan:
/home/megaproxy/.claude/plans/snug-mapping-milner.md - Tauri 2 prerequisites: https://v2.tauri.app/start/prerequisites/
- Tauri 2 window customization: https://v2.tauri.app/learn/window-customization/
- Tauri 2 autostart plugin: https://v2.tauri.app/plugin/autostart/
- Tauri 2 capabilities: https://v2.tauri.app/security/capabilities/
- ccusage (algorithm reference): https://github.com/ryoppippi/ccusage
- Claude Max weekly reset issues (context for "rolling 7d" choice): https://github.com/anthropics/claude-code/issues/54974 · https://github.com/anthropics/claude-code/issues/52921