Shipped and the cost per shipped change sit beside Runs and Spent, and they are sampled the same way.
studioApp (src/web/studio/app.ts:101-104) counts them as listPulls(db, repo.id, { state: "closed", limit: 100 }).filter(p => p.mergedAt !== null && p.mergedAt >= week).length. Past a hundred closed pulls — merged or abandoned, they share the page — the week's shipping under-reports, and the per-change cost that divides spend by it reads high with nothing saying why.
This is the second of the four issues #91 was split into. It waits on the runs aggregate so the two land in one panel without fighting over the same lines.
Acceptance
[ ] A new aggregate in src/core/pulls.ts — one query — counts pulls of one repository with merged_at at or after a given ISO instant.
[ ] studioApp uses it for Shipped and for the cost of a shipped change; the limit: 100 scan is gone from that path.
[ ] A test seeds 150 closed pulls inside the window, plus a closed-unmerged one and a merged one older than the window, and asserts Shipped counts exactly the 150 — it fails on today's code.
Closed is not shipped: a pull closed without a merge has no merged_at and must not be counted. Cost per shipped change divides by a count that can be zero — keep whatever the page does today for that case, and let a test hold it.
Shipped and the cost per shipped change sit beside Runs and Spent, and they are sampled the same way.
studioApp(src/web/studio/app.ts:101-104) counts them aslistPulls(db, repo.id, { state: "closed", limit: 100 }).filter(p => p.mergedAt !== null && p.mergedAt >= week).length. Past a hundred closed pulls — merged or abandoned, they share the page — the week's shipping under-reports, and the per-change cost that divides spend by it reads high with nothing saying why.This is the second of the four issues #91 was split into. It waits on the runs aggregate so the two land in one panel without fighting over the same lines.
Acceptance
src/core/pulls.ts— one query — counts pulls of one repository withmerged_atat or after a given ISO instant.studioAppuses it for Shipped and for the cost of a shipped change; thelimit: 100scan is gone from that path.bun testgreen.Files I expect to change
src/core/pulls.ts,src/web/studio/app.ts,test/studio.test.ts.Risks
Closed is not shipped: a pull closed without a merge has no
merged_atand must not be counted. Cost per shipped change divides by a count that can be zero — keep whatever the page does today for that case, and let a test hold it.Suggested: Mason.