feat: add the two-tier dashboard (task 0017)
All checks were successful
CI / test (pull_request) Successful in 47s
CI / test (push) Successful in 50s

Wire the bare `gitea-axi` home command to a two-tier repository
dashboard (ADR 0012): the short tier shows up to 3 open issues and 3
open PRs with the client-side `review` decision, and `--full` shows the
20-row open-PR table plus open issue counts grouped by label, aggregated
across every page of open issues up to the 1000-issue cap with a `+`
suffix when capped. Empty states render the raw `prs: 0 open` /
`issues: 0 open` strings; issue fetches pass `type=issues`; outside a
Gitea repo the dashboard errors with REPO_NOT_FOUND.

`paginate.ts` now reports whether pagination stopped at the cap, which
drives the label-count `+` suffix.
This commit was merged in pull request #18.
This commit is contained in:
2026-07-14 11:01:07 -04:00
parent c2c5f1728c
commit 5b6a9b4917
7 changed files with 705 additions and 27 deletions

View File

@@ -13,11 +13,31 @@ Issue fetching passes `type=issues`; outside a recognizable Gitea repo the dashb
## Acceptance criteria
- [ ] Bare `gitea-axi` renders the header, `repo:` line, up to 3 issues and 3 PRs with the specified fields (including the computed `review`), and a `help:` block hinting at `--full`
- [ ] `gitea-axi --full` renders the PR table capped at 20 rows with `count: 20 of T total` and issue counts grouped by label
- [ ] Label aggregation paginates to the 1000-issue cap, suffixes counts with `+` when capped, counts each issue under all its labels, and shows `unlabeled` only when nonzero
- [ ] Empty states render `prs: 0 open` / `issues: 0 open` as raw strings
- [ ] Issue fetches pass `type=issues` so PRs never appear in the issue block
- [ ] Outside a Gitea repo the dashboard exits with `REPO_NOT_FOUND` and help mentioning `-R` and `--login`
- [ ] Fixture-server tests cover both tiers, the cap-and-suffix behavior, empty states, and the no-repo error
- [ ] End-to-end tests render the bare dashboard and `--full` against a live Gitea instance, confirming the live issue/PR list response shapes and that the computed `review` and label-aggregation fields hold against real responses — behavior the fixture server cannot attest to
- [x] Bare `gitea-axi` renders the header, `repo:` line, up to 3 issues and 3 PRs with the specified fields (including the computed `review`), and a `help:` block hinting at `--full`
- [x] `gitea-axi --full` renders the PR table capped at 20 rows with `count: 20 of T total` and issue counts grouped by label
- [x] Label aggregation paginates to the 1000-issue cap, suffixes counts with `+` when capped, counts each issue under all its labels, and shows `unlabeled` only when nonzero
- [x] Empty states render `prs: 0 open` / `issues: 0 open` as raw strings
- [x] Issue fetches pass `type=issues` so PRs never appear in the issue block
- [x] Outside a Gitea repo the dashboard exits with `REPO_NOT_FOUND` and help mentioning `-R` and `--login`
- [x] Fixture-server tests cover both tiers, the cap-and-suffix behavior, empty states, and the no-repo error
- [x] End-to-end tests render the bare dashboard and `--full` against a live Gitea instance, confirming the live issue/PR list response shapes and that the computed `review` and label-aggregation fields hold against real responses — behavior the fixture server cannot attest to
## Implementation Notes
The two tiers live in a new `src/commands/dashboard.ts` wired as the SDK's `home` handler.
The handler returns a **string** (not an object), so the SDK prepends the `bin:`/`description:` header verbatim above the dashboard's bespoke layout — the raw `prs: 0 open` / `issues: 0 open` empty-state lines and the label→count record cannot be expressed as a plain object for the SDK to encode.
`--full` is extracted in `runCli` before the SDK dispatches, and only when it is the sole remaining argument (after the global `-R`/`--login` flags are stripped).
This is because the SDK rejects any flag placed before a command, so `gitea-axi --full` would otherwise never reach the `home` handler; the extraction leaves an empty argv for the SDK and threads a `full` boolean into `dashboardCommand`.
`paginate.ts`'s `PaginatedResult` gained a `capped` flag: `fetchAllPages` now reports whether it stopped at the 20-page/1000-item cap with every page full, which drives the `+` suffix on the label counts.
The addition is backward-compatible — existing callers destructure only `items`/`total`.
Decisions made mid-implementation (all beyond the literal spec, kept deliberately):
- **Label-count ordering.** The spec fixes *what* to count but not the order; the `issues:` record is emitted by descending count, ties broken by name ascending, with `unlabeled` always last — a stable, at-a-glance ordering rather than an arbitrary one.
- **Defensive PR cap.** `fetchOpenPulls` slices the returned page to the requested limit so a server that ignored `limit=20` cannot inflate the table (or the review-fetch fan-out) past the cap.
- **Count line always present in the full tier**, including the empty case (`count: 0 of 0 total` above `prs: 0 open`), consistent with the list commands' count-line convention.
- **Top-level `--help`** gained a short note that the bare command shows the dashboard and `--full` selects the rich view, for discoverability.
The short and full tiers each re-slot the computed `review` after the PR fields via a local `prRowsWithReview` helper rather than sharing `pr list`'s inline loop — `pr list` also merges `--fields` extras into the same row, so the two are not the same operation despite the shared "review after the fields" shape (ADR 0006).