---
path: /operator/studio
surface: operator
domain: operator (studio — editorial review queue and approval workspace)
auth: signed-in
source: apps/oshun/web/src/app/operator/studio/page.tsx
status: walked
last_walked:
  '2026-06-29 operator-studio route-DOM addendum — Playwright now covers the
  shipped /operator/studio queue rows, selected TR-047 artifact pane,
  waveform/transcript, review notes, citation statuses, decision-row entry
  point, static queue-row boundary, and anonymous redirect-before-render.
  Evidence: WALKTHROUGH/results/v1-real-infra-run-2026-06-22.md §222; spec:
  apps/oshun/web/e2e/operator-studio-surface.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'
---

# Operator · Studio · review

## Purpose

The editorial review queue and approval workspace. Three-column register layout:
review queue (left), the artifact under review (center), and an approval /
annotation pane (right). Robots-noindex. Backed by `getStudioReview()` and
rendered by `StudioReview` (line 42 of the 665-LOC
`apps/oshun/web/src/components/lilith/operator.tsx`).

## Entry points

- **From `/operator`** (index) — operator-surface card
- **Profile menu → Operator → Studio**
- **Direct URL** — session-gated only (`proxy.ts` redirects sessionless →
  `/welcome?redirect=`); no operator-role gate in the proxy
- **From `/operator/admin`** inbox detail — editorial INC links here with the
  artifact code preselected
- **Studio editorial workflow notification** — push/email handoff

## Layout regions

`StudioReview` uses Lilith primitives (`LGraphPaper`, `LOperatorBar`, `LCode`,
`LChip`, `LEyebrow`, `L`, `SERIF_L`, `SANS_L`).

- **Top**: `LOperatorBar` — product="Lilith Studio", section="Editorial",
  breadcrumb="Tara · Path IV › Session 03 · review" (the `›` is
  `CRUMB_SEP = '  ›  '`, operator.tsx:38/48; the inner dots are literal)
- **Main 3-column grid** (300px / 1fr / 320px; `data-operator-studio-layout`):
  - **Queue column (300px)**:
    - Eyebrow accent "In review · 7" (derived from `data.queue.length`)
    - List of review items — each row has:
      - `LCode` artifact code (e.g., `TR-047`)
      - Status code (e.g., `s3`, uppercase, tracked-out, muted)
      - Italic Garamond 14 title (e.g., "On the illusion of a separate self")
      - `LEyebrow` byline (e.g., teacher name)
      - Stable attributes: `data-operator-studio-queue-row`,
        `data-artifact-code`, `data-review-status`, and `data-selected`
      - Selected row paints with `r.here` flag (accent border + bg). Today these
        rows are static orientation rows: they have pointer styling but no
        link/button/tabindex/selection handler.
  - **Artefact column (1fr)** — bordered card, padded 28:
    - Top band (flex space-between):
      - Left: `LCode` "TR-047 · Tara · Path IV · §03"
        (`data-operator-studio-artifact-code`)
      - Title (Garamond 26 medium, italic ref): "On the illusion of a separate
        self" (`data-operator-studio-artifact-title`)
      - Eyebrow byline: "Saro Imani · 12:00 · v3 · uploaded 14:22"
      - Right: chip row — `LChip` "Audio · 12:03", `LChip` "Transcript",
        `LChip accent` "Confidence · strong"
    - Player band (`data-operator-studio-waveform`): paper-background card with
      a 600×48 SVG waveform, `aria-hidden="true"`, 120 deterministic rects via
      sin/cos seeding, and static time labels `0:00`, `2:14 · current marker`,
      and `12:03`
    - Transcript (`data-operator-studio-transcript`): four visible timecode
      passages at 00:00, 00:42, 02:14, and 03:08
    - `StudioReviewDecisionRow` for artifact `TR-047`
  - **Detail column (320px)**:
    - Review notes: four rows with author/role/mark attributes
    - Source citations: three rows with citation code and status attributes

## States

- [ ] **Loading** — `getStudioReview()` server-side; no route-local
      `loading.tsx`
- [x] **Anonymous user** — redirected to `/welcome?redirect=` by `proxy.ts`
- [x] **Signed-in user** — renders (session-gated only; no operator-role gate in
      the proxy)
- [x] **Queue with selection** — TR-047 selected row is painted and exposes
      `data-selected="true"`; the artefact/detail panes render TR-047
- [ ] **Empty queue** — copy / empty state (verify)
- [x] **Audio-bearing artefact** — waveform and transcript region are visible;
      there are no real play/pause/scrub controls yet
- [ ] **Text-only artefact** — player likely hidden / replaced
- [x] **Confidence levels** — shipped fixture renders `Confidence · strong`;
      partial/weak variants are not present in this route fixture
- [ ] **`getStudioReview()` fails** — no local route `error.tsx` exists under
      `apps/oshun/web/src/app/operator`
- [ ] **Audio player offline** — disabled with explanation (verify)

## Interactions

### Queue column

- [x] **Each queue row** — renders the seven fixture artifacts with stable code,
      status, title, byline, selected-state attributes, and ≥44px row height
  - Keyboard: no row exposes link/button/`tabindex`; arrow-key navigation is not
    wired today
  - SR: no explicit row role/name is wired today; rows remain plain text
  - Selected: `r.here === true` paints TR-047 with accent border
  - Static boundary: clicking TR-048 does not change selection or the artifact
    pane
- [x] **`LCode`** (artefact code) — non-interactive
- [x] **Status label** — non-interactive text label (`in review`, `changes`,
      `sources`, `audio`, `copy`, `fact-check`, `queued`)

### Artefact column

- [x] **`LCode` header** "TR-047 · Tara · Path IV · §03" — non-interactive
- [x] **Chip row** — `Audio · 12:03`, `Transcript`, `Confidence · strong` are
      visual labels, not filters/actions
- [x] **Waveform SVG** — `aria-hidden="true"`; 120 rects; non-interactive visual
- [x] **Audio player controls** — no real play/pause/scrub/volume control is
      wired; only the static waveform and time labels are present
- [x] **Transcript** — four static timecoded paragraphs are visible
- [x] **Decision row** — `StudioReviewDecisionRow` exposes Mark for revoice,
      Adjust pacing, Re-cite source, Request changes, and Approve · publish
      06:00 controls

### Detail column (right pane)

- [x] **Review notes** — four note rows render M. Doss / Editor at 00:42, A.
      Patel / Theology at 02:14, R. Lee / Audio at 03:08, and Lilith / Auto at
      `—`
- [x] **Source citations** — `SN 22.59` verified, `MN 22` pending, and
      `Harris, S.` verified render as static citation rows
- [ ] **History view** — no separate review-history timeline is present beyond
      notes/citations and the local decision row

## Data & contracts

- **Reads**: `getStudioReview()` from `@/lib/lilith-data/operator-depth`
  returning `StudioReviewData` (queue with `c/s/t/by/here` fields, etc.). NOTE:
  this is a static in-process fixture (returns `getStudioReviewFixture()`
  directly), **not** a BFF call — the module is headed "fixture today, BFF
  tomorrow"
- **Writes**: `StudioReviewDecisionRow` stores annotation toggles, approve, and
  request-changes verdict locally in `localStorage` under
  `oshun.studio.review.decision.v1.<artifactId>`, and its approve/request-
  changes branches create and patch admin release streams through the BFF. The
  returned release-stream id/status are persisted for reload durability, and the
  corresponding `admin.editorial_release_stream.*` audit events are covered in
  the editorial journey specs.
- **Realtime**: review-state changes from other operators — Psyche channel
  (verify)
- **Caching**: server-rendered; no SW caching
- **Auth/role check**: session-gated only (`proxy.ts` redirects sessionless →
  `/welcome?redirect=`); no operator-role gate in the proxy

## Cross-references

- Parent: [`operator-overview.md`](./operator-overview.md)
- Sibling operator routes:
  - [`operator-admin.md`](./operator-admin.md)
  - [`operator-metis.md`](./operator-metis.md)
  - [`operator-tenant.md`](./operator-tenant.md)
- Customer-side Tara:
  [`../customer/03-tara/tara.md`](../customer/03-tara/tara.md)
- Studio (editorial workspace):
  [`../studio/studio-overview.md`](../studio/studio-overview.md)
- Component source: `apps/oshun/web/src/components/lilith/operator.tsx` (line
  42: `StudioReview`)
- Feature spec: [`V1/features.md`](../../V1/features.md)
- Architecture:
  [`V1/ARCHITECTURE.md`](../../V1/ARCHITECTURE.md#operator-surfaces)

## E2E coverage

- [`apps/oshun/web/e2e/operator-studio-surface.spec.ts`](../../apps/oshun/web/e2e/operator-studio-surface.spec.ts)
  — signed-in `/operator/studio` render; operator bar and breadcrumb; all seven
  queue rows with code/status/title/byline/selected attributes and ≥44px row
  height; static queue-row boundary; TR-047 artifact code/title/byline/chips;
  waveform `aria-hidden` + 120 rects + static time labels; transcript
  timecodes/copy; decision-row control entry point; four note rows; three
  citation rows/statuses; anonymous redirect-before-render.
- [`apps/oshun/web/e2e/editorial-release-streams.spec.ts`](../../apps/oshun/web/e2e/editorial-release-streams.spec.ts)
  — deeper `StudioReviewDecisionRow` local approve/request-changes branches and
  reload durability, plus the release-stream BFF API/audit path and
  invalid-mutation guards.

## Open questions / known gaps

- [x] Walk the detail column (lines after the artefact column) to enumerate the
      approval action set and annotation UI
- [x] Confirm the relationship between this surface and
      `/studio/review-approval-workflows` — they are distinct surfaces today:
      `/operator/studio` is the static editorial review queue with a
      localStorage-backed `StudioReviewDecisionRow`;
      `/studio/review-approval-workflows` is a stateless admin-scoped
      approval-gate evaluator backed by `/v1/admin/studio/approval-workflow`
- [x] Document audio player controls — none are wired on this surface today;
      only the static waveform and time labels render
- [x] Verify keyboard navigation in the queue — no row-level
      link/button/tabindex or arrow-key navigation is wired today
- [ ] An operator-role gate appears absent — `proxy.ts` only redirects
      sessionless users to `/welcome`; no role check in the proxy or page
      component (possible gap vs intended scope)
