You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Projects panel is where the README's central argument lands, and it is the least finished surface in the product.
Verified
The client sends filters. The server reads none of them.
dashboard/src/lib/api.ts:90-109 builds from, to, source, limit, timeZone into the request. The handler at src/lib/local-api.js:1232-1305 reads project.queue.jsonl and aggregates it unconditionally — awk over the whole handler finds zero references to searchParams or url..
Concrete failure: pick "24h". Every other card narrows. Projects keeps showing all-time totals, with nothing on screen saying so. A user comparing "this week's spend by repo" is reading lifetime numbers.
There is no cost, and none can be computed. The handler accumulates total_tokens and billable_total_tokens only. Project rows are keyed project|source|hour (src/lib/rollout.js:1596-1611) and carry no model — and cost is per-model. So per-repo cost is not merely unimplemented, it is absent from the data model.
README:33 says the product answers "which repo, which model, and which hour". Hour resolution is stored on every project row and thrown away at the API. The repo×model join has never existed.
Coverage is partial.projectBucketsQueued appears in 7 parsers (codex, claude, gemini, opencode ×2, openclaw, antigravity). Cursor, Copilot, Zed, Goose and Kiro usage is invisible to the per-repo story entirely — and the panel does not say which sources it can account for.
Proposal
Honour the filters.from/to against the stored hour_start in the request timezone, plus source and limit. Pure surfacing — the data is already on disk.
Add model to newly written project rows, moving the key to project|source|model|hour. Per-repo cost then computes through the existing computeRowCost.
Say what is not counted. If a source has no project attribution, the panel should name it rather than let its absence read as "that tool cost nothing here".
The risk to get right
Mixed-era rows. Legacy project|source|hour and new project|source|model|hour describe the same bucket at different granularity; summing both double-counts. Needs one explicit keep-last rule and a test over a fixture containing both eras — the same shape as the migration in sync.js:1836.
Out of scope
Backfilling model onto historical project rows. The source logs may no longer exist, and unattributed is the honest rendering of "we recorded this before we recorded models".
The Projects panel is where the README's central argument lands, and it is the least finished surface in the product.
Verified
The client sends filters. The server reads none of them.
dashboard/src/lib/api.ts:90-109buildsfrom,to,source,limit,timeZoneinto the request. The handler atsrc/lib/local-api.js:1232-1305readsproject.queue.jsonland aggregates it unconditionally —awkover the whole handler finds zero references tosearchParamsorurl..Concrete failure: pick "24h". Every other card narrows. Projects keeps showing all-time totals, with nothing on screen saying so. A user comparing "this week's spend by repo" is reading lifetime numbers.
There is no cost, and none can be computed. The handler accumulates
total_tokensandbillable_total_tokensonly. Project rows are keyedproject|source|hour(src/lib/rollout.js:1596-1611) and carry nomodel— and cost is per-model. So per-repo cost is not merely unimplemented, it is absent from the data model.README:33 says the product answers "which repo, which model, and which hour". Hour resolution is stored on every project row and thrown away at the API. The repo×model join has never existed.
Coverage is partial.
projectBucketsQueuedappears in 7 parsers (codex, claude, gemini, opencode ×2, openclaw, antigravity). Cursor, Copilot, Zed, Goose and Kiro usage is invisible to the per-repo story entirely — and the panel does not say which sources it can account for.Proposal
from/toagainst the storedhour_startin the request timezone, plussourceandlimit. Pure surfacing — the data is already on disk.modelto newly written project rows, moving the key toproject|source|model|hour. Per-repo cost then computes through the existingcomputeRowCost.unattributed— the tier added in fix(pricing): stop reporting the "unknown" placeholder as an unpriced model #94 handles exactly this and renders honestly rather than as a silent $0.The risk to get right
Mixed-era rows. Legacy
project|source|hourand newproject|source|model|hourdescribe the same bucket at different granularity; summing both double-counts. Needs one explicit keep-last rule and a test over a fixture containing both eras — the same shape as the migration insync.js:1836.Out of scope
Backfilling
modelonto historical project rows. The source logs may no longer exist, andunattributedis the honest rendering of "we recorded this before we recorded models".