6.15.2 Filtered board read — thread a compiled filter predicate (6.1 AST) into getBoard/loadColumnCards; cap + `truncated` over the filtered set; Scrum-scope compose
Estimate: 32m
Make the board projection filterable. Pure backend, no UI — the reusable read 6.15.3 consumes.
boardsService.getBoard grows an optional filter input — a FilterAst (built inline from a board query) OR a resolved saved-filter id (reuse the 6.2 saved-filter resolution + access gate). The service compiles it with the SAME resolveFilterAst (lib/filters/registry.ts) the /issues list uses, producing the Prisma where fragment, and threads it into loadColumnCards so each column’s card read is AND-ed with the predicate (mirror workItemRepository.findByProjectFiltered — the shipped precedent that applies a compiled filter to a project read). Repository writes still require tx; this is read-only.
Cap / truncated over the FILTERED set (3.8). The BOARD_ISSUE_CAP count + the truncated flag + the virtualization bound must be computed over the filtered result, not the whole board — so a filter that brings the set under the cap clears truncated (that is the over-cap banner’s whole purpose). Scrum compose (4.5): the predicate ANDs with the existing active-sprint scope filter — it narrows WITHIN the sprint, never widening it. Tenant + permission: the explicit workspaceId gate (finding #26) and the 6.4 project-access scope are unchanged and still apply.
Acceptance criteria
getBoardaccepts an optional filter (inline AST or saved-filter id); when present, each column projects ONLY matching cards; when absent, behaviour is byte-identical to today (no regression to the unfiltered board).truncated/ the cap count / the virtualization bound are computed over the filtered set; a filter under the cap clearstruncated. On a Scrum board the predicate composes with (does not replace) the 4.5 sprint scope.- Invalid / unauthorized saved-filter id → a typed error the route maps (no raw Prisma escaping the service); workspace + 6.4 scope preserved; no schema change (
migrate dev= "No difference detected");pnpm test:coverage≥ 90% on changed files.
Context refs
lib/services/boardsService.ts(getBoard+loadColumnCards+ the 3.8 cap/truncatedlogic + the 4.5 sprint-scope filter),lib/mappers/boardMappers.ts,lib/dto/boards.tslib/filters/ast.ts+lib/filters/registry.ts(resolveFilterAst— the 6.1 compiler) andworkItemRepository.findByProjectFiltered(the shipped "apply a compiled filter to a project read" precedent)lib/savedFilters/*(6.2 — saved-filter resolution +access.tsgate)- finding #26 (explicit
workspaceIdtenant filter);motir-core/CLAUDE.md(4-layer)