4.5 Scrum board (sprint-scoped view)
The Scrum variant of the board: the same Story-3.2/3.3 board surface, scoped to a board's active sprint, under a sprint header (name, goal, date range, time remaining, points remaining, per-column point totals). Per the stub this story is thin — "mostly a scope filter + sprint header" — because Story 3.1 already shipped the BoardType { kanban, scrum } enum FOR this story (4.5 adds no enum) and the Kanban surface (3.2) + swimlanes/WIP (3.3) are reused wholesale. 4.5 is the view layer: a sprint scope on the projection plus the sprint header chrome. It changes NO part of the move contract (a drag is still a workflow transition) and adds NO new card or column vocabulary.
Why this story lives in Epic 4, not Epic 3 (mistake #32). Originally drafted as Story 3.4 under Epic 3 (Boards), this Story was the canonical example of mistake #32: a forward-pointing cross-epic dependency (Epic 3 → Epic 4) that would have shipped a story whose own file admitted its subtasks could not reach the ready set until the next epic landed. The fix per the (a)-vs-(b) decision (MOTIR.md) was (b)-move-the-story, not (a)-swap-the-epics, because only this story crossed (Stories 3.1 / 3.2 / 3.3 / 3.6 — Kanban projection, drag-drop, swimlanes/WIP, board config — are pure Kanban substrate, a standalone capability in the mirror product). So 4.5 ships from inside Epic 4 alongside the sprints, points, and lifecycle it depends on; its dependency arrows are now normal backward-pointing intra-epic deps.
Sibling Epic-4 deps (intra-epic — story-level forward refs while siblings are unexpanded stubs). A Scrum board is meaningless without sprints: the sprint entity (goal, start/end, state planned·active·complete) + the issue→sprint association are Story 4.1 (sprint + backlog data model); story points (for "points remaining" + the per-column point totals) are Story 4.3 (estimation); the sprint lifecycle — start (which sets a sprint active and provisions the project's Scrum board), the one-active-sprint-per-board guard rail, and complete sprint (carry-over) — is Story 4.4. Stories 4.1 / 4.3 / 4.4 are unexpanded (stubs.ts stubs with no subtasks yet), so 4.5's code subtasks depend at the STORY level on 4.1 / 4.3 / 4.4. Consequence: 4.5 stays out of the ready set until 4.1 / 4.3 / 4.4 land — that is correct and intended (a normal backward intra-epic dependency, not the mistake-#32 forward cross-epic kind). A re-plan after 4.1 / 4.3 / 4.4 expand can retarget these to specific subtasks.
How a Scrum board comes to exist (Epic-4 provisioning, NOT 4.5). v1 auto-seeds exactly one Kanban board per project (Story 3.1.2); board.projectId is deliberately non-unique (3.1.1) so a second, scrum-type board can be added per project. Provisioning that scrum board — auto-creating it when a team first uses sprints, and setting a sprint active — is a 4.1/4.4 responsibility, captured here as the dependency above. 4.5 does NOT add board CRUD (out of scope, consistent with Story 3.3) and does NOT invent a provisioning path; it RENDERS the sprint-scoped view for a board whose type is scrum, resolving that board's active sprint. If the resolved scrum board has no active sprint, 4.5 shows the no-active-sprint empty state (below) rather than failing.
Sprint SCOPE on the projection (the one backend change). Extend Story 3.1.4's boardsService.getBoard so that for a scrum-type board it resolves the board's active sprint (the single state == active sprint — the 4.4 one-active-per-board guard makes this unambiguous) and FILTERS the projection to issues associated with that sprint. The column / card / bounded-page / swimlane shape is otherwise byte-for-byte the 3.1.4/3.3.4 projection — sprint scope is an additional WHERE issue.sprintId = :activeSprintId on the same queries, not a new projection. A kanban-type board is unaffected (no sprint scope). When a scrum board has no active sprint, the projection returns a sprint: null marker (the UI renders the empty state); it never falls back to showing the unscoped backlog as if it were a sprint.
Sprint SUMMARY in the projection (drives the header — bounded aggregates, finding #57). Alongside the scoped columns, getBoard returns a SprintSummaryDto for the active sprint: { id, name, goal, startDate, endDate, state, daysRemaining, points: { committed, completed, remaining }, columnPoints: { columnId → pointSum } }. The point figures are aggregate SUM(storyPoints) queries scoped to the sprint (committed = all sprint issues; completed = issues in terminal/done statuses; remaining = committed − completed; columnPoints = the Jira scrum-board per-column point total). They are computed from a grouped aggregate, NOT by summing the loaded card page — the board stays bounded exactly as 3.1/3.2/3.3 (a board that summed every loaded card to total points would be prototype-thinking; points come from an aggregate). daysRemaining is derived from endDate (calendar days to end, floored at 0; an overdue sprint reads "Ended" / a negative-clamped 0, not a negative number). The in-sprint burndown chart is explicitly Story 4.6 (velocity + burndown, formerly 4.5 before 4.5 was reassigned to this Story), NOT here — 4.5 shows the numeric remaining only and leaves a documented seam for 4.6 to add the chart.
The sprint header (the one UI surface, per 4.5.1's design). Above the reused board: the sprint name + goal (truncated with reveal), the date range + time remaining ("5 days remaining" / "Ends Jun 14" / "Ended"), and a compact points summary (committed / completed / remaining) — each carried by text+number, never colour alone (finding #35). The per-column point totals render in the reused column headers (Jira's scrum "sprint health" pills) from columnPoints. The header also hosts the Complete-sprint entry point — but the complete-sprint FLOW (the confirm modal + carry-over handling + the sprint report) is Story 4.4's; 4.5 REUSES 4.4's complete-sprint trigger/flow and does NOT rebuild carry-over (if 4.4 exposes it as a mountable action, 4.5 mounts it in the header; otherwise 4.5 renders the button as a seam 4.4 wires — the same seam pattern 3.2 used for the Epic-6 Filter button).
Completeness — the real-product states. No active sprint (the common pre-start / post-complete state): an EmptyState in place of the board — "No active sprint", a one-line explainer, and a CTA pointing at the Backlog (Story 4.2) to plan/start one (the Backlog is where a sprint is started — 4.2/4.4 — so 4.5 links there rather than starting a sprint itself). No scrum board for the project (sprints never enabled): fall through to the existing Kanban board (3.2) — 4.5 only replaces the view when a scrum board is resolved. Loading / error reuse the 3.2.2 board scaffold + ErrorState. The header degrades gracefully when points are unestimated (4.3 not yet used on these issues): a sprint with no estimated points shows "—" for point figures, never a broken NaN.
Reuse, not rebuild (the load-bearing scope decision). The columns, cards, drag-as-transition, snap-back, keyboard DnD, per-column lazy load-more + virtualization (3.2), and the swimlanes + WIP layer (3.3) are the SAME components, rendered with a sprint-scoped projection. Swimlanes/WIP compose on the scrum board for free (Jira scrum boards have both) because it is the same board component — 4.5 wires none of it again. The ONLY net-new code is the projection sprint-scope + summary (4.5.2) and the sprint-header chrome + scrum page resolution (4.5.3).
Out of scope (Epic-4 siblings / later): the sprint entity / lifecycle / start-complete flow / one-active-sprint guard (Story 4.1 + 4.4); the backlog + assign-to-sprint + grooming (Story 4.2); story-point estimation itself (Story 4.3); the burndown + velocity CHARTS (Story 4.6 — 4.5 shows numeric remaining + leaves the chart seam); board CRUD / scrum-board provisioning / multi-board navigation (Story 3.7); parallel multiple active sprints (the 4.4 guard is one active sprint per board — no multi-sprint selector or stacked-sprint board, matching the planned guard, no complexity for nothing); the cross-cutting Epic-3 board journey at scale (Story 3.5). The board surface, the move contract, swimlanes/WIP, and the bounded projection all come from Stories 3.1 + 3.2 + 3.3 (Epic 3) and are reused, not rebuilt.
Verification
- Pull the Story branch,
pnpm install,pnpm prisma migrate dev(no 4.5 migration — the sprint schema is Story 4.1, theBoardTypeenum is 3.1.1),pnpm db:seed,pnpm dev. (Requires sibling Stories 4.1 / 4.3 / 4.4 merged so a scrum board + an active sprint with points exist to render.) pnpm test— vitest covers: the projection sprint-scope (a scrum board returns only its active sprint's issues; a kanban board is unscoped/unchanged; no-active-sprint →sprint: null), theSprintSummaryDtoaggregates (committed/completed/remaining points +columnPointsfrom SUM aggregates, NOT from the loaded page;daysRemainingfloored at 0; unestimated → null/"—"), and the bounded/paged shape preserved (no load-all).pnpm test:e2e --grep board-scrum— Playwright drives the real scrum board: header shows name/goal/dates/remaining + the points summary; the board renders only the active sprint's issues; per-column point totals show; the no-active-sprint empty state links to the Backlog.- Scrum render check: sign in as
info@moooon.net, open a project that has a scrum board with an active sprint →/boardsrenders the sprint header (name, goal, date range, "N days remaining", committed/completed/remaining points) above the board, the board shows ONLY the active sprint's issues in workflow columns, and each column header shows its point total. The layout matchesdesign/boards/scrum.mock.html. - Scope check: an issue NOT in the active sprint does not appear on the scrum board; moving a card still transitions its workflow status (the 3.2 contract is unchanged) and does not change its sprint.
- Reuse check: swimlanes (group-by Assignee/Epic/Priority) and per-column WIP limits work on the scrum board exactly as on the Kanban board (3.3) — same controls, same drag-reassign, same soft WIP warning — with no scrum-specific reimplementation.
- No-active-sprint check: on a scrum board whose sprint is completed (or before any sprint is started) the board area shows the "No active sprint" empty state with a CTA to the Backlog (4.2), not an empty six-column board and not the unscoped backlog masquerading as a sprint.
- Points-degradation check: a sprint whose issues have no story-point estimates shows "—" for the point figures (no
NaN), and the board still renders. - Scale check (finding #57):
pnpm db:seed:largewith a large active sprint → the board pages per column (3.2.5 load-more + virtualization intact) and the point totals come from the aggregate (no all-cards fetch); DOM row count stays bounded. - a11y check: the sprint header is a labelled landmark; time/points remaining are read by assistive tech as text (not colour/shape alone, finding #35); the empty-state CTA is keyboard-operable.