Context. surface customer · domain atelier · route /atelier/motion · auth signed-in · source apps/oshun/web/src/app/atelier/motion/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; refreshed 2026-06-28 in apps/oshun/web/e2e/atelier-motion-to-cinematheque.spec.ts: route anchors, fixture-derived timeline/takes, placeholder action semantics, read-only playhead behavior, real /atelier/video handoff, reduced-motion static state, offline-stable loaded markup, and 390 px no-overflow stack; refreshed 2026-06-30 with explicit URL-scoped motion→Cinematheque handoff context and inbound Cinematheque panel assertions.
Purpose#
Animated 3D atelier — "a figure, set in motion." Five-step workflow: imported
form → rigging → takes capture → bake keyframes → kept plate clip. Server-
rendered by apps/oshun/web/src/app/atelier/motion/page.tsx via
getAtelierMotion() → <AtelierMotion data>.
Entry points#
- Atelier index (
/atelier) — fifth room card - Lilith index (
/lilith) — section VIII card 46 "XIV · Stage of Motion" - From Forms atelier (intended) — "Open in Stage · rig" exists on
/atelier/forms, but is still anLBtnplaceholder with nohref/handler - Direct URL / bookmark — yes (signed-in)
Layout regions#
AtelierMotion (apps/oshun/web/src/components/lilith/atelier.tsx) renders
inside LWebShell with LCustomerNav active="explore".
The route root exposes data-atelier-motion-page with
data-atelier-motion-track-count="5", data-atelier-motion-take-count="3",
data-atelier-motion-current-take="take 02",
data-atelier-motion-current-frame="74", and
data-atelier-motion-bake-progress="41". Its body exposes
data-atelier-motion-layout; below 980 px the layout stacks to one column and
the 390 px Playwright path verifies no horizontal overflow.
- Plate head (
LAPlateHead no="XIV"):- kicker: "The atelier · stage of motion"
- title:
A figure, <em>set in motion</em>. - right chips: "Take 02 · 06s", "Rig · 24 joints", "Baking · 41%" (accent)
- Three-column body (
230px 1fr 320px):- Left rail (
LAStepRail, current=4): five steps — "Form · imported", "Rig · skeleton, weights", "Takes · capture", "Baking · keyframes", "Plate · clip kept" - Center:
- Stage viewport (
Stage · viewport): grid-paper background; 16:9 SVG showing a rigged stick figure with onion-skin echo poses at 0.15 / 0.3 / 0.55 opacity, accent IK markers on hands and heels, a horizon line, and labelsSTAGE · PERSP · 38 mm/f · 074 / 144(frame counter) /RIG · 24 joints · IK on heels - Timeline (
Timeline · 06 s · 24 fps · 144 frames): frame ruler (000 / 012 / 024 / … / 144) plus 5 keyframe tracks fromdata.timelineTracks(Spine · root, Head · pitch, Hand L · IK, Hand R · IK, Breath · param) with diamond keyframe markers (frame 72 highlighted accent); playhead at 51% labelledf.074 · 03:04 - Bake progress card (accent border):
LAProgress value=0.41 eta="03:42", italic caption "Baking keys → motion clip · take 02", counter "59 / 144 frames" - Action row (
data-atelier-motion-actions): "New take" and "Re-target rig" are disabled-placeholderLBtncontrols; "Open in Cinematheque" is a real primary link to/atelier/video?from=motion&shot=sh-03&take=take-02&voice=foundry-take-03&light=forms-cream-north
- Stage viewport (
- Right:
- Clip · take 02 card (accent border) with
LFieldrows: Length "06.000 s", Frames "144 · 24 fps", Joints "24 · IK heels", Source "Capture · live actor", Curves "bezier · clamped", Loop "Yes · 1 cycle in 4 s", Format "usd · alembic · glb" LRule- Other takes list from
data.takes(take 01 archived, take 02 kept — accent, take 03 draft) - Compose · downstream panel — italic note "Sends to Cinematheque (sh
03), inherits voice (foundry · take 03) and light (forms · cream-north)."
The panel now exposes
data-atelier-motion-handoff-source="motion",data-atelier-motion-handoff-state="url-context",data-atelier-motion-handoff-shot="sh 03",data-atelier-motion-handoff-take="take 02",data-atelier-motion-handoff-voice="foundry · take 03", anddata-atelier-motion-handoff-light="forms · cream-north".
- Clip · take 02 card (accent border) with
- Left rail (
States#
- Loading boundary absence — no segment
loading.tsxexists underapps/oshun/web/src/app/atelier/motion;getAtelierMotion()currently returns the synchronous fixture through the server component - Populated — fixture provides 5 timeline tracks + 3 takes, asserted via
data-atelier-motion-track-count="5"anddata-atelier-motion-take-count="3" - Mid-bake — accent progress card exposes
data-atelier-motion-bake-progress="41",data-atelier-motion-bake-complete-frames="59", total frames144, and ETA03:42 - Kept take selected — take 02 row exposes
data-atelier-motion-take-status="kept"anddata-atelier-motion-take-selected="true" - Error boundary absence — no route-local
error.tsxexists; failures fall through the inherited app boundary - Offline — after the route is loaded, browser offline mode keeps the static stage, timeline, playhead, and clip markup visible
- Reduced motion — the route has zero subtree animations under
data-atelier-motion-page; onion-skin opacity and bake progress are static rendered values - Mobile stack — 390 px viewport stacks rail → stage/timeline/bake → clip/takes with no horizontal overflow
Interactions#
Stage viewport#
- Viewport — read-only SVG render; clicking it does not scrub, rotate,
mutate
data-atelier-motion-playhead-frame, or navigate
Timeline#
- Frame ruler — clicking frame
120leaves the playhead at f.074 / 51%; no click handler is wired - Spine · root track (7 keys at frames 0, 24, 48, 72, 96, 120, 144;
curve
ease) - Head · pitch track (5 keys; curve
ease) - Hand L · IK track (5 keys; curve
step) - Hand R · IK track (5 keys; curve
step) - Breath · param track (7 keys; curve
sine) - Accent keyframe (frame 72) — keyframes at frame 72 expose
data-atelier-motion-keyframe-selected="true"; tracks without frame 72 expose zero selected keyframes - Playhead marker (at 51%, label
f.074 · 03:04) — marker exposesdata-atelier-motion-playhead-frame="74"anddata-atelier-motion-playhead-position="51"; dragging/clicking is not wired
Center actions#
- "New take" button (ghost) — placeholder
LBtnwitharia-disabled="true",data-placeholder="true", tooltipNo handler wired — placeholder, and no navigation - "Re-target rig" button (ghost) — same disabled-placeholder semantics
- "Open in Cinematheque" button (primary) — real link with
href="/atelier/video?from=motion&shot=sh-03&take=take-02&voice=foundry-take-03&light=forms-cream-north"; Playwright follows it to the Cinematheque route and verifies the inbound handoff context panel
Right column#
- Clip metadata — read-only panel
data-atelier-motion-clip-panelwith take 02, length, frames, joints, source, curves, loop, and format rows - Take rows (3, from
data.takes):- take 01 — archived, muted
- take 02 — kept, accent, selected
- take 03 — draft
- Compose · downstream panel — read-only note referencing Cinematheque (sh 03), foundry (take 03), forms (cream-north), with stable URL-context handoff attributes for target shot, current motion take, inherited voice, and inherited light
Data & contracts#
- Reads:
getAtelierMotion()fromapps/oshun/web/src/lib/lilith-data/atelier-depth.ts— returnsAtelierMotionDatawithtimelineTracksandtakes(planned BFF at/atelier/plates/motion) - Writes: none in current static composition
- Realtime: none
- Caching: server fetch
- Auth/role check: middleware enforces signed-in
Cross-references#
- Atelier index:
atelier.md - Sibling rooms:
atelier-image.md,atelier-audio.md,atelier-video.md,atelier-forms.md - Upstream —
AtelierForms' "Open in Stage · rig" CTA targets this room;AtelierAudiovoice take 03 is inherited;AtelierFormscream-north light study is inherited - Downstream — "Open in Cinematheque" routes to
atelier-video.md - Component source:
apps/oshun/web/src/components/lilith/atelier.tsx(AtelierMotion) - Data:
apps/oshun/web/src/lib/lilith-data/atelier-depth.ts
E2E coverage#
Spec: apps/oshun/web/e2e/atelier-motion-to-cinematheque.spec.ts (4 tests)
Coverage depth: deep for the current static/read-only route contract: fixture-derived stage/timeline/take rendering, disabled-placeholder semantics, read-only playhead non-mutation, real Cinematheque navigation, mobile no-overflow, reduced-motion static state, and offline-stable loaded markup. Remaining gaps are product gaps rather than test gaps: no route-local loading/error boundaries, no new-take/retarget write flow, and no durable cross-room project-state object beyond the URL-scoped handoff context.
Open questions / known gaps#
- Confirm whether the timeline supports keyframe dragging or only visual display — current route is read-only; playhead remains f.074 after ruler, selected-keyframe, and viewport clicks
- Document the cross-room handoff state — the "Compose · downstream" note now exposes a URL-scoped handoff object and sends it to Cinematheque, where it renders as an inbound context panel. This is not yet durable project persistence.
- Verify whether "New take" creates a new entry in
data.takesor is a no-op — current control is an honest disabled-placeholderLBtn