V1 Web PWA · Surface walkthrough

Lilith Studio · Tara (read-only mirror)

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

walked
8sections4 minread

On this page

Context. surface customer · domain lilith · route /lilith-studio/tara · auth signed-in + role:v1_editorial_producer (default) · source apps/oshun/web/src/app/lilith-studio/tara/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

Purpose#

A single read-only pane on what Tara is currently serving end-users: today's recommended sit, the course sittings, and the daily ritual. It mirrors live content for the author's reference — edit/publish actions live behind the studio shell, not here. Server-rendered by fetching /v1/tara/today, /v1/tara/sittings, and /v1/tara/ritual in parallel.

Entry points#

  • Lilith Studio hub (/lilith-studio) — verify a Tara surface link
  • /lilith-studio/scenes — verify cross-link
  • Footer buttons here: "← Studio hub" (→ /lilith-studio), "Scenes" (→ /lilith-studio/scenes), "New scene" (→ /lilith-studio/scene/new)
  • Direct URL / bookmark — yes (signed-in + editorial scope)

Layout regions#

/lilith-studio/layout.tsx mounts <ShellLayout active="studio"> and the editorial gate. This page additionally mounts its own LWebShell with LCustomerNav active="explore" (see Open questions — likely a double-shell). Max width 1080, centered.

  • Header: studio ShellLayout header + this page's LCustomerNav (Explore)
  • Masthead (LMasthead): left "Lilith Studio · Tara", right "read-only mirror", kicker "What end-users see now", title "Tara, currently live."
  • Two-column grid (1.4fr / 1fr):
    • Left column:
      • Today (LEyebrow): accent-bordered card with mono line "Recommended sit · · session / · min", serif title, italic "taught by ". Empty → dashed "No recommended sit on the wire for today."
      • Sittings (course) (LEyebrow): <ol> of sitting rows (40px 1fr auto grid) — mono index, serif title, and a state·duration chip whose color keys off state (here=accent, done=green #1f4f3a, else muted). Empty → dashed "No sittings configured for the active path."
    • Right column:
      • Daily ritual (LEyebrow): rule-bordered card with mono " · · min", serif title, accent "Pattern · ". Empty → dashed "No daily ritual configured."
  • Action row (flex, wraps): ghost "← Studio hub", ghost "Scenes", primary "New scene"

States#

  • Loading — async server component awaits three bffGet calls in parallel; no client skeleton. Verify loading.tsx
  • Fully populated — today's sit + sittings list + ritual all present
  • No sit todaytoday.recommendedSit falsy → "No recommended sit on the wire for today."
  • No sittingssittings.sittings empty → "No sittings configured for the active path."
  • No ritualritual falsy → "No daily ritual configured."
  • All empty — every BFF call returns null (network/non-2xx via bffGet) → all three dashed empty rows
  • Sitting state variantshere (accent), done (green), next / queued (muted)
  • Error (recoverable)bffGet swallows errors to null, so failures render as the empty states — no distinct error UI
  • Offline — 5s abort → null → empty rows; SW-cached HTML may show last render
  • Gated (access denied) — non-editorial scope → layout renders LilithStudioAccessDenied
  • Standalone PWAdata-responsive-page; verify the 1.4fr/1fr grid collapses at narrow widths (no explicit responsive rule in code)

Interactions#

This page is a read-only mirror — the only interactive elements are the three footer navigation buttons. The today/sittings/ritual cards are non-interactive display.

  • Today card (<div>) — non-interactive
  • Sitting row (<li>) — non-interactive; verify whether a row should link to its sit (none coded)
  • Ritual card (<div>) — non-interactive
  • "← Studio hub" (LBtn kind="ghost", href="/lilith-studio")
  • "Scenes" (LBtn kind="ghost", href="/lilith-studio/scenes")
  • "New scene" (LBtn kind="primary", href="/lilith-studio/scene/new")
    • Function: → lilith-studio-scene-new.md
    • Keyboard: Enter activates; in tab order
    • Mobile (≤ 640 px): action row flexWrap: 'wrap' lets buttons stack

Data & contracts#

  • Reads (parallel, bffGet):
    • GET /v1/tara/today{ generatedAt, recommendedSit?: { id, title, teacher, durationSeconds, pathLabel, sessionIndex, pathTotal } }
    • GET /v1/tara/sittings{ generatedAt, sittings: { id, index, title, durationSeconds, state: 'done'|'here'|'next'|'queued' }[] }
    • GET /v1/tara/ritual{ generatedAt?, ritualId, title, pattern, durationSeconds, recommendedDaypart: 'morning'|'midday'|'evening' }
  • Writes: none (read-only mirror)
  • Realtime: none
  • Caching: bffGet default revalidate = 60 s; session cookie forwarded
  • Auth/role check: /lilith-studio/layout.tsx editorial gate (admin:* or studio:editorial scope) → else LilithStudioAccessDenied

Cross-references#

Open questions / known gaps#

  • Double shell: the layout mounts ShellLayout and this page mounts LWebShell too. Confirm whether the page should drop its LWebShell (the layout's refactor note says pages "return their inner authoring component directly")
  • Nav active="explore" vs layout active="studio" — two competing active-tab signals
  • The mirror is read-only by design but offers no link from a sitting row to its underlying sit/source — confirm authors don't need a drill-in here
  • bffGet null on failure collapses error and empty into one state — no way to tell "no content configured" from "BFF unreachable"
  • Confirm the three /v1/tara/* BFF endpoints are the same the end-user /tara hub consumes (so the mirror is truly faithful)