V1 Web PWA · Surface walkthrough

Operator · Studio · review

A per-surface walkthrough of the V1 Web PWA operator surface: layout, states, interactions, data, and cross-references.

walked
9sections6 minread

On this page

Context. surface operator · domain operator (studio — editorial review queue and approval workspace) · route /operator/studio · auth signed-in · source apps/oshun/web/src/app/operator/studio/page.tsx

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

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#

  • LoadinggetStudioReview() server-side; no route-local loading.tsx
  • Anonymous user — redirected to /welcome?redirect= by proxy.ts
  • Signed-in user — renders (session-gated only; no operator-role gate in the proxy)
  • 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)
  • 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
  • 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#

  • 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
  • LCode (artefact code) — non-interactive
  • Status label — non-interactive text label (in review, changes, sources, audio, copy, fact-check, queued)

Artefact column#

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

Detail column (right pane)#

  • 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
  • Source citationsSN 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#

E2E coverage#

  • 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 — 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#

  • Walk the detail column (lines after the artefact column) to enumerate the approval action set and annotation UI
  • 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
  • Document audio player controls — none are wired on this surface today; only the static waveform and time labels render
  • 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)