3.1.2 Default-board seed (`lib/boards/defaultBoard.ts`) wired into `createProject` + backfill
Estimate: 16m · Depends on: 3.1.1, 3.1.3, 2.2.2
Generate, for every new project, one default Kanban board whose columns are the column-from-workflow projection: one column per workflow status, in status.position order, each column mapped to its single status. This is a seeded default over the durable many-to-one mapping (3.1.1), not a hardcoded 1:1 — an admin can later merge/split columns.
lib/boards/defaultBoard.ts — a pure function buildDefaultBoard(statuses: WorkflowStatusDto[]): DefaultBoardSpec that takes the project's seeded statuses (the six from lib/workflows/defaultWorkflow.ts: To Do / Blocked / In Progress / In Review / Done / Cancelled) and returns the board (name "Board", type: kanban), one column per status (name = status.label, position mirroring status.position), and one board_column_status mapping per column → its status. Pure / typed / no I/O — snapshot-testable, exactly like defaultWorkflow.ts.
Wire into projectsService.createProject. Inside the SAME transaction that already seeds the default workflow (Story 2.2.2), after the statuses exist, call the board repositories (3.1.3) to persist the default board + columns + mappings. Ordering matters: the board seed reads the just-created status rows to map columns, so it runs after the workflow seed within the one createProject transaction (one service method = one transaction, per CLAUDE.md).
Backfill existing projects. The seed reseeds the moooon tenant wholesale, but real projects created before this story have no board. Ship an idempotent backfill (a one-off script under scripts/ or a data-migration step, mirroring how prior stories backfilled): for each project lacking a board, build + persist the default board from its current statuses. Idempotent — re-running skips projects that already have a board.
Out of scope (own stories): board CRUD / rename / multi-board (Story 3.7); the column↔status mapping admin (Story 3.6 — where an unmapped status gets put on a column); reacting to LATER status additions (a custom status added post-seed lands unmapped, surfaced by the 3.1.4 projection — this story does not auto-append a column for it, matching Jira).
Acceptance criteria
lib/boards/defaultBoard.tsexports a purebuildDefaultBoard(statuses)returning the board + ordered columns + column→status mappings; unit-tested in isolation (snapshot of the six-column default).projectsService.createProjectseeds the default board inside its existing transaction, after the workflow seed; a newly created project has exactly one Kanban board with one column per status, in workflow order, each mapped to its status.- The seed is workspace-scoped (runs under the
prodect_approle with the workspace GUC) — no raw SQL inserts, all through the 3.1.3 repositories. - An idempotent backfill creates the default board for pre-existing board-less projects and is safe to re-run (no duplicate boards).
- A vitest integration test (real Postgres) asserts: create project → board + 6 columns + 6 mappings exist with the right names/order; re-running the backfill is a no-op.
Context refs
lib/workflows/defaultWorkflow.ts— the six-status default this projects from (Story 2.2.2)lib/services/projectsService.ts— thecreateProjecttransaction the board seed joins- Story 2.2.2 — the default-workflow seed wired into
createProject(the exact pattern this mirrors) scripts/plan-seed/seed.ts— how the tenant reseed runs services under the workspace GUCmotir-core/CLAUDE.md— one-service-method-one-transaction; no raw inserts