Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-160Done

3.1.4 Board projection service — columns + grouped, per-column-paginated cards + unmapped statuses

Estimate: 24m · Depends on: 3.1.3, 3.1.2, 2.5.12

The read heart of the story: boardsService turns a project’s board + workflow + issues into the column-of-cards projection the 3.2 UI renders — bounded, never load-all (finding #57).

getBoard(projectId, opts, ctx)BoardProjectionDto. Reads the default board (3.1.3), its columns (ordered by position), and the board_column_status mapping; reads the project's workflow statuses (workflowsService.listStatusesByProject). For each column, it loads the first page of cards whose work_item.status ∈ that column’s mapped status keys, ordered by work_item.position asc, via the Story-2.5 read path (a grouped/per-status workItemRepository query — reuse findProjectIssuesFlat + count, the same AND-across-facet filter shape, NOT a new full scan). Returns, per column: the column meta (id, name, position, wipLimit), the page of card DTOs, the per-column total count, and a cursor for "load more". Plus a top-level unmappedStatuses: WorkflowStatusDto[] — every project status with no board_column_status row (Jira’s behavior; surfaced, never silently dropped).

Per-column pagination + lazy load (finding #57). Default page size (e.g. 50) per column; the projection NEVER returns every card. loadColumnCards(boardId, columnId, cursor, ctx)PagedColumnCardsDto returns subsequent pages so 3.2 can virtualize within a tall column. Done/terminal columns (status.category = 'done') are additionally bounded to a recent window (most-recent N by completion/position) with the full count surfaced — mirroring Jira’s "hide done older than ~14 days"; document the window as the durable shape, not a magic cap.

BoardCardDto — the card projection of work_item: (id, key, identifier, kind, title, status, priority, assigneeId, assignee summary, position, parentId, dueDate, estimateMinutes, isReady/blocked signal). The blocked/ready signal reuses Story 1.4 / finding #21 readiness (category = 'done' terminal generalization) so a board card can show a blocked indicator. Mapper in lib/mappers/boardMappers.ts; DTOs in lib/dto/boards.ts.

Out of scope: swimlane grouping + WIP enforcement (Story 3.3 — the projection returns wipLimit but does not enforce it); the move/reorder writes (3.1.5); any UI (3.2).

Acceptance criteria

  • boardsService.getBoard returns ordered columns, each with its mapped statuses, a bounded first page of cards (ordered by position), a per-column total count, and a cursor — plus a top-level unmappedStatuses list.
  • A project status with no column mapping appears in unmappedStatuses and in NO column (not dropped, not auto-columned).
  • The projection reuses the Story-2.5 issue-read path (no new full-table scan); it never returns all cards for a column — loadColumnCards returns subsequent pages by cursor.
  • Done-category columns are bounded to a documented recent window with the full count still reported.
  • BoardCardDto carries the readiness/blocked signal via the finding-#21 terminal predicate; cards are DTOs (no raw Prisma rows cross the boundary), mapped in lib/mappers/boardMappers.ts.
  • Vitest (real Postgres) covers: grouping into the right columns, per-column count + pagination + cursor, unmapped-status surfacing, terminal-column windowing, and workspace scoping.

Context refs

  • lib/services/workItemsService.ts + lib/repositories/workItemRepository.tsfindProjectIssuesFlat / countProjectIssues / the RepoIssueFilter shape (Story 2.5.12) the projection reuses
  • lib/services/workflowsService.tslistStatusesByProject / getTerminalStatusKeys (the done-category set for terminal-column windowing)
  • lib/dto/workItems.ts — the card-field precedent for BoardCardDto
  • Story 1.4 / finding #21 — the readiness predicate the blocked signal reuses
  • Finding #57 — the bounded-projection (no load-all) scale rule; finding #26 — the workspace gate
  • motir-core/CLAUDE.md — service owns DTO mapping; mappers in lib/mappers/*