Operant Studio
OPER-61

NEEDS_RESCOPE panel in Ledger triage UI

Body

# OPER-61 — NEEDS_RESCOPE panel in Ledger triage UI

Ship the human-facing surface for the `NEEDS_RESCOPE` state that OPER-60 introduces. Without this UI, rejected tasks pile up invisibly and the admission gate becomes a silent bottleneck.

## Scope

Add a `NEEDS_RESCOPE` panel to the Ledger triage view at `apps/web/src/app/studio/[team]/triage/`. Panel shows:

1. **Queue depth** at the top — the metric OPER-58 will eventually arm `pipeline_throughput_below_target` against.
2. **List of NEEDS_RESCOPE tasks** — one row per task, sorted by `updatedAt` desc.
3. **Per-row: rejection reasons badge** — pulls from the most recent `PlanningRejection` row for that task, displays each reason as a color-coded chip. Fixed vocabulary (from OPER-60): `missing_size` = amber, `missing_writesets` = amber, `writesets_unresolved` = blue, `missing_verify_cmd` = amber, `missing_module` = amber, `body_too_short` = red, `missing_acceptance_criteria` = red, `phi_no_reviewer` = purple.
4. **PHI flag prominence** — any row where `phiInScope = true` displays a `PHI` badge on the left rail, visible without scroll (Devi non-negotiable). PHI badge is not conditional on rejection reasons — it's always visible when the flag is true.
5. **Per-row action buttons**: "Patch fields" (opens edit modal), "Send back to READY_FOR_PLANNING" (only enabled once all rejection reasons are addressed), "Cancel" (opens confirm dialog with reason free-text).

## Non-goals

- Auto-CANCEL after N weeks — explicitly rejected by Justin 2026-07-29. Cancel action requires human click every time.
- Bulk operations across multiple NEEDS_RESCOPE rows — v0.2 concern, not shipping in v0.1.
- Concept card UI — separate work (2B in the memo).
- Risk-tier display — descoped from v0.1 (DOC memo 2026-07-29).

## Acceptance Criteria

- [ ] New panel visible at `/studio/operant/triage` with heading "NEEDS_RESCOPE queue" and current depth count.
- [ ] Task rows render with title, identifier, `updatedAt`, rejection-reason chips.
- [ ] Reason chips accurately reflect the most-recent `PlanningRejection.reasons` array for each task using the fixed vocabulary from OPER-60.
- [ ] PHI badge renders on any row with `phiInScope = true`, always visible on the left rail without scroll.
- [ ] "Patch fields" opens the standard task edit modal with focus on the first missing field.
- [ ] "Send back to READY_FOR_PLANNING" transitions status via PATCH and shows a toast; disabled if any rejection reason still applies (validated client-side against current task fields).
- [ ] "Cancel" opens confirm dialog; on confirm transitions to `CANCELED` with an audit note.
- [ ] No auto-transition, no bulk actions, no scheduled cleanup — every state change requires an explicit human click.
- [ ] Panel data query is a single SQL statement — no JSONB unpacking (matches Priya's non-negotiable in OPER-60).
- [ ] Playwright test covers the "patch → send back" happy path end-to-end.
- [ ] Storybook stories for empty queue, single-item queue, 20-item queue, mixed-reason queue, PHI-flagged row.

## Verification

Run `pnpm test apps/web/src/app/studio` — all green. Run `pnpm playwright test triage-needs-rescope` — happy path passes. Visit `/studio/operant/triage` in preview deploy, verify panel renders and depth count matches `SELECT COUNT(*) FROM "Task" WHERE status = 'NEEDS_RESCOPE'`.

## Dependencies

- **Blocks on OPER-60** — the `NEEDS_RESCOPE` enum value and `PlanningRejection` table must exist before this UI can render.

## Reasoning references

- Ratification memo: `docs/pdt/2026-07-28-admission-gate-v0.1.md` (PR #171)
- DOC descope memo: `docs/doc/2026-07-29-admission-gate-risk-classifier.md`
- State machine: `docs/task-state-machine.md`
- Related: OPER-60 (backend implementation)

Attachments

Loading attachments…

Comments

Loading comments…