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-74

2.2 Status workflows (per-project, customizable)

Done
Description

Ships the durable workflow primitive every later issue surface reads from: each project owns an ordered set of statuses, each status carries a category (todo / in_progress / done) — the Jira-style three-bucket taxonomy — and a per-project set of legal transitions between them. New projects auto-seed the default workflow (To Do → In Progress → In Review → Done). After this Story, every work_item row's status string is resolvable to a typed status row, and Epic 3's boards (columns), Epic 6's reports (filters/groupings), and Story 1.4's isReady predicate (finding #21 — "terminal" generalizes from hardcoded 'done' to category = 'done') all read this single source of truth.

Prerequisites: Story 1.3 ships the project table and the projectsService.createProject transaction that seeds per-project state (the key counter). Story 1.4 ships work_item.status as a String column with 'todo' default; this Story does NOT change its type (kept as a string for portability) but adds the typed-status integrity layer via service-layer validation + an RLS-scoped lookup table. Story 2.1 ships the issuesService that this Story's transition validation hangs off. All work follows motir-core/CLAUDE.md's 4-layer architecture (Route → Service → Repository → Prisma). Per finding #26, every new route in this Story carries an explicit workspaceId gate at the application layer — RLS is the backstop, not the sole gate, because the dev/CI superuser bypasses RLS.

Verification

  • Pull the Story branch, pnpm install && pnpm prisma generate && pnpm prisma migrate dev against a fresh local DB.
  • pnpm test — vitest covers schema RLS, default seed, service read API, transition validation, the management writes (including atomic initial-status flip + delete protections), and the finding-#21 generalization.
  • pnpm test:e2e --grep workflow-flow — the closing Playwright suite runs against the real stack.
  • Manual UX check: create a workspace + project, visit /settings/project/[key]/workflow: verify all six default statuses (To Do / Blocked / In Progress / In Review / Done / Cancelled) and the fifteen transitions, rename one, add a custom status, add/remove a transition, flip policy mode, attempt a restricted-mode illegal status change on a real issue → typed-error toast.
  • Cross-project blocker check (finding #21 resolution): create two projects; create an issue in A blocked by an issue in B; mark the blocker "Cancelled" in B (legal per the default seed — no admin customization needed); A's issue's readiness flips to ready — proving the readiness predicate honors both done AND cancelled terminal categories.
  • Block-as-status vs. block-as-link parity check: on one issue, set status: 'blocked' (the status flag); on another, leave status todo but add a work_item_link.is_blocked_by edge to an open blocker. Both should show up in a "what's blocked?" filter / view (when those surfaces ship in Epic 2.5 / Epic 6); for this Story, just confirm both signals coexist without conflict — isReady returns false for the link-blocked one, and the status-blocked one is filterable by status.
  • RLS proof: open a psql session as prodect_app, SET app.workspace_id = '<workspace-A>', query workflow_status + workflow_transition — see only workspace A's rows.