MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-79

2.2.5 Workflow management API + project-settings UI

Done
Description

Estimate: 22m · Depends on: 2.2.3

Ship the write surface so a project admin can edit their workflow: add/rename/recolor/reorder statuses, add/remove transitions, and flip workflow_policy_mode. Built as Server Actions following the 4-layer rule (Action → Service → Repository → Prisma) and mounted under the existing project-settings route shell from Story 1.5.

Service write methods:

  • createStatus({ projectId, workspaceId, key, label, category, color, position }) — inserts, validates key is project-unique, position via fractional-indexing.
  • updateStatus({ statusId, workspaceId, label?, category?, color?, position?, isInitial? }) — partial update; flipping isInitial to true inside one tx unsets the previous initial-status row (atomic to satisfy the partial unique index from 2.2.1).
  • deleteStatus({ statusId, workspaceId }) — refuses (throws StatusInUseError → 422) if any work_item.status still references this status key OR if it's the initial status OR if removing it would leave the project with zero category = 'done' statuses (the last terminal-status invariant). Same-tx cleanup deletes all workflow_transition rows pointing to or from this status.
  • addTransition({ projectId, workspaceId, fromStatusId, toStatusId }) — inserts; the unique constraint from 2.2.1 makes duplicate inserts idempotent (catch P2002 → return existing).
  • removeTransition({ transitionId, workspaceId }).
  • setPolicyMode({ projectId, workspaceId, mode }) — flips the project's workflow_policy_mode column.

Permission gate: every write method calls workspacesService.assertProjectAdmin(userId, projectId, workspaceId). v1 routes "project admin" to the workspace owner role added in Story 1.6.5 (finding #36) — full per-project RBAC is Epic 6 work. This is intentionally narrow: the durable shape is a permission check that exists; the durable scope expansion (to a real "project admin" role) is the Epic-6 add. Owner gate today, admin gate later — same gate function, swapped implementation. Not a "shortcut now, real later" — the gate is the durable shape; the role-set behind it widens in Epic 6.

UI: /settings/project/[projectKey]/workflow route under the existing project-settings shell. Two tabs: Statuses (drag-to-reorder list, inline edit, add/delete) and Transitions (a matrix grid: rows = from-status, columns = to-status, click cell to toggle). A header toggle controls policy mode (restricted vs open); a banner under the toggle explains the consequence ("Open mode: any status can transition to any other"). Uses the Story 1.5 shell primitives (Modal, Toast, Pill); no new design-system components needed beyond a color-swatch picker that composes from the existing color tokens. All actions are optimistic with toast confirmation; failures revert + toast the typed-error message.

Acceptance criteria

  • Six service write methods shipped with their typed errors; every one calls assertProjectAdmin first and surfaces the typed-error message on failure.
  • Initial-status flip is atomic (the previous initial-status row is unset in the same tx); the partial unique index never sees two true rows.
  • Delete protections: deleting a status referenced by any work_item → 422 STATUS_IN_USE; deleting the initial status → 422 CANNOT_DELETE_INITIAL_STATUS; deleting the last category = 'done' status → 422 CANNOT_DELETE_LAST_TERMINAL_STATUS.
  • /settings/project/[projectKey]/workflow route renders the two tabs, drives all six service writes through Server Actions, optimistic UI + toast confirmation, A11y (axe) sweep on the page passes.
  • Vitest covers the matrix + the three delete-protections + the atomic initial-status flip.
  • Playwright spec covers: an owner edits the default workflow (rename "In Review" → "QA"), adds a "Cancelled" terminal status (category: done), removes the back-transition in_review→in_progress, flips policy mode → restricted re-applied.

Context refs

  • Story 1.5's AppLayout + settings-route shell
  • Story 1.6.5's JobsDashboard — closest precedent for a project-settings-style admin surface with a Server Action + permission gate
  • Story 1.2.5's typed-error catalog + WorkspaceMembershipError shape
  • Story 1.6.5's owner gate + finding #36's workspaceRolesService — the temporary "owner == admin" implementation seam
  • Design system tokens for category colors (semantic Pill tones, post-finding-#35 fix)