V1 Web PWA · Surface walkthrough

Atelier · XIV · Stage of Motion

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

walked
9sections6 minread

On this page

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 an LBtn placeholder with no href/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 labels STAGE · 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 from data.timelineTracks (Spine · root, Head · pitch, Hand L · IK, Hand R · IK, Breath · param) with diamond keyframe markers (frame 72 highlighted accent); playhead at 51% labelled f.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-placeholder LBtn controls; "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
    • Right:
      • Clip · take 02 card (accent border) with LField rows: 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", and data-atelier-motion-handoff-light="forms · cream-north".

States#

  • Loading boundary absence — no segment loading.tsx exists under apps/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" and data-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 frames 144, and ETA 03:42
  • Kept take selected — take 02 row exposes data-atelier-motion-take-status="kept" and data-atelier-motion-take-selected="true"
  • Error boundary absence — no route-local error.tsx exists; 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 120 leaves 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 exposes data-atelier-motion-playhead-frame="74" and data-atelier-motion-playhead-position="51"; dragging/clicking is not wired

Center actions#

  • "New take" button (ghost) — placeholder LBtn with aria-disabled="true", data-placeholder="true", tooltip No 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-panel with 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() from apps/oshun/web/src/lib/lilith-data/atelier-depth.ts — returns AtelierMotionData with timelineTracks and takes (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; AtelierAudio voice take 03 is inherited; AtelierForms cream-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.takes or is a no-op — current control is an honest disabled-placeholder LBtn