---
path: /lilith-studio
surface: customer
domain: lilith
auth: signed-in + role:v1_editorial_producer (default)
source: apps/oshun/web/src/app/lilith-studio/page.tsx
status: walked
last_walked: >-
  2026-06-28 automated shell-contract walk (Playwright Chromium against real dev
  infra) — validated default/editorial and non-editorial access states, every V1
  continuity lane link and entry id, every V3 authoring surface
  tab/route/provenance contract, provenance drawer open/close behavior,
  authoring-contract and release-check list presence, the full embedded
  provenance inspector, the V1 Studio entry link, and the now-built
  `/lilith-studio/release` route. Evidence:
  `apps/oshun/web/e2e/lilith-studio-shell-smoke.spec.ts` and
  `apps/oshun/web/e2e/lilith-provenance-inspector-smoke.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
---

# Lilith Studio · authoring shell

## Purpose

V3 authoring shell for editorial users — operates Lilith V3 authoring surfaces
beside the V1 Studio editorial workflow, asset library, taxonomy, and
release/versioning lanes. Lets editorial work in Lilith without leaving the
Studio operating model. The page
(`apps/oshun/web/src/app/lilith-studio/page.tsx`) returns
`<LilithStudioShell />` directly; the shell is supplied by the route's
`layout.tsx` via `<ShellLayout active="studio">`, which also enforces the
editorial scope gate (see Data & contracts).

## Entry points

- **Direct URL / bookmark** — yes (signed-in)
- **From V1 Studio** — verify navigation from `/studio` to `/lilith-studio` for
  editorial roles
- **From sub-surfaces** — every sub-route under `/lilith-studio/*` has a
  back-link / "Open V1 Studio" link to this shell

## Layout regions

`LilithStudioShell`
(`apps/oshun/web/src/app/lilith-studio/LilithStudioShell.tsx`) renders inside
`<ShellLayout active="studio">` — the shell mounted by the route's `layout.tsx`,
not by the page — and has `data-testid="lilith-studio-shell"`.

- **Aside (left rail, `aria-label="Lilith Studio session"`)**:
  - kicker "V3 authoring", title "Lilith Studio", summary copy
  - role pill with `CheckCircle2` icon — "Signed in as <role label>" (default
    "V1 editorial producer")
  - meta list: Tenant `tara-studio-editorial`, Program `project-lilith-v3`,
    Workspace `lilith-authoring-main`
  - "Open V1 Studio" link → `/studio` (`data-testid="lilith-v1-studio-link"`)
- **Main**:
  - **Header**: eyebrow "V1 continuity with V3 surfaces", heading "Editorial can
    work in Lilith without leaving the Studio operating model.", lede; two
    header actions — "Review workflow" → `/studio/review-approval-workflows` and
    "Asset preview" → `/studio/asset-preview-pipeline`
  - **Continuity lanes** (`data-testid="lilith-studio-v1-continuity"`,
    `LILITH_STUDIO_V1_CONTINUITY_LANES`): four lanes, each
    `data-testid="lilith-studio-v1-lane"`:
    1. **Editorial workflow** (`ListChecks`) — 3 entries (program bootstrap,
       cross-domain pipeline, approval submission) → opens
       `/studio/review-approval-workflows`
    2. **Asset library** (`Boxes`) — 3 entries (tara venue, avatar + costume,
       saraswati media) → opens `/studio/yemaya/asset-library`
    3. **Taxonomy** (`Tags`) — 3 entries (tara room, persona + surface, music
       rights) → opens `/studio/hathor/journal-and-codex`
    4. **Versioning** (`GitBranch`) — 3 entries (draft branch, editorial
       candidate, release edition) → `/studio/hathor/entity-version-management`
  - **V3 authoring surface grid**:
    - **Surface tabs** (`role="toolbar"`,
      `data-testid="lilith-studio-surface-tab"`): eight tabs from
      `LILITH_STUDIO_V3_AUTHORING_SURFACES` — Scene authoring, Avatar and
      costume, Asana authoring, Persona dossiers, Music authoring, Concert
      authoring, Provenance inspector, Release bridge
    - **Surface detail** (`data-testid="lilith-studio-surface-detail"`) for the
      active tab — surface status "Shell ready", title, source- lane pill,
      description, route + provenance pills, "Open <surface>" link
      (`data-testid="lilith-studio-open-active-surface"`), authoring contract
      list, provenance summary aside with "Open provenance" button
      (`data-testid="lilith-studio-open-provenance"`), release- checks list
    - **Provenance drawer** (`data-testid="lilith-studio-provenance-drawer"`) —
      `role="dialog"`; opens when "Open provenance" is clicked; shows Source
      lane, Route contract, Release branch chain; close button
  - **`LilithProvenanceInspector`** (full surface) — surface ID
    `lilith-studio-shell`, asset list = all 8 V3 surfaces
  - **Status strip** — four KPI tiles: Workflow entries, Asset lanes, Taxonomy
    entries, V3 surfaces

## States

- [x] **Loading** — client component; hydrates immediately; shell DOM and
      provenance inspector hydration are asserted in browser coverage.
- [x] **Default role** — `v1_editorial_producer` (`LILITH_STUDIO_DEFAULT_ROLE`);
      `canOpenLilithStudioShell` returns true for editorial roles. Covered by
      `lilith-studio-shell-smoke.spec.ts`.
- [x] **Viewer role** — signed-in non-editorial scope renders
      `<LilithStudioAccessDenied />` with `data-v1-editorial-role="false"` and
      no authoring shell. Covered by `lilith-studio-shell-smoke.spec.ts`.
- [x] **Default active surface** — `scene-authoring`. Covered by
      `lilith-studio-shell-smoke.spec.ts`.
- [x] **Surface tab switched** — `activeSurfaceId` updates; previously open
      provenance drawer closes. Covered for all eight tabs by
      `lilith-studio-shell-smoke.spec.ts`.
- [x] **Provenance drawer open** — drawer visible with `data-open-provenance-id`
      set. Covered by `lilith-studio-shell-smoke.spec.ts`.
- [x] **Provenance drawer closed** — drawer absent; "Open provenance" button
      visible. Covered by `lilith-studio-shell-smoke.spec.ts`.
- [ ] **Offline** — page is mostly static client JSX with internal links; no
      fetch; renders offline

## Interactions

### Aside

- [x] **"Open V1 Studio" link** (`data-testid="lilith-v1-studio-link"`) — href
      `/studio`

### Header actions

- [x] **"Review workflow" link** — href `/studio/review-approval-workflows`
- [x] **"Asset preview" link** — href `/studio/asset-preview-pipeline`

### Continuity lanes (4 lanes)

- [x] **Workflow lane link** (icon button) — href
      `/studio/review-approval-workflows` (aria-label "Open Editorial workflow")
- [x] **Asset library lane link** — href `/studio/yemaya/asset-library`
- [x] **Taxonomy lane link** — href `/studio/hathor/journal-and-codex`
- [x] **Versioning lane link** — href `/studio/hathor/entity-version-management`
- [x] **Lane entry rows** — read-only items per lane, all 12 stable
      `data-lane-entry-id` values asserted.

### Surface tabs (8 tabs)

- [x] **Scene authoring tab** (`data-surface-id="scene-authoring"`,
      `aria-pressed`)
  - Function: sets `activeSurfaceId='scene-authoring'`; closes any open
    provenance
- [x] **Avatar and costume tab** (`avatar-costume`)
- [x] **Asana authoring tab** (`asana-authoring`)
- [x] **Persona dossiers tab** (`persona-dossiers`)
- [x] **Music authoring tab** (`music-authoring`)
- [x] **Concert authoring tab** (`concert-authoring`)
- [x] **Provenance inspector tab** (`provenance-inspector`)
- [x] **Release bridge tab** (`release-bridge`)

### Surface detail panel

- [x] **"Open <surface>" link**
      (`data-testid="lilith-studio-open-active-surface"`) — href
      `activeSurface.routeContract` (e.g., `/lilith-studio/scenes`)
- [x] **Authoring-contract list** — read-only checklist with stable
      `data-contract-count`
- [x] **"Open provenance" button**
      (`data-testid="lilith-studio-open-provenance"`)
  - Function: `setOpenProvenanceId(activeSurface.provenanceId)`
- [x] **Release-checks list** — read-only with stable `data-release-check-count`

### Provenance drawer (when open)

- [x] **Drawer dialog** (`role="dialog"`,
      `aria-labelledby="lilith-studio-provenance-title"`)
- [x] **Close button** (icon, `X`) — sets `openProvenanceId(null)`
- [x] **Audit list** — three checks (Source lane, Route contract, Release branch
      chain)

### Provenance inspector (full)

- [x] **`LilithProvenanceInspector`** — surface-level provenance browser (read
      its own walkthrough; here it lists all 8 V3 assets)

## Data & contracts

- **Reads**: in-file constants `LILITH_STUDIO_V1_CONTINUITY_LANES`,
  `LILITH_STUDIO_V3_AUTHORING_SURFACES`, `LILITH_STUDIO_SHELL_PROVENANCE_ASSETS`
  (derived)
- **Writes**: none — client state only (`activeSurfaceId`, `openProvenanceId`)
- **Realtime**: none
- **Caching**: client bundle
- **Auth/role check**: signed-in is enforced upstream; the editorial-role gate
  lives in `apps/oshun/web/src/app/lilith-studio/layout.tsx`, which calls
  `hasLilithStudioAccess()` (`src/lib/customer-role.ts`) requiring an `admin:*`
  or `studio:editorial` scope. With access it renders the
  `<ShellLayout active="studio">` + children; without it the layout renders
  `<LilithStudioAccessDenied />` in place of the authoring shell. This layout
  gate covers `/lilith-studio` and every `/lilith-studio/*` sub-route, so the
  per-page `data-v1-editorial-role` flag is now a display detail, not the
  enforcement point.

## Cross-references

- Sub-surfaces (one file per sub-route):
  - [`lilith-studio-asana.md`](./lilith-studio-asana.md)
  - [`lilith-studio-avatar-costume.md`](./lilith-studio-avatar-costume.md)
  - [`lilith-studio-concerts.md`](./lilith-studio-concerts.md)
  - [`lilith-studio-music.md`](./lilith-studio-music.md)
  - [`lilith-studio-personas.md`](./lilith-studio-personas.md)
  - [`lilith-studio-provenance.md`](./lilith-studio-provenance.md)
  - [`lilith-studio-release.md`](./lilith-studio-release.md)
  - [`lilith-studio-scenes.md`](./lilith-studio-scenes.md)
  - [`lilith-studio-scene-new.md`](./lilith-studio-scene-new.md)
  - [`lilith-studio-tara.md`](./lilith-studio-tara.md)
- Lilith design system index: [`lilith.md`](./lilith.md)
- Component sources:
  - `apps/oshun/web/src/app/lilith-studio/layout.tsx` (shell mount + scope gate)
  - `apps/oshun/web/src/app/lilith-studio/LilithStudioShell.tsx`
  - `apps/oshun/web/src/app/lilith-studio/LilithProvenanceInspector.tsx`
  - `apps/oshun/web/src/app/lilith-studio/LilithStudioAccessDenied.tsx`
  - `apps/oshun/web/src/app/lilith-studio/release/page.tsx`
- E2E evidence:
  - `apps/oshun/web/e2e/lilith-studio-shell-smoke.spec.ts`
  - `apps/oshun/web/e2e/lilith-provenance-inspector-smoke.spec.ts`

## Open questions / known gaps

- [x] Resolved: non-editorial roles are blocked by `layout.tsx`
      (`hasLilithStudioAccess()` → `admin:*`/`studio:editorial`), which renders
      `<LilithStudioAccessDenied />` instead of the shell for the whole
      `/lilith-studio/*` subtree
- [x] Resolved: the Release bridge surface (`/lilith-studio/release`) now has a
      route, release-state page, provenance inspector, and E2E coverage.
- [ ] Document how role can be changed from the default `v1_editorial_producer`
      — the shell takes `role` as a prop but the page wraps it without passing
      one
