0053 — PR dashboard: every PR you own, on one page #133

Merged
chad merged 12 commits from feat/0053-pr-dashboard into main 2026-10-19 23:14:49 -04:00
Collaborator

Implements docs/0053-pr-dashboard.md (approved in chat): a /prs page listing every PR by the repo's own credential account across all project repos — hand-opened and agent-opened alike — with live status and review signals, replacing the browser "PRs" folder habit.

  • GitHost.listPRs seam (GitHub + Forgejo): PRSummary[] | null, never throws. Open PRs by the account carry an aggregated review verdict (aggregateReviews: latest review per reviewer, then changes-requested > approved > commented); review fetches bounded to 25 open PRs/repo. All API shapes verified live against git.crussell.io, Codeberg, gitea.com, and api.github.com by a fresh-eyes review pass (which caught three wrong Forgejo field names before merge); fixtures pinned to the real shapes.
  • Loader lib/prs/dashboard.ts: walks project_repos, resolves credentials, filters by author case-insensitively, dedupes by canonical URL, sorts open first (changes-requested → commented → activity) then settled (merged before closed, dimmed); un-askable repos are a red note, never fatal.
  • /prs page + dashboard UI: open section, settled section (cap 10 + reachable show-more/fewer toggle), repo badge, branch chip, review badges, relative last-activity. Fetch is live on page load — nothing stored, no polling (the 0052 philosophy).
  • Sidebar: PRs nav row between Inbox and Projects.
  • Forgejo getPRState (0052) also corrected: merged is a boolean with the timestamp at merged_at (it previously worked by accidental truthiness).
  • Known limitation (documented): GitHub pull LIST rows carry no comments field, so the comment-count badge shows only on Forgejo.

Design doc, AGENTS.md bullet, and tests included. Gate: typecheck clean, lint 0 errors, 849 tests passing.

Implements docs/0053-pr-dashboard.md (approved in chat): a `/prs` page listing every PR by the repo's own credential account across all project repos — hand-opened and agent-opened alike — with live status and review signals, replacing the browser "PRs" folder habit. - **`GitHost.listPRs` seam** (GitHub + Forgejo): `PRSummary[] | null`, never throws. Open PRs by the account carry an aggregated review verdict (`aggregateReviews`: latest review per reviewer, then changes-requested > approved > commented); review fetches bounded to 25 open PRs/repo. All API shapes verified live against git.crussell.io, Codeberg, gitea.com, and api.github.com by a fresh-eyes review pass (which caught three wrong Forgejo field names before merge); fixtures pinned to the real shapes. - **Loader** `lib/prs/dashboard.ts`: walks project_repos, resolves credentials, filters by author case-insensitively, dedupes by canonical URL, sorts open first (changes-requested → commented → activity) then settled (merged before closed, dimmed); un-askable repos are a red note, never fatal. - **`/prs` page + dashboard UI**: open section, settled section (cap 10 + reachable show-more/fewer toggle), repo badge, branch chip, review badges, relative last-activity. Fetch is live on page load — nothing stored, no polling (the 0052 philosophy). - **Sidebar**: PRs nav row between Inbox and Projects. - Forgejo `getPRState` (0052) also corrected: `merged` is a boolean with the timestamp at `merged_at` (it previously worked by accidental truthiness). - Known limitation (documented): GitHub pull LIST rows carry no `comments` field, so the comment-count badge shows only on Forgejo. Design doc, AGENTS.md bullet, and tests included. Gate: typecheck clean, lint 0 errors, 849 tests passing.
glen added 12 commits 2026-10-19 23:14:36 -04:00
0053: GitHost gains listPRs(credential, remoteUrl, fetchImpl?) ->
PRSummary[] | null (null when the host can't be asked, never throws,
mirroring getPRState). One shared driver (listPRsWithReviews) lists a
repo's PRs and aggregates review verdicts for OPEN PRs by the
credential's account — latest review per reviewer, then
changes-requested > approved > commented (aggregateReviews, pure) —
bounded to the first 25 per repo. GitHub: /pulls?state=all&per_page=50
+ per-PR /reviews (user.login, state, submitted_at). Forgejo:
/api/v1/repos/…/pulls?state=all&limit=50 + /reviews (reviewer.login,
state APPROVED/REQUEST_CHANGES/COMMENT, dismissed rows dropped).
0053: loadPRDashboard(db, listPRs?) walks project_repos, resolves each
repo's host-scoped credential (the open-pr rule), asks listPRs, filters
to PRs authored by that credential's username (case-insensitive — host
logins are), dedupes by canonical URL (the same repo in two projects is
one row), and sorts: open first (changes-requested, then commented,
then last activity), settled after (merged before closed, newest
first). A repo with no credential, a host that answers null, or a
thrown answer lands in failedRepos — reported, never fatal. View
shapes live in lib/read-models/prs.ts (0049: components never touch
@/db); the host seam is injectable for tests.
0053: a force-dynamic server page awaits loadPRDashboard and renders
the client dashboard (no API route — page load is the fetch, refresh
re-asks, nothing stored). Rows carry the lifecycle icon
(approvals.tsx's PR vocabulary, token-toned), the title as an external
link, a repo badge derived from the PR URL, the head branch chip, the
review badge (changes requested warn / approved ok / N comments
neutral) and LocalTime's relative last activity. Settled rows dim,
cap at 10 behind a +N more toggle; failed repos render as a note, the
rest of the page still draws. Semantic tokens only (DESIGN.md).
0053: NavPage gains "prs" (SEGMENT maps the /prs top-level segment)
and the sidebar adds the NavRow — GitPullRequest icon, no badge (a
badge needs a background poll; the page itself is the check).
Fresh-eyes review of 0053 verified the API shapes against live hosts
(git.crussell.io, codeberg.org, gitea.com) and found the forgejo
adapter parsing three fields that don't exist:

- pulls carry the author at user.login — 'poster' is the Issue shape;
  with it every forgejo row parsed to null and the dashboard showed
  zero PRs on the primary host, silently.
- 'merged' is a BOOLEAN (x-go-name HasMerged); the timestamp is
  merged_at. merged detection is now merged === true OR a string
  merged_at; before, the boolean failed strField and every merged PR
  rendered closed. getPRState (which worked by accidental truthiness
  and claimed 'merged' was a timestamp) uses the same detection and
  merged_at for mergedAt; comments corrected.
- reviews carry the reviewer at user.login, not 'reviewer' — all
  forgejo verdicts were dead (aggregateReviews never saw a row).

Test fixtures re-pinned against the real shapes: user.login (not
poster/reviewer), merged boolean + merged_at (getPRState's
merged-without-timestamp case included), and explicit open-row
(merged:false + merged_at:null) mapping.
Live-verified in the 0053 review: GET /repos/{o}/{r}/pulls list rows
have no 'comments' (the field exists on the single-PR GET / issues
API only), so GitHub commentCount stays null — PRSummary already
allows null and the badge renders only when known. Code comment says
so; the spec's API-shape notes are corrected (GitHub comments,
Forgejo user.login author, boolean merged + merged_at, reviewer at
user.login) with an implementation note recording the live
verification, and the AGENTS.md 0053 bullet follows.
Review findings 5 and 7. The settled toggle gated on 'more !== 0',
but more is exactly 0 once expanded — Show fewer was unreachable, so
expanding was one-way. The button now renders whenever settled.length
exceeds the cap, labeled +N more collapsed / Show fewer expanded.

sortPRs gets a url localeCompare tiebreak on both sorts: equal-
updatedAt rows arrived in Promise.all completion order (Map
insertion), so they could swap between loads. The loader's ordering
test pins the tie with a same-activity PR pair.
test(prs-route): forgejo single-PR stub to the live shape
All checks were successful
deploy / checks (pull_request) Successful in 42s
deploy / deploy (pull_request) Has been skipped
ff4b2d2ba3
The 0052 route test stubbed forgejo's merged flag as a string
timestamp — the fictional shape the 0053 review disproved (merged is
a boolean; merged_at carries the time). The corrected getPRState
rightly stopped treating a string merged as merged, so the fixture
now uses merged: true + merged_at, still asserting merged wins over
state: open.
chad merged commit dfb14c9f78 into main 2026-10-19 23:14:49 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
chad/sweatshop-legacy!133
No description provided.