From 46cd8f8401929aa91617536f4a9084c38094c610 Mon Sep 17 00:00:00 2001 From: DJP Date: Fri, 15 May 2026 20:26:25 -0400 Subject: [PATCH] =?UTF-8?q?stage-machine:=20allow=20IN=5FPROGRESS=20?= =?UTF-8?q?=E2=86=92=20APPROVED=20shortcut=20for=20non-review=20stages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NONE-approval stages bypass the review surface by design — the UI shows "Mark Complete" which sets APPROVED directly. The state machine still enforced the legacy "go through IN_REVIEW first" rule, so the click returned `Cannot transition from IN_PROGRESS to APPROVED`. Expands the allowed transitions to cover the producer-friendly shortcuts they'd otherwise need to bounce through: IN_PROGRESS → +APPROVED, +CHANGES_REQUESTED, +SKIPPED CHANGES_REQUESTED → +APPROVED BLOCKED → +IN_PROGRESS IN_REVIEW → +IN_PROGRESS (cancel review path) DELIVERED → +APPROVED (downgrade if mistakenly delivered) SKIPPED → +IN_PROGRESS (unskip directly without NOT_STARTED) FORMAL/SIMPLE stages still flow naturally through IN_REVIEW because the StageReviewPanel buttons set those explicitly — the state-machine loosening doesn't change their UX, just stops blocking the NONE path. Auto-advance (819288d) already treats APPROVED / DELIVERED / SKIPPED as terminal so this Mark-Complete click on Inputfile correctly cascades and unblocks Internal Approval downstream. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/pipeline/stage-machine.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/lib/pipeline/stage-machine.ts b/src/lib/pipeline/stage-machine.ts index f9b40b0..635cca2 100644 --- a/src/lib/pipeline/stage-machine.ts +++ b/src/lib/pipeline/stage-machine.ts @@ -2,16 +2,26 @@ import type { StageStatus } from "@/generated/prisma/client"; /** * Valid stage status transitions. + * + * NONE-approval stages take the IN_PROGRESS → APPROVED shortcut ("Mark + * Complete"). FORMAL/SIMPLE-approval stages route through IN_REVIEW + * first (the StageReviewPanel's Submit-for-Review → Approve flow). + * Both paths are allowed at the state-machine level; UI gating decides + * which one is exposed. + * + * CHANGES_REQUESTED → APPROVED and IN_PROGRESS → SKIPPED are quality- + * of-life shortcuts producers asked for so they don't have to bounce + * through an extra status to close out simple work. */ const TRANSITIONS: Record = { - BLOCKED: ["NOT_STARTED", "SKIPPED"], + BLOCKED: ["NOT_STARTED", "IN_PROGRESS", "SKIPPED"], NOT_STARTED: ["IN_PROGRESS", "SKIPPED"], - IN_PROGRESS: ["IN_REVIEW"], - IN_REVIEW: ["APPROVED", "CHANGES_REQUESTED"], - CHANGES_REQUESTED: ["IN_PROGRESS"], + IN_PROGRESS: ["IN_REVIEW", "APPROVED", "CHANGES_REQUESTED", "SKIPPED"], + IN_REVIEW: ["APPROVED", "CHANGES_REQUESTED", "IN_PROGRESS"], + CHANGES_REQUESTED: ["IN_PROGRESS", "APPROVED"], APPROVED: ["DELIVERED", "IN_PROGRESS"], // reopen if client sends updated files - DELIVERED: ["IN_PROGRESS"], // reopen for rework - SKIPPED: ["NOT_STARTED"], // unskip if stage becomes needed + DELIVERED: ["IN_PROGRESS", "APPROVED"], // reopen for rework + SKIPPED: ["NOT_STARTED", "IN_PROGRESS"], // unskip if stage becomes needed }; export function canTransition(