Feature #1554
closedFeature #1511: Replace ticket view with Kanban board for CTO and assigned developers
API: per-category counts on the board payload, so the tab pills stop undercounting
0%
docs/superpowers/specs/2026-08-03-kanban-board-design.md
Description
**Surface:** backend (`api/`)
**Depends on:** #1542 (merged)
**Blocks:** the Feature/Bug/SecOps pill counts being correct in #1546
**Found during:** #1546 review
### The regression, and why this epic owns it
The ticketing screen's Feature / Bug / SecOps tab pills (#85) show a count per category. They are computed client-side by `tabCategoryCounts` over the board payload.
That worked while the route used `mode=list`, which returned every ticket. #1542 switched it to **board mode, capped at 50 per status** — so the pills now count only what survived the cap.
**Concrete failure:** a project with 60 tickets in Spec, 12 of them bugs, all ranked below the top 50. The **Bug pill reads 0** while twelve bug tickets exist. A client clicks Bug, sees an empty board, and concludes their bug reports were lost.
This is a regression introduced by this epic, not pre-existing — hence a child of #1511 rather than a parked follow-up. It carries no complexity score of its own; #1511 is pinned at 7/10 and already charged.
### Why it cannot be fixed in the frontend
The tickets are not in the response at all. No amount of client-side counting can recover them. #1546 has made the pills and the column headers derive from the same slice, so they at least never contradict each other on screen — but both are still wrong against the project's real totals.
### What to build
Return per-category totals on the board payload, computed server-side over the **whole** project before the per-status cap is applied — the same "count before you truncate" discipline the sort already follows.
Shape is yours to choose; the frontend needs Feature, Bug and SecOps totals for the project, honouring the caller's D2 visibility scope (a developer's pills must count only the tickets a developer can see). Note the existing convention: `tabCategoryCounts` excludes Shipped from the pill counts, since a shipped ticket is done and must not inflate what the client reads as open work — preserve that.
## Acceptance criteria
- [ ] The board payload carries per-category counts (feature / bug / secops) computed over the whole project, not over the capped slice.
- [ ] Counts respect the D2 visibility matrix: a developer's counts include only tickets they can see, a client's include the whole project, CTO/admin all — parametrised over every viewer kind.
- [ ] Shipped is excluded from the counts, matching the existing `tabCategoryCounts` behaviour (regression test).
- [ ] Test the exact failure above: 60 tickets in one status, 12 of them bugs ranked below the cap, and the bug count reads **12**, not 0.
- [ ] No new per-ticket call and no N+1 — a call/query-count assertion, as on the rest of this endpoint.
- [ ] A degraded or absent aggregator still yields a schema-valid 200 with the counts zeroed or null, never a 500.
- [ ] `src/features/tasks/types.ts`'s `tabCategoryCounts` is updated to read the server counts, and its stale client-side derivation is removed rather than left as a fallback that could silently disagree.
- [ ] Gates green: `python -m pytest -q` (**run serially**), `ruff check .`, `mypy src`; and the frontend gates for the consuming change.