3.6 Board configuration — columns + status→column mapping
The board administration surface: a project admin manages the default board’s columns (add / rename / reorder / delete) and the column ↔ status mapping, and renames the board. This is the screen the unmapped-statuses tray (Story 3.2.6) has been pointing users toward — the place to put a status that has no column onto the board.
Why this story exists. Story 3.1 auto-seeds one column per status when a board is created (3.1.2) but deliberately leaves a LATER-added status unmapped (3.1 out-of-scope: "reacting to LATER status changes"; its acceptance test asserts a new status is "not dropped, not auto-columned"). The intended resolution was always an admin that maps it — but that admin was labelled "board CRUD / column-remap admin (not v1)" in 3.1/3.2/3.3 with no owning story. Motir has no V1 tier (the planned epics are the complete product), so a complete Jira-equivalent’s column manager must be planned: this is its home. Auto-columning stays rejected (it would reverse 3.1’s tested decision) — the user maps via this UI.
Pure config over the shipped schema (no new table). The board + board_column + board_column_status model is Story 3.1.1; the board-config WRITE seam — boardsService (setSwimlaneGroupBy / setColumnWipLimit), the PATCH /api/board/columns/[columnId] route, and the boardColumnRepository (create / update) + boardColumnStatusRepository (create / deleteByColumn / deleteByStatus) repos — already exists from Story 3.3 (swimlane group-by + WIP). 3.6 extends that exact seam with column create/reorder/delete and status mapping; it adds NO new repository entity and NO migration.
The mapping contract. board_column_status carries @@unique([boardId, statusId]) (3.1.1) — a status maps to at most one column per board. So "map status S to column C" is an UPSERT that moves S’s mapping (delete any existing row for S on this board, create the new one) inside one transaction, never a second row. Unmapping deletes the row — S returns to the unmappedStatuses tray (3.2.6), its work items hidden from the board but never deleted (a card’s column is DERIVED from its work_item.status, Story 3.1 — config never touches work items).
Deleting a column unmaps its statuses (they go back to the tray) and is refused while a mapped status still holds work items on the board UNLESS the admin remaps those statuses first — mirroring Jira’s "you can’t delete a column with issues" guard (decided at 3.6.2). No work item is ever deleted by a board-config write.
Where it lives. A new project-settings page app/(authed)/settings/project/board/ beside the Workflow editor (settings/project/workflow, Story 2.2.5) — the two are siblings (workflow owns the statuses/transitions; board-config owns how those statuses map onto board columns). The board’s 3.2.6 unmapped tray and the 3.2.3 column [⋯] menu (a disabled seam today) both deep-link here.
Out of scope (Story 3.7): multiple boards per project — board create/delete, the board switcher, per-board config (the board API already takes a boardId, 3.1). 3.6 configures the single default board only. Roles/permissions are Epic 6.4 (membership-gated now, TODO(6.4)).
Verification
- Pull the Story branch,
pnpm install,pnpm dev(NO migration — pure config over the Story-3.1 schema). Sign in to the seededmoooon→motirproject as an owner. pnpm test— vitest covers the column-config service (add / rename / reorder / delete; map/unmap with the@@unique([boardId, statusId])move-not-duplicate behaviour; delete-column unmaps its statuses and never deletes work items; the membership + workspace gates) and the route layer (typed-error → status).pnpm test:e2e --grep board-config— Playwright drives the real resolution flow: add a custom status in Workflow settings → it appears in the board’s unmapped-statuses tray → open Board settings → map it to a column → it now shows as/in that column on/boards; the tray entry is gone.- Column CRUD check: add a column, rename it, drag to reorder it (order persists on reload), delete an empty column — its previously-mapped statuses return to the unmapped tray, and no work items are lost.
- Mapping check: move a status from column A to column B — its cards move columns on the board, and the status maps to exactly ONE column (no duplicate). Unmap a status — it returns to the tray; its work items are hidden from the board but still exist (open one via search / issue list).
- Guard check: deleting a column whose mapped status still holds board cards is refused (or requires remapping first) per the 3.6.2 decision; a non-admin (non-owner) member sees the board-config surface read-only and every write is re-gated 403 server-side; a write against another workspace’s board is 403/404.
- Board rename check: rename the board in Board settings → the new name shows in the
/boardsheader. - Tray round-trip (the headline): the 3.2.6 unmapped tray’s CTA now reads "Map columns →" and opens Board settings (no longer the "Manage statuses" interim); after mapping, the tray is empty and absent.