Context. surface customer · domain atelier · route /atelier · auth signed-in · source apps/oshun/web/src/app/atelier/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' '2026-06-25 by Codex (room-grid and house-rule row-cell anchors covered), against commit HEAD' '2026-06-26 by Codex (auto-fit room grid and 390 px no-overflow coverage), against commit HEAD
Purpose#
The Atelier index — "a small workshop attached to the reading desk." Five rooms
of making (Image / Audio / Video / Forms / Motion) presented as a single
broadsheet card row, plus three house rules that govern every generative surface
(only from a line; provenance shown; slow by default). Server-rendered by
apps/oshun/web/src/app/atelier/page.tsx via getAtelierIndex() →
<AtelierIndex data>. When opened with ?sceneId=<id>, the route also reads
the signed-in member's real Atelier scenes from /v1/atelier/scenes and renders
an active-draft handoff only if that scene belongs to the current account.
Entry points#
- Lilith index (
/lilith) — section VIII card 41 "Atelier · index" - Customer nav (
LCustomerNav active="explore") — the Atelier shell is reachable from the explore tab in the Lilith design system - Atelier scene starter (
/atelier/new) — accepted state "Open the composer" link lands on/atelier?sceneId=<new scene id> - Direct URL / bookmark — yes (signed-in)
Layout regions#
AtelierIndex (apps/oshun/web/src/components/lilith/atelier.tsx, line 1985)
renders inside LWebShell with LCustomerNav active="explore".
- Masthead (
LMasthead):- left: "The atelier · est. 2026"
- right: "Tuesday, the sixth of May"
- kicker: "Five rooms of making"
- title:
The <em>atelier</em>.
- Lede: centred italic copy about the workshop
- Active draft band (conditional) — if
sceneIdis present, a full-width top/bottom-rule band appears below the lede:- found:
data-atelier-active-scene="found", "Active draft", title, scene id, state, "Clear" (/atelier) and "Start another" (/atelier/new) - missing/unowned:
data-atelier-active-scene="missing", "Draft unavailable.", explanatory copy, and "Clear"
- found:
LDoubleRule- Rooms grid (auto-fit card shelf, 1px border) — one card per room from
data.rooms:- X · Camera Obscura · image · href
/atelier/image - XI · Foundry of Voices · audio · href
/atelier/audio - XII · Cinematheque · video · href
/atelier/video - XIII · Forms · three D · href
/atelier/forms - XIV · Stage of Motion · animated 3 D · href
/atelier/motion
- X · Camera Obscura · image · href
- House rules grid (3 columns): three rules from
data.houseRules— "Only from a line.", "Provenance shown.", "Slow by default."
The route root exposes data-atelier-index-page with
data-atelier-index-room-count="5" and
data-atelier-index-house-rule-count="3". The room grid exposes
data-atelier-room-grid / data-atelier-room-grid-count; each room card
exposes data-atelier-room-card=<Roman room no>, data-atelier-room-href, and
row-cell children for number, name, line, meta, and engine. The house-rule grid
exposes data-atelier-house-rules, data-atelier-house-rule-count, and each
tile exposes data-atelier-house-rule=<1..3> with title/description cells.
States#
- Loading — server component awaits
getAtelierIndex(); verify whether aloading.tsxskeleton is present - Populated — fixture returns 5 rooms and 3 house rules; covered by
atelier-image-to-library.spec.tswith exact count attributes and cell-by-cell room / house-rule assertions. - Empty rooms / rules — fixture always populated; verify behaviour when arrays are empty (currently no fallback copy)
- Error —
getAtelierIndex()synchronously returns fixture; verifyerror.tsx/global-error.tsxboundary - Offline — fully static markup; renders offline from SW cache
- Standalone PWA — verify safe-area insets at top
- Reduced motion — no animations declared
- Mobile / narrow — auto-fit grid stacks without horizontal overflow;
covered by
atelier-image-to-libraryat 390 px. - Active draft found —
/atelier?sceneId=<owned id>renders the real owner-scoped title/state from/v1/atelier/scenesand keeps the five-room grid present; covered byatelier-new-scene.spec.ts. - Active draft missing / unowned —
/atelier?sceneId=<unknown id>renders an honest unavailable band and no draft title; covered byatelier-new-scene.spec.ts.
Interactions#
Rooms grid (each card is <a href>)#
- "Camera Obscura" card — anchor →
/atelier/image- Function: navigates to the image atelier
- Keyboard: Tab focuses; Enter activates
- Screen reader: announces card heading, sub, and engine
- "Foundry of Voices" card — anchor →
/atelier/audio - "Cinematheque" card — anchor →
/atelier/video - "Forms" card — anchor →
/atelier/forms - "Stage of Motion" card — anchor →
/atelier/motion
House rules grid#
- Each rule tile — read-only (kicker
House rule · I/II/III, title, description)
Active draft band#
- "Clear" — anchor to
/atelier, removes the query selection - "Start another" — anchor to
/atelier/new, starts the new-scene flow - Unavailable "Clear" — anchor to
/atelier; no retry or fabricated draft controls appear when the scene id is not found for the member
Data & contracts#
- Reads:
getAtelierIndex()fromapps/oshun/web/src/lib/lilith-data/atelier-depth.ts— returnsAtelierIndexData(fixture today; BFF planned at/atelier/plates/...per the data file header). With asceneIdquery param, the server route also callsbffGet('/v1/atelier/scenes'), then selects the matching owner-scoped scene id from the live BFF response. Missing/unowned/BFF-unavailable responses render the unavailable band rather than a draft. - Writes: none
- Realtime: none
- Caching: fixture index data; authenticated BFF scene reads are
no-store - Auth/role check: middleware enforces signed-in
Cross-references#
- Lilith index:
../10-lilith/lilith.md— section VIII lists all five rooms - Five rooms (sibling files in this folder):
- Component source:
apps/oshun/web/src/components/lilith/atelier.tsx(AtelierIndex) - Data:
apps/oshun/web/src/lib/lilith-data/atelier-depth.ts - E2E:
apps/oshun/web/e2e/atelier-image-to-library.spec.ts - E2E:
apps/oshun/web/e2e/atelier-new-scene.spec.ts
E2E coverage#
| Interaction / state | Coverage |
|---|---|
| Five-room index | covered: exact room count, room cell text, hrefs, and house-rule rows |
| Mobile room grid | covered at 390 px: no horizontal overflow across index and image room |
| Active draft composer link | covered: create scene through live BFF, click "Open the composer", render title/id/state |
| Missing active draft | covered: signed-in /atelier?sceneId=<unknown> shows unavailable copy and no draft title |
| Grid remains with query | covered: found and missing scene-id states keep the five-room grid rendered |
Open questions / known gaps#
- Confirm responsive behaviour — the rooms grid uses auto-fit tracks and is
covered at 390 px by
atelier-image-to-library. - The masthead's
righttext is a static "Tuesday, the sixth of May" — confirm whether this is intended fixture copy or expected to be replaced with a live date - Document any handoff state shared between rooms (
AtelierMotion's "Compose · downstream" panel mentions Cinematheque sh 03 + foundry take 03 + forms cream-north — confirm whether the index reflects project state)