4 months ago
bbf5479The flat feed showed one row per event, but the real unit is the change request, not the event. A PR evaluated 15 times is one PR, not 15 rows. Regroup the feed by change request. - listActivityFeed (db/services) groups by (repo, subject_number) IN SQL: a CTE picks the top-N change requests by latest activity, a second join pulls each group's chronological timeline (events + runs + the leading reason). Standalone events (installation/push) fetched separately, interleaved by latest activity. Grouping in the query, never client-side over a paged list. - One group = one collapsible row, collapsed by default. Header carries number, title, actor, repo, current-verdict chip, count, time. Expanded is the PR's timeline; each entry links to /runs/$runId or the event's html_url. - Grouped live merge (activity.query.ts): a new event upserts into its group and bumps it to the top without growing the list; a run resolves in place and re-derives the current verdict. Same SSE plumbing, no polling. - Filter chips filter GROUPS by current verdict. Shared VerdictChip. Checks: biome scoped clean, typecheck (web+db) 0, boundaries ok, 196 tests.
Parentc958bee