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-188Done

3.6.2 Column config service + API — add/rename/reorder/delete column; map/unmap status

Estimate: 30m · Depends on: 3.1.1, 3.3.3

The write path for board-column administration, through the 4-layer architecture (Route → Service → Repository → Prisma), extending the Story-3.3 board-config seam rather than adding a new service or table.

Service (boardsService, the 3.3 config home). Add: addColumn(boardId, { name, position? }), renameColumn(columnId, name), reorderColumn(columnId, position) (fractional-index between neighbours, the same rank scheme work items use), deleteColumn(columnId), mapStatusToColumn(boardId, columnId, statusId), unmapStatus(boardId, statusId), and renameBoard(boardId, name). Each is ONE transaction, returns a DTO (extend lib/dto/boards.ts + lib/mappers/boardMappers.ts), throws typed errors (lib/boards/errors.ts), and calls assertProjectAdmin (membership gate, TODO(6.4), mirroring setColumnWipLimit / the 2.2.5 workflow writes).

The mapping is a MOVE, not a duplicate. board_column_status has @@unique([boardId, statusId]) (3.1.1). mapStatusToColumn therefore, in one $transaction: boardColumnStatusRepository.deleteByStatus(statusId, tx) then .create({ columnId, statusId }, tx) — so re-mapping a status replaces its row; a P2002 backstop covers the concurrent race (mirror createStatus).

Delete-column safety. deleteColumn unmaps the column’s statuses (deleteByColumn) — they return to unmappedStatuses — then deletes the board_column row. It never deletes a work item (a card’s column is derived from its status). Decide + implement the Jira-style guard: refuse to delete a column whose mapped status still holds board cards (ColumnNotEmptyError → 409) unless those statuses are remapped first; the last column / a column mapping the INITIAL status may need extra guards (decide from the mirror).

Routes (HTTP-only, one service call each, typed-error → status): POST /api/board/columns (add), extend PATCH /api/board/columns/[columnId] (3.3.3 — add name / position alongside the existing wipLimit), DELETE /api/board/columns/[columnId], PUT /api/board/columns/[columnId]/statuses (set the column’s mapped statuses) + DELETE .../statuses/[statusId] (unmap), and extend PATCH /api/board (3.3.3 group-by) with name. A write from another workspace is 403/404 (the workspaceId gate, finding #26). No Prisma/transaction in any route.

Acceptance criteria

  • boardsService gains addColumn / renameColumn / reorderColumn / deleteColumn / mapStatusToColumn / unmapStatus / renameBoard, each one transaction, returning DTOs, throwing typed errors, gated by assertProjectAdmin (TODO(6.4)).
  • mapStatusToColumn is a MOVE: a status maps to exactly one column per board (the @@unique([boardId, statusId]) invariant holds; re-mapping replaces, never duplicates), proven by a vitest test.
  • deleteColumn unmaps its statuses (they reappear in unmappedStatuses) and deletes NO work item; the empty/guard rule (a column whose status still holds cards) behaves per the chosen mirror-product semantics with a typed error.
  • Routes are HTTP-only (no Prisma, no $transaction), map typed errors to status codes, and reject cross-workspace + non-admin writes (403/404); the existing WIP-limit + group-by behaviour of the extended routes is unchanged.
  • Repository writes go through boardColumnRepository / boardColumnStatusRepository (add a boardColumnRepository.delete if missing) with required tx; no new table / migration.
  • Vitest (real Postgres) covers add/rename/reorder/delete/map/unmap, the unique-move, the delete guard, and the workspace + membership gates.

Context refs

  • lib/services/boardsService.tssetColumnWipLimit / setSwimlaneGroupBy (3.3.3): the config-write home + assertProjectAdmin pattern to extend
  • lib/repositories/boardColumnRepository.ts (create / update / findById) + boardColumnStatusRepository.ts (create / deleteByColumn / deleteByStatus) — the repos to reuse / extend (add column delete)
  • app/api/board/columns/[columnId]/route.ts (PATCH wipLimit, 3.3.3) + app/api/board/route.ts (PATCH group-by) — the routes to extend
  • Story 3.1.1 — the board / board_column / board_column_status schema + @@unique([boardId, statusId]); lib/workflows/* for the status source
  • lib/services/workflowsService.ts createStatus — the P2002-backstop + assertProjectAdmin precedent; motir-core/CLAUDE.md (4-layer rules)