---
path: /studio/compose
surface: studio
domain: authoring
auth: anon (explicit public route)
source: apps/oshun/web/src/app/studio/compose/page.tsx
status: walked
last_walked:
  '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; body
  re-derived 2026-06-03 from current source (lane-console architecture)'
---

# Studio · Compose

## Purpose

Compose Living Scenes from kept segments and template anchors using
`@yemaya/living-scenes-runtime/compose-assist`. The route is explicitly public
(middleware adds `/studio/compose` to `PUBLIC_PATHS`); tier and tenant policy
are enforced inside the route and the validation library. The page
(`ComposeClient`) is a single client component with a 3-segment seeded timeline
(breath / sky / lesson-step), drag-and-drop reorder, boundary-technique picker,
scrub-preview, Compose Assist, promotion-gates panel, and audit trail.

## Entry points

- **Studio root** (`/studio`) — promoted via the "Open Compose" quick action
  where present
- **Public link from marketing surfaces** — `/studio/compose` is in the
  middleware `PUBLIC_PATHS` set
- **Direct URL / bookmark** — yes
- **Deep link from generation surfaces** — once a kept Living Scene exists, it
  can route here

## Layout regions

`page.tsx` is a thin wrapper:
`export default function StudioComposePage() { return <ComposeClient /> }`. The
`ComposeClient` renders its own layout without `ShellLayout`:

- **Outer `<main>`** with `data-compose-ready` + `data-compose-surface`
- **Header strip**: Studio eyebrow + "Compose" h1 + tier state
  (`data-compose-tier-state` with `data-compose-allowed` and
  `data-compose-routes-to-studio-editor` attrs)
- **Workspace grid** (`data-compose-workspace`): two columns on `lg`
  - **Left column**:
    - **Segment library** (`data-segment-library`): three demo segments
      (`seg-breath`, `seg-sky`, `seg-lesson`) as cards
    - **Timeline** (`data-compose-timeline`): ordered list of segments with grip
      handle, move-up / move-down / alternatives buttons, drag-and-drop reorder,
      boundary-technique select per segment
    - **Alternatives panel** (`data-alternatives-panel`) — appears when a
      segment is long-pressed or context-menu'd
  - **Right column** (aside):
    - **Scrub preview** (`data-scrub-preview`) — range input drives which
      segment renders in the preview pane
    - **Compose Assist** (`data-compose-assist`) — "Accept suggestion" button
      appends a new `seg-assist-N` segment with match-action technique
    - **Promotion gates** (`data-score-promotion`) — continuity pre-score,
      workflow policy state, reduced-motion technique map, blocked message
    - **Audit trail** (`data-manual-override-log`) — ordered list of moves,
      technique changes, cites

## States

- [ ] **Loading** — server-rendered + client hydration; `data-compose-ready`
      flips to "true" after mount
- [ ] **Initial seeded composition** — three segments rendered in default order;
      promotion gates show ✓ allowed (default fixtures pass)
- [ ] **Reorder via move-up / move-down** — clicking arrows reorders segments;
      audit log appends a "Moved … to position N" entry
- [ ] **Reorder via drag-and-drop** — drag a segment over another and drop;
      audit log appends "Drag-arranged Segment N to position M"
- [ ] **Boundary technique change** — selecting from the per-segment select
      updates technique; audit log entry "<segmentId> boundary set to <Label>"
- [ ] **Alternatives via right-click / long-press** — alternatives panel shows
      first 4 technique options; clicking applies + dismisses panel
- [ ] **Compose Assist accept** — appends `seg-assist-1` (then `-2`, …) with
      `match-action` technique; audit log entry "<segmentId> inserted from
      Compose Assist with Match action"
- [ ] **Scrub preview** — moving the range updates which segment narration
      renders inside the preview card
- [ ] **Promotion blocked** — when `promoteCompositionDraft` throws (e.g., bad
      policy combo), the promotion panel shows `Blocked` + error message
- [ ] **Standalone PWA** — usable
- [ ] **Reduced motion** — verify any animation paths honor preference

## Interactions

### Header tier state

- [ ] **Tier badge** — read-only display of
      `<tier> · <segmentCount>     Segments · <durationSeconds>s`;
      `data-compose-allowed` mirrors `validation.allowed`

### Segment library cards

- [ ] **Per-segment card** — read-only summary; `data-segment-source`,
      `data-rights-tags` attributes for diagnostic walkthroughs

### Timeline rows (per segment)

- [ ] **Move up** (button `aria-label="Move <segmentId> up"`,
      `data-move-segment="up"`)
  - Disabled when: `index === 0`
- [ ] **Move down** (button `aria-label="Move <segmentId> down"`,
      `data-move-segment="down"`)
  - Disabled when: last segment
- [ ] **Alternatives** (button
      `aria-label="Show alternatives for     <segmentId>"`,
      `data-technique-alternatives-toggle`)
- [ ] **Boundary technique select** (`data-technique-picker=<segmentId>`)
- [ ] **Drag handle** (grip icon; `draggable` on `<li>`) — drag + drop reorder

### Alternatives panel

- [ ] **Per-option button** (`data-alternative-option=<techniqueId>`) — applies
      technique and closes panel

### Scrub preview

- [ ] **Scrub range** (`type="range"`, 0-100) — updates `scrub` state and drives
      which segment narrates

### Compose Assist

- [ ] **"Accept suggestion"** (button `data-compose-assist-accept`)
  - Function: appends a new assist segment with `match-action` technique
  - Telemetry: implicit via audit log only

### Promotion gates

- [ ] **Continuity pre-score / Workflow policy / Reduced motion** — read-only
- [ ] **Blocked message** — error string from `promoteCompositionDraft`

### Audit trail

- [ ] **Ordered list** — newest entry at the end

## Data & contracts

- **Reads**: in-page seeded `INITIAL_SEGMENTS`, `ALL_TECHNIQUE_SUGGESTIONS`,
  `createDraft`
- **Writes**: none — all mutations are local React state
- **Validation library**: `@yemaya/living-scenes-runtime/compose-assist` —
  `validateCompositionDraft`, `promoteCompositionDraft`,
  `filterComposeAssistTechniqueSuggestions`
- **Realtime**: none
- **Auth/role check**: middleware allows anonymous (PUBLIC_PATHS); tier + tenant
  policy enforced inside the validator

## Cross-references

- Parent: [`../studio-overview.md`](../studio-overview.md)
- Sibling: [`studio-authoring.md`](./studio-authoring.md),
  [`../generation/studio-generation-living-scene.md`](../generation/studio-generation-living-scene.md)
- Source: `apps/oshun/web/src/app/studio/compose/ComposeClient.tsx`

## Open questions / known gaps

- [ ] Confirm whether anonymous access truly survives in production or whether
      the public route is staged behind a feature flag
- [ ] Document where the validator obtains the real tier / tenant policy from at
      request time (currently all values are seeded)
