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

2.2.10 Protect default statuses (immutable except color) + rename restore action

Done
Description

Estimate: 16m · Depends on: 2.2.5, 2.2.9

Per finding #49 (Yue, 2026-06-03): the six default statuses (todo/blocked/in_progress/in_review/done/cancelled) are a STANDARD, protected set. An admin cannot rename, recategorize, reorder, or delete a default status — the ONLY editable property is color. Custom statuses the admin adds stay fully editable + deletable. This revises 2.2.5's "all statuses equally mutable" model and resolves the three UX gaps Yue raised: a default has no delete button (non-deletable), a "Default" badge marks it, and since it can't be renamed its label always shows the standard meaning (so the internal-key confusion disappears).

Service. Export DEFAULT_STATUS_KEYS from defaultWorkflow.ts. deleteStatus rejects a default (new typed DefaultStatusProtectedError → 422). updateStatus on a default rejects any change to label/category/isInitial/position and allows only color (same typed error). The 2.2.5 initial / last-terminal delete-protections stay as the backstop for CUSTOM statuses (a custom status could still be initial or the last terminal).

UI (WorkflowEditor). A "Default" badge (Pill) on default statuses. Default rows show NO delete / reorder buttons, and their edit affordance exposes ONLY the ColorSwatchPicker (label/category read-only or hidden). Custom rows keep the full edit/delete/reorder set. Reorder: a default's up/down is disabled; custom statuses still reorder freely (incl. interleaved with defaults via fractional positions).

Rename the restore action. Because default statuses can no longer go missing, the only thing that can be lost is default TRANSITIONS. Rename the "Restore defaults" button → "Restore default transitions" and update the confirm-modal copy. 2.2.9's restoreDefaultWorkflow narrows to (or is renamed for) re-adding missing default transitions; its status-re-add path becomes a defensive no-op under the protected model.

Acceptance criteria

  • A default status CANNOT be deleted, renamed, recategorized, or reordered (typed DefaultStatusProtectedError → 422 at the service); it CAN be recolored. Verified per operation.
  • A custom status remains fully editable + deletable (the 2.2.5 behavior), and the initial / last-terminal delete-protections still fire for custom statuses where they apply.
  • UI: default rows render a "Default" badge, no delete/reorder controls, and a color-only edit; custom rows render the full controls. A11y sweep still passes.
  • The "Restore defaults" button is renamed "Restore default transitions"; its action re-adds only the missing default transition edges (idempotent), and the confirm copy reflects the narrower scope.
  • Vitest (real Postgres): reject delete/rename/recategorize/reorder of a default; accept recolor; custom-status full edit; restore-default-transitions re-adds a deleted default edge and is a no-op when complete.
  • Quality gates green (tsc + eslint + prettier + vitest).

Context refs

  • Finding #49 in PRODECT_FINDINGS.md — the protected-default decision
  • lib/workflows/defaultWorkflow.ts — add/export DEFAULT_STATUS_KEYS
  • lib/services/workflowsService.ts (2.2.5/2.2.9) — deleteStatus / updateStatus / restoreDefaultWorkflow to gate + rescope; lib/workflows/errors.ts for the new error
  • app/(authed)/settings/project/workflow/_components/WorkflowEditor.tsx + actions.ts (2.2.5/2.2.9) — the badge, the default-vs-custom affordances, the button rename
  • components/ui/ColorSwatchPicker.tsx (2.2.8) + components/ui/Pill.tsx