Skip to content

projects: the panel ignores every filter and cannot show cost #102

Description

@pitimon

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

  1. 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.
  2. Add model to newly written project rows, moving the key to project|source|model|hour. Per-repo cost then computes through the existing computeRowCost.
  3. Rows written before the change price as 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.
  4. 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".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions