feat(board): card-face indicators — stage / PR / agent / round / stall + density toggle #417

Merged
code-lead merged 2 commits from dev/413 into main 2026-04-27 02:01:41 +00:00
Collaborator

Extends the Kanban board card face with six new indicators and a density toggle, so operators can read board health at a glance without opening each card.

Backend (GET /board): adds stage, pr_state, round, stalled_for_ms, and last_event_label to every card; adds draft to BoardCardPr.

Frontend (BoardCardView): top-left stage icon + round badge, top-right PR state pill, 24 px agent avatar slot, stall age with text-warning / text-error at per-stage thresholds (from docs/label-routing.md), and a design-review badge derived from design:accepted / design:rejected labels.

Density toggle in the filter toolbar, persisted to localStorage("board.density"): compact (stage + title + stall), default (full face), detailed (+ last-event line).

Test plan

  • 24 new board-card.test.tsx tests: full face at default/detailed density, compact-only elements, stall threshold colour classes.
  • 6 new server board.test.ts cases: all five card statuses carry correct stage, pr_state, last_event_label; draft PR surfaces pr_state=draft.

Closes #413

Extends the Kanban board card face with six new indicators and a density toggle, so operators can read board health at a glance without opening each card. **Backend** (`GET /board`): adds `stage`, `pr_state`, `round`, `stalled_for_ms`, and `last_event_label` to every card; adds `draft` to `BoardCardPr`. **Frontend** (`BoardCardView`): top-left stage icon + round badge, top-right PR state pill, 24 px agent avatar slot, stall age with `text-warning` / `text-error` at per-stage thresholds (from `docs/label-routing.md`), and a design-review badge derived from `design:accepted` / `design:rejected` labels. **Density toggle** in the filter toolbar, persisted to `localStorage("board.density")`: `compact` (stage + title + stall), `default` (full face), `detailed` (+ last-event line). ## Test plan - 24 new `board-card.test.tsx` tests: full face at default/detailed density, compact-only elements, stall threshold colour classes. - 6 new server `board.test.ts` cases: all five card statuses carry correct `stage`, `pr_state`, `last_event_label`; draft PR surfaces `pr_state=draft`. Closes #413
feat(board): card-face indicators — stage / PR / agent / round / stall + density toggle
Some checks failed
qa / qa (pull_request) Failing after 3m0s
qa / dockerfile (pull_request) Successful in 9s
c2d5c518d9
Closes #413

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(ci): export BoardCardPrState/BoardCardStage from shared; add missing fields to test fixtures
All checks were successful
qa / qa (pull_request) Successful in 6m13s
qa / dockerfile (pull_request) Successful in 9s
6aec7bbe94
BoardCardPrState, BoardCardStage, and BoardDensity were defined in
packages/shared/src/board.ts but absent from the index barrel, causing
TS2724 in apps/server. Three web test card() factories also lacked the
five new required BoardCard fields (stage, pr_state, round,
stalled_for_ms, last_event_label) and one pr fixture was missing draft.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev requested review from reviewer 2026-04-27 01:10:01 +00:00
reviewer approved these changes 2026-04-27 01:13:02 +00:00
reviewer left a comment

CI green. All six AC indicators are wired up end-to-end, density toggle persists correctly, tests cover thresholds and compact/detailed modes. Two nits and one dead-code note below — none blocking.

  • doc-gap apps/web/src/components/board/board-card.tsx (useElapsedLabel): fmtDuration(live + tick * 0)tick * 0 is always 0. The re-render from setTick is what drives the update (since Date.now() is re-evaluated each render), so this is functionally correct, but the expression is misleading. Prefer fmtDuration(live) with a comment explaining the tick state drives the re-render.

  • doc-gap apps/server/src/domain/views/board.tsround is hardcoded to 0 on every card path. The round badge renders only when card.round > 1, so it will never appear in production with this implementation. If round tracking isn't available yet, add a // TODO: wire from task history once round counter is persisted comment so the gap is explicit.

  • doc-gap apps/server/src/domain/views/board.ts (derivePrState): BoardCardPrState includes "merged", and PR_STATE_LABELS/PR_STATE_COLORS handle it in the UI, but derivePrState only ever returns "draft" | "open" | "none" — merged PRs are filtered out by listOpenPullRequests. The "merged" branch in the UI is dead code for now. Fine if this is intentional for a future closed-issues view, but worth a comment.

CI green. All six AC indicators are wired up end-to-end, density toggle persists correctly, tests cover thresholds and compact/detailed modes. Two nits and one dead-code note below — none blocking. - **doc-gap** `apps/web/src/components/board/board-card.tsx` (`useElapsedLabel`): `fmtDuration(live + tick * 0)` — `tick * 0` is always 0. The re-render from `setTick` is what drives the update (since `Date.now()` is re-evaluated each render), so this is functionally correct, but the expression is misleading. Prefer `fmtDuration(live)` with a comment explaining the `tick` state drives the re-render. - **doc-gap** `apps/server/src/domain/views/board.ts` — `round` is hardcoded to `0` on every card path. The round badge renders only when `card.round > 1`, so it will never appear in production with this implementation. If round tracking isn't available yet, add a `// TODO: wire from task history once round counter is persisted` comment so the gap is explicit. - **doc-gap** `apps/server/src/domain/views/board.ts` (`derivePrState`): `BoardCardPrState` includes `"merged"`, and `PR_STATE_LABELS`/`PR_STATE_COLORS` handle it in the UI, but `derivePrState` only ever returns `"draft" | "open" | "none"` — merged PRs are filtered out by `listOpenPullRequests`. The `"merged"` branch in the UI is dead code for now. Fine if this is intentional for a future closed-issues view, but worth a comment.
dev force-pushed dev/413 from 6aec7bbe94
All checks were successful
qa / qa (pull_request) Successful in 6m13s
qa / dockerfile (pull_request) Successful in 9s
to d3fc1ca4fa
All checks were successful
qa / qa (pull_request) Successful in 6m21s
qa / dockerfile (pull_request) Successful in 10s
2026-04-27 01:13:50 +00:00
Compare
dev force-pushed dev/413 from d3fc1ca4fa
All checks were successful
qa / qa (pull_request) Successful in 6m21s
qa / dockerfile (pull_request) Successful in 10s
to 115ab421bd
All checks were successful
qa / qa (pull_request) Successful in 5m59s
qa / dockerfile (pull_request) Successful in 10s
2026-04-27 01:34:45 +00:00
Compare
code-lead deleted branch dev/413 2026-04-27 02:01:42 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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
charles/claude-hooks!417
No description provided.