Context. surface studio · domain authoring · route /studio/compose · auth anon (explicit public route) · source apps/oshun/web/src/app/studio/compose/page.tsx
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)
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/composeis in the middlewarePUBLIC_PATHSset - 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>withdata-compose-ready+data-compose-surface - Header strip: Studio eyebrow + "Compose" h1 + tier state
(
data-compose-tier-statewithdata-compose-allowedanddata-compose-routes-to-studio-editorattrs) - Workspace grid (
data-compose-workspace): two columns onlg- 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
- Segment library (
- 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 newseg-assist-Nsegment 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
- Scrub preview (
- Left column:
States#
- Loading — server-rendered + client hydration;
data-compose-readyflips 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 "
boundary set to - 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, …) withmatch-actiontechnique; audit log entry "inserted from Compose Assist with Match action" - Scrub preview — moving the range updates which segment narration renders inside the preview card
- Promotion blocked — when
promoteCompositionDraftthrows (e.g., bad policy combo), the promotion panel showsBlocked+ 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-allowedmirrorsvalidation.allowed
Segment library cards#
- Per-segment card — read-only summary;
data-segment-source,data-rights-tagsattributes for diagnostic walkthroughs
Timeline rows (per segment)#
- Move up (button
aria-label="Move <segmentId> up",data-move-segment="up")- Disabled when:
index === 0
- Disabled when:
- 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;
draggableon<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) — updatesscrubstate and drives which segment narrates
Compose Assist#
- "Accept suggestion" (button
data-compose-assist-accept)- Function: appends a new assist segment with
match-actiontechnique - Telemetry: implicit via audit log only
- Function: appends a new assist segment with
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 - Sibling:
studio-authoring.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)