Context. surface customer · domain atelier · route /atelier/image · auth signed-in · source apps/oshun/web/src/app/atelier/image/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 (image room row-cell anchors, save/cast states, and library landing covered), against commit HEAD' '2026-06-26 by Codex (aspect/style controls, seed lock, compare panel, live step rail, and 390 px stacking covered), against commit HEAD
Purpose#
Image atelier — "an image, drawn from a line." Five-step workflow from a written
prompt to a kept plate (contact sheet of twelve specimens → refine chosen
plate). Server-rendered by apps/oshun/web/src/app/atelier/image/page.tsx via
getAtelierImage() → <AtelierImage data>.
Entry points#
- Atelier index (
/atelier) — first room card - Lilith index (
/lilith) — section VIII card 42 "X · Camera Obscura" - Direct URL / bookmark — yes (signed-in)
Layout regions#
AtelierImage (apps/oshun/web/src/components/lilith/atelier.tsx, line 285)
renders inside LWebShell with LCustomerNav active="explore".
The route root exposes data-atelier-image-page with
data-atelier-image-source-count="3" and
data-atelier-image-specimen-count="12". The auto-fit room body exposes
data-atelier-image-layout; the plate head, prompt card, contact sheet, plate
detail, provenance rows, and save/cast action rows now carry stable
data-atelier-* anchors used by the Playwright journey.
- Plate head (
LAPlateHead no="X"):- kicker: "The atelier · camera obscura"
- title:
An image, drawn from a <em>line</em>. - right chips: "Specimen 047", "Saved to notebook", "Plate finalised" (accent)
- Auto-fit room body (rail / composer / plate detail, stacked on narrow
viewports):
- Left rail (
AtelierStepRail, current=3 initially): five steps — "A line, written", "Reference, gathered", "Twelve specimens", "Plate kept · refined", "Final · saved to notebook". The live rail exposesdata-atelier-step-rail,data-atelier-step-current,data-atelier-step-count="5", and each row exposesdata-atelier-step=<n>withdata-atelier-step-state="done|current|pending". - Center:
- Prompt card (
The line · prompt): editable textarea (data-atelier-prompt-input) seeded with "A monk, seated on a stone bench, lit only by a high north window…" plus adata-atelier-prompt-previewline and chips Aspect, Plate cream, Steps 38, Seed 0x4f2a locked/unlocked, Style, Re-cast (accent). Anchors:data-atelier-prompt-card,data-atelier-prompt-chips, and per-chipdata-atelier-prompt-chip="aspect|plate|steps|seed|style|recast". Aspect and style option buttons exposedata-atelier-option="aspect|style"plus pressed/active state. - Contact sheet (
Contact sheet · twelve): 4×3 grid of 12 selectable specimen buttons (statuskeptforp.07,selectedfor the current non-kept choice,draftfor the rest); each contains a procedural SVG illustration. The sheet exposesdata-atelier-contact-sheet-count="12"and each tile exposesdata-atelier-specimen,data-atelier-specimen-status, anddata-atelier-specimen-kept; the chosen tile also exposesdata-atelier-specimen-selected="true". - Action row: "Cast twelve more", "Lock seed · vary prompt", "Compare
side-by-side"; Compare opens an inline kept-vs-active panel with
data-atelier-compare-panel.
- Prompt card (
- Right:
- Plate detail (
Plate · p.07by default): 4:5 SVG of the selected plate + field rows (Engine, Aspect 4:5, Steps 38, Guidance 6.4, Seed 0x4f2a · locked, Style "Cormorant warmth", Sources "3 · cited") LRule- Provenance · 3 sources: list from
data.sources(Vermeer · light study, Hammershøi · interiors, Lilith · plate library). The lane exposesdata-atelier-provenance-count="3"and every source row exposes source name and kind cells. - Action stack: primary "Save plate to notebook", ghost "Refine · vary slightly"
- Plate detail (
- Left rail (
States#
- Loading — server component awaits
getAtelierImage(); verifyloading.tsx - Populated — fixture provides three provenance sources; covered by exact source-count and provenance row-cell assertions.
- Kept plate selected —
p.07highlighted; other tilesdraft; covered bydata-atelier-specimen-status="kept"anddata-atelier-selected-plate="p.07"assertions. - Alternate specimen selected — clicking a draft tile sets
data-atelier-specimen-selected="true"and updatesdata-atelier-selected-plateon the right-side plate detail; the rail advances to step 4. - Final saved — clicking "Save plate to notebook" advances the rail to step 5 and shows the saved state.
- Empty sources — verify if
data.sourcesempty (no fallback in code) - Error — verify error boundary
- Offline — entire view is static markup; renders offline
- Reduced motion — no animations declared
Interactions#
Prompt card chips and controls#
- Aspect controls — option buttons update
data-atelier-aspect, the Aspect chip, the right-side Aspect field, and the cast/refine payload. - Style controls — option buttons update
data-atelier-style, the Style chip, the right-side Style field, and the cast/refine payload. - Seed chip / lock state — "Lock seed · vary prompt" toggles
data-atelier-seed-state, the Seed chip, the Seed field, and the cast/refine payload. - Plate / Steps chips (
LChip) — read-only. - "Re-cast" chip (
LChip accent) — verify whether interactive (component propaccentdoes not necessarily imply click)
Contact sheet specimens#
- Specimen tile (
button,data-atelier-specimen="p.01"–p.12) — all 12 tiles are rendered with stable specimen/status/kept/selected attributes; tapping a draft specimen selects it and updates the right detail pane. - "Kept" tile (p.07) — accent visual; selected state asserted
- "Draft" tiles — muted treatment
Center actions#
- "Cast twelve more" button (
AtelierPlateActions mode="cast")- Function: POSTs
/v1/atelier/castwith the browser-entered prompt, selected specimen id, aspect, style, seed-lock state, andintent: cast; in the local real-dev stack without Stability credentials it fails closed withdata-atelier-cast-result="not-configured"anddata-atelier-cast-state="not-configured".
- Function: POSTs
- "Lock seed · vary prompt" button — toggles seed locked/unlocked state.
- "Compare side-by-side" button — opens/closes an inline compare panel with kept reference p.07 and the active selected plate.
Right column actions#
- "Save plate to notebook" button (
AtelierPlateActions mode="save")- Function: writes
atelier-plate-kepttooshun.web.library.saved-items.v1askind: generated-artifact, then the Playwright journey opens/library?kind=generated-artifactand assertsdata-library-item="atelier-plate-kept"with the "Open in Atelier" action.
- Function: writes
- "Refine · vary slightly" button (
AtelierPlateActions mode="refine")- Function: POSTs
/v1/atelier/castwith the browser-entered prompt, selected specimen id, aspect, style, seed-lock state, andintent: refine; in the local real-dev stack it fails closed withdata-atelier-refine-result="not-configured".
- Function: POSTs
Data & contracts#
- Reads:
getAtelierImage()fromapps/oshun/web/src/lib/lilith-data/atelier-depth.ts— returnsAtelierImageDatawithsources(label + kind tuples; planned BFF at/atelier/plates/image) - Writes:
Save plate to notebookcallssaveOshunWebLibraryItem(...)throughAtelierPlateActions, creating a local web-library generated-artifact row withid: atelier-plate-kept,domain: nisaba,resumePath: /atelier/image, andresumeLabel: Open in Atelier.Cast twelve moreandRefine · vary slightlycallapi.post('/v1/atelier/cast', ...)with prompt/specimen/intent plus aspect, style, and seed-lock state; the real generation seam fails closed with typed 503 provider-not-configured when the Stability key is absent.
- Realtime: none
- Caching: server fetch
- Auth/role check: middleware enforces signed-in
Cross-references#
- Atelier index:
atelier.md - Sibling rooms:
atelier-audio.md,atelier-video.md,atelier-forms.md,atelier-motion.md - Component source:
apps/oshun/web/src/components/lilith/atelier.tsx(AtelierImage) - Data:
apps/oshun/web/src/lib/lilith-data/atelier-depth.ts
Open questions / known gaps#
- Confirm whether the action buttons wire to handlers — Cast, Refine, and
Save are wired through
AtelierPlateActions; Lock seed and Compare are wired in the composer. - Document touch behaviour for specimen tiles — draft tiles are buttons; tap/click selects the specimen and updates the right detail pane.
- Confirm responsive layout —
atelier-image-to-libraryverifies the image room at 390 px with no horizontal overflow and stacked rail → composer → plate detail.