---
path: /studio/review-approval-workflows
surface: studio
domain: collaboration
auth: signed-in + studio entitlement
source: apps/oshun/web/src/app/studio/review-approval-workflows/page.tsx
status: walked
last_walked:
  '2026-06-29 review-approval-workflows real-BFF addendum — Playwright now
  covers the admin-scoped route render, real approval-workflow catalog GET,
  default workflow POST/evaluate result, stage table semantics, quick-action
  targets, loading/error/unauthorized states, malformed JSON client block, live
  BFF 400 detail, pending submit lockout, and anonymous redirect-before-render.
  Evidence: WALKTHROUGH/results/v1-real-infra-run-2026-06-22.md §223; spec:
  apps/oshun/web/e2e/studio-review-approval-workflows.spec.ts. 2026-05-29
  automated runtime walk (Playwright headless) — render, /v1 data (2xx),
  console/page-errors, expected content, screenshot verified; live
  screen-reader, touch, offline, and telemetry-delivery checks pending a manual
  AT pass. Evidence: WALKTHROUGH/results/runtime-sweep-2026-05-29.md; content
  re-verified 2026-06-03 against current source'
---

# Studio · Review and Approval Workflows

## Purpose

Operate review and approval workflows as a live, admin-scoped BFF console. The
workspace (`StudioReviewApprovalWorkflowsWorkspace`) wires a single **Approval
Gate Lane** to the real multi-stage approval-gate evaluator via
`/v1/admin/studio/approval-workflow`: each stage is evaluated for quorum
(required approvals), required-role satisfaction, and reject-override; the
workflow rollup reports approved/pending/rejected stage counts and points at the
current (first non-approved) stage. This is a real domain computation, not a
simulation.

## Entry points

- **Studio root** (`/studio`) — discoverable as a collaboration workspace
- **Quick-action links** from sibling workspaces (asset preview pipeline,
  file/media ingestion, commenting/annotation, presence, real-time
  collaboration, activity/change feeds)
- **Direct URL / bookmark** — yes

## Layout regions

`page.tsx` mounts `ShellLayout active="studio"`, renders
`StudioReviewApprovalWorkflowsWorkspace`, then a single `panel` containing 6
sibling `quickAction` links. There is no Route Map panel on this page.

- **Shell header** — from `ShellLayout`
- **Workspace `<h1>`** — "Studio Review & Approval Workflows"
  (`WorkspaceHeading`), followed by a `data-review-approval-summary` paragraph
  describing the evaluation
- **Approval Gate Lane** (`<h2>` `data-aw-lane-heading` "Approval Gate Lane") —
  the catalog/loading/unauthorized/error states, the evaluate form, and the
  result
- **Quick-actions panel** — `panel` with 6 `quickAction` links to sibling
  workspaces (no self-link, no Route Map), anchored by
  `data-review-approval-quick-actions`,
  `data-review-approval-quick-action-count="6"`, and one
  `data-review-approval-quick-action="<workspace-id>"` per link

## States

- [x] **Loading** — `data-aw-loading` "Loading workflow evaluator…" while the
      GET catalog request is in flight (`outcome === null`)
- [x] **Unauthorized** — `data-aw-unauthorized` "Access restricted." on 401/403
      (admin scope required); shows the BFF message or "Studio admin scope
      required to evaluate the workflow."
- [x] **Error** — `data-aw-error` "Could not load the workflow evaluator." on
      any non-OK catalog response or network failure
- [x] **Ready (form)** — catalog loaded; `data-aw-enums` shows the stage-status
      and workflow-status counts (for the shipped catalog:
      `3 stage statuses · 3 workflow statuses`), and the `data-aw-form` form
      renders
- [x] **Result** — after a successful POST, `data-aw-result` renders the
      workflow headline + per-stage table
- [x] **Evaluate validation error** — `data-aw-evaluate-error` for invalid JSON,
      network unavailable, or a non-200 POST (shows the POST `detail`)
- [x] **Pending evaluate POST** — form flips `data-aw-form-state="evaluating"`,
      submit is disabled with `aria-disabled="true"` / `aria-busy="true"` and
      label "Evaluating workflow…" until the response returns
- [x] **Anonymous user** — redirected to
      `/welcome?redirect=%2Fstudio%2Freview-approval-workflows` before the
      workspace root renders

## Interactions

### Approval Gate Lane form

- [x] **Workflow** (`textarea` `data-aw-payload`, `aria-label="workflow json"`)
      — seeded with a default JSON payload:

      ```ts
      { stages: [{ stageId, name, requiredApprovals, requiredRole?, approvals: [{ approver, role, decision }] }] }
      ```

- [x] **Evaluate workflow** (`button` `data-aw-submit`, `type="submit"`) —
      `submitEvaluate` parses the JSON (rejecting invalid JSON with
      `data-aw-evaluate-error`) and POSTs to
      `/v1/admin/studio/approval-workflow/evaluate`
- [x] **Client-side malformed JSON block** — invalid JSON surfaces "Payload must
      be valid JSON." and does not send a POST
- [x] **Live BFF invalid payload branch** — valid JSON with `stages: []` reaches
      the BFF and surfaces `detail: "stages must be a non-empty array"`
- [x] **Touch target** — the textarea and submit button have measurable boxes;
      the submit control is ≥44px tall

### Result

- [x] **Headline** (`data-aw-headline`, `data-workflow-status`) — renders:
      `Workflow {workflowStatus} · current stage: <data-aw-current-stage> · {approvedStages} approved · {pendingStages} pending · {rejectedStages} rejected`
- [x] **Stages table** (`data-aw-stages`) — one `data-aw-stage-row` per stage
      with `data-stage-id`, `data-status`, plus `data-aw-stage-name`,
      `data-aw-stage-status`, `data-aw-stage-approvals`
      (`{approveCount} (need {approvalsNeeded})`), and `data-aw-stage-role-ok`
      cells
- [x] **Reject-override precedence** — the default fixture proves stage `st3`
      renders `rejected` even though it has one approval and needs zero more
      approvals, because one reviewer rejected; workflow status is therefore
      `rejected`
- [x] **Current-stage pointer** — the default fixture renders current stage
      `st2` (the first non-approved stage)

### Quick actions

- [x] Open Asset Preview Pipeline workspace
- [x] Open File and Media Ingestion workspace
- [x] Back to Commenting and Annotation System workspace
- [x] Back to Presence and Cursor Systems workspace
- [x] Back to Real-Time Collaboration Substrate workspace
- [x] Back to Activity and Change Feeds workspace

## Data & contracts

- **GET catalog**: `/v1/admin/studio/approval-workflow` — fetched on mount with
  `buildBffAuthHeaders()` and `cache: 'no-store'`; returns
  `{ stageStatuses[], workflowStatuses[] }`
- **POST verb**: `/v1/admin/studio/approval-workflow/evaluate` with body:

  ```ts
  { stages: [{ stageId, name, requiredApprovals, requiredRole?, approvals: [{ approver, role, decision }] }] }
  ```

  Returns a 200 `result` with `stages[]`, `workflowStatus`, `currentStageId`,
  and `summary{ approvedStages, pendingStages, rejectedStages }`, or a `detail`
  error.

- **Persistence**: evaluated workflows are stateless in this lane today. The
  browser posts a payload and renders the returned verdict; no persisted
  workflow id, audit id, or review queue mutation is returned by this BFF route.
- **Realtime**: none — request/response only
- **Client fetch**: direct `fetch` with `cache: 'no-store'`; no client-side
  caching
- **Auth/role check**: admin-scoped, fail-closed at the BFF (`admin:*` |
  `admin:studio`; 401/403); reached through the signed-in + studio route gate

## Cross-references

- Parent: [`../studio-overview.md`](../studio-overview.md)
- Sibling:
  [`studio-commenting-annotation-system.md`](./studio-commenting-annotation-system.md),
  [`studio-real-time-collaboration-substrate.md`](./studio-real-time-collaboration-substrate.md),
  [`studio-notification-center.md`](./studio-notification-center.md)
- Component:
  `apps/oshun/web/src/components/studio/StudioReviewApprovalWorkflowsWorkspace.tsx`
- BFF route: `apps/oshun/bff/src/routes/admin-studio-approval-workflow.ts`
- BFF store: `apps/oshun/bff/src/studio/approval-workflow-store.ts`

## E2E coverage

- [`apps/oshun/web/e2e/studio-review-approval-workflows.spec.ts`](../../../apps/oshun/web/e2e/studio-review-approval-workflows.spec.ts)
  — signed-in admin `/studio/review-approval-workflows` render; real
  approval-workflow catalog GET with admin bearer; default workflow evaluate
  POST through the live BFF; rejected workflow headline/current-stage/summary;
  all four default stage rows with status, approval counts, and role
  satisfaction; six quick-action links and target sizes; catalog loading/error;
  client-side malformed JSON zero-POST; live BFF 400 invalid-payload detail;
  pending POST disabled/busy state; non-admin 403 fail-closed; anonymous
  redirect-before-render.
- [`apps/oshun/web/e2e/studio-route-render-smoke.spec.ts`](../../../apps/oshun/web/e2e/studio-route-render-smoke.spec.ts)
  — broad Studio route render and accessibility smoke coverage includes this
  route, but does not replace the focused real-BFF workflow spec above.

## Open questions / known gaps

- [x] Confirm whether evaluated workflows persist their verdicts, or whether the
      lane stays a stateless evaluator — today it is stateless request/response
      only
- [x] Document the reject-override precedence relative to quorum and
      required-role satisfaction — reject wins first; otherwise quorum plus
      required-role approval is needed for a stage to become approved
- [ ] Live screen-reader and touch-device manual passes remain outside the
      automated route spec
