Repository navigation
Description
Follow-up to #152, which shipped the current view (range presets, cost/tokens toggle, daily bars, provider rows, model table). Two data defects make the numbers untrustworthy today, and the view itself has room to become the surface that actually answers "where did the money go".
1. Providers that report no usage vanish entirely
electron/store.js:1359:
if (costUsd === 0 && inputTokens === 0 && outputTokens === 0) return;Any turn that reports neither cost nor tokens is dropped on the floor, so the provider never appears in the view at all. Read-only scan of the live coder-store.json today:
claude/claude-opus-5 $229.34 in 3,001 out 438,289 turns 46
grok/grok-4.6 $69.67 in 23,615,038 out 2,973,407 turns 141
--- threads by provider ---
{ claude: 167, grok: 231, kimi: 41 }
41 kimi threads, zero kimi rows. The runner does call recordUsage on the kimi path (electron/runner.js:3629) — and its own comment two lines up says kimi "reports billable in/out at best, never a full prompt" (#317) — so the turns arrive with zeroes and get discarded.
Silently dropping is the wrong default. The view should show kimi · 41 turns · usage not reported rather than pretend the provider does not exist, because "this provider costs nothing" and "this provider does not tell us what it costs" are completely different facts and only one of them is good news. Record the turn whenever a provider is named; keep zeros as zeros and render them as unreported, not as $0.00.
2. Claude's token counts are undercounted by ~1000x
Same scan: 3,001 input tokens across 46 Claude Opus turns. That is not plausible — it is one prompt's worth spread over a month of work.
electron/runner.js:2768 records usage.input_tokens only. Under prompt caching, input_tokens counts just the uncached remainder; the real prompt is input_tokens + cache_read_input_tokens + cache_creation_input_tokens. The codebase already knows this — claudeContextTokens() at runner.js:85-103 sums exactly those three fields for the context ring, with a comment explaining that omitting them "reads as ~0% then jumps (#317)". The usage rollup never got the same fix.
Consequence: the Tokens toggle is meaningless across providers. Grok reports full input and shows 23.6M; Claude reports post-cache and shows 3K. Comparing them ranks a cache-heavy provider as ~7,800x cheaper on tokens than it is.
Keeping input_tokens as the billable number is defensible; reporting it as the token count is not. The fix is to record the split rather than pick one:
inputTokens // uncached, billable at full rate (today's number)
cachedInputTokens // cache_read_input_tokens
cacheWriteTokens // cache_creation_input_tokens
outputTokens
UsageEntry in src/shared/ipc.ts and normalizeUsageByDay need the two new fields, defaulting to 0 so existing store data still loads.
3. What the view should become
Reference (T3 Code's Usage screen, not to be copied 1:1):
- A single headline figure with an honest caveat — "$12,739.09 * if billed at full API rate" — because almost everyone here is on a subscription and the raw-rate number is a counterfactual, not a bill.
- Provider rows with share bars underneath the headline.
- One overlaid daily area chart per provider with a legend, instead of a single merged bar per day.
- A stat strip: processed tokens · cached input · uncached input · output · cache savings ("6.8x the raw token cost").
- Breakdown table with a Model / Day toggle, sorted by cost, with share and tokens columns.
What Solenta should do differently, given it knows things a single-vendor tool cannot:
- Break down by project and thread, not just model. Make the table's toggle
Model · Day · Project · Thread. This is the multi-provider tool's actual advantage, and it is the direct input to Per-client cost attribution with invoice-ready export #403's per-client attribution and Work receipts: per-thread claim-vs-proven split and plan-quota cost #339's work receipts — that issue wants exactly this rollup with a client label on top. - A wasted-spend column. Solenta knows which runs failed, were stopped, or were superseded by a rerun. "$X on runs that ended in
failed/stopped" is a number nobody else can show, and it is the one most likely to change behaviour. Ties to Thrash detector: interrupt same-error retry loops with rollback and a provider switch #391 (thrash detector) and Failure taxonomy: classify before retry, quarantine flakes #297 (failure taxonomy). - Subscription reality first. Where the CLI reports plan quota (Claude's usage ring, plan usage for the period), show quota consumed next to the counterfactual dollar figure. Dollars are the wrong unit for the provider you cannot exceed but can exhaust.
- Be explicit about unreported providers rather than letting them read as free — the whole point of item 1.
- Cache savings only where it is real. The 6.8x line is meaningful for Claude and meaningless for a provider that reports no cache fields; render it per-provider or not at all, never as a single global multiplier.
Deliberately not adopting: the exact typography and colour treatment, and the 24h range preset (the current 7/30/90 is fine; note the 90-day view will look sparse for a while — SPEND_RETENTION_DAYS = 90 is correct, there is simply only three days of history in the store so far).
Tests
test/usage.test.ts(or whereversummarizeUsageis covered): a provider whose entries are all-zero still appears inproviderswith its turn count, and is flagged unreported.electron/test/store.test.js:recordUsagewith{provider:"kimi", costUsd:0, inputTokens:0, outputTokens:0}persists a row withturns: 1.- A rollup case with
cachedInputTokens/cacheWriteTokensset, asserting processed-vs-billable totals differ and old entries without the fields still load.
Related
#152 (the view this extends), #317 (the cache-token issue this is the sibling of), #403, #339, #401, #405, #323.
Activity
usage: note the origin/main merge in the review itinerary (#556)
currentbits commentedon Aug 20, 2026
Shipped on main (78fe3bf); verified in the tree. Moving to plan:done.
usage: match unreported copy and add the #556 review itinerary