3.8.2 Projection load model — bounded whole-set load + Done-age window + `truncated`/`cap` signal (retire the per-column cursor)
Estimate: 32m · Depends on: 3.1.4, 3.3.4
Change boardsService.getBoard from a per-column first-page + cursor to the bounded whole-set load the mirror uses, through the 4-layer architecture (Route → Service → Repository → Prisma), keeping the explicit workspaceId gate (finding #26).
The change. Introduce a BOARD_ISSUE_CAP constant (a generous bound, e.g. 5,000 — the Jira Software figure). The projection loads each mapped column’s cards up to the board-level cap across the board (ordered by position), applies the Done-age window to terminal columns (issues resolved within ~14 days; refines 3.2.5’s count-based window to the age-based Jira behaviour, full count still surfaced), and returns a board-level truncated: boolean + cap: number alongside the existing per-column totalCount. When the board’s total exceeds the cap, truncated is true (the UI shows the banner) and the load stops at the cap — bounded, never "load every row" (finding #57 holds). The swimlane projection (3.3.4 swimlaneKey + swimlanes[] aggregate) is unchanged in shape — it just buckets the now-fully-loaded (bounded) cards.
Retire the cursor. The per-column cursor (3.1.4) and the GET /api/board/columns/[id]/cards load-more route (3.1.6) become dead with no "Load more" caller; remove them (and the PagedColumnCardsDto path) or mark them removed in this subtask — do NOT leave a half-paged contract. Keep BoardColumnDto.totalCount (the denominator the count badge + the 3.3.6 WIP chip read).
Acceptance criteria
getBoardloads each column’s cards up toBOARD_ISSUE_CAP(no per-column cursor paging) and applies the Done-age window to terminal columns; per-columntotalCountis retained.- The projection returns board-level
truncated+cap;truncatedis true exactly when the board total exceeds the cap, and the load is bounded by the cap (never unbounded). - The dead per-column cursor + the
/columns/[id]/cardsload-more route are removed (no orphaned paging contract); DTOs updated inlib/dto/boards.ts+ mapped inlib/mappers/boardMappers.ts. - Vitest (real Postgres) covers: the bounded load (count == min(total, cap)),
truncatedtrue past the cap / false under it, the Done-age window (old done items excluded, count still full), and the swimlane buckets still resolve over the bounded set.
Context refs
lib/services/boardsService.tsgetBoard+lib/dto/boards.ts+lib/mappers/boardMappers.ts(3.1.4 / 3.3.4) — the projection this changes (addtruncated/cap, dropcursor)app/api/board/columns/[columnId]/cards/route.ts(3.1.6) — the load-more route to retirelib/repositories/workItemRepository.ts— the column read + count; the Done-age filter- finding #57 (bounded, not load-all — the cap IS the bound); the verified Jira cap + 14-day Done window (rung 1);
notes.htmlmistake #33;motir-core/CLAUDE.md(4-layer + DTO mapping)