V1 Web PWA · Surface walkthrough

Tara · sit

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

walked
8sections6 minread

On this page

Context. surface customer · domain tara · route /tara · auth signed-in · source apps/oshun/web/src/app/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; content re-verified 2026-06-03 against current source. 2026-06-27 Codex Tara hub addendum — signed-in /tara render against the real BFF room model, canonical metadata, presentational Lilith subnav, default here/next sitting links, begin CTA to /tara/sit/[id], static teacher rows, breathwork + Nisaba handoffs, live-class booking reference, and standalone PWA relaunch are covered by the E2E references below.

Purpose#

The Tara domain hub. "A few minutes, seated." Today's sit, the course, and the teachers. The experiential and thematic center of V1. Built on the Lilith design system (TaraRoom) backed by getTara().

Entry points#

  • Domain card on / (DomainCardGrid) — primary cold-entry from home
  • Tara ritual continuation rail on / — direct to last ritual or domain hub
  • Domain switcher in shell header / /switcher
  • Shortcut g-chord (if mapped to Tara) — verify in shell/06-keyboard-a11y.md
  • PWA shortcut (if defined in manifest.json shortcuts[])
  • Direct URL / bookmark — yes (auth required)
  • Deep link from notification — Tara ritual reminder push
  • Search result — domain card surfaces in /search for relevant queries

Layout regions#

page.tsx is a thin server component: await getTara()<TaraRoom>. The TaraRoom is a Lilith design-system room composition (apps/oshun/web/src/components/lilith/rooms.tsx, TaraRoom, lines 61–337).

  • Header: LCustomerNav active="explore", then LSubNav active="today" with items Today · Courses · Teachers · Library (lines 67–75). These four LSubNav items have no href in source — they render as presentational tabs.
  • Masthead row: eyebrow "Tara · sit", display title "A few minutes, seated.", and an LAIDisclosure "Curated · by editor + signal" (lines 90–104)
  • Body grid (1.4fr 1fr desktop; one-column mobile stack):
    • Left columntoday's sit card: eyebrow "Today's sit · 12 min", LDisplay title (data.todaysSit.title), italic description, a circular play Link to /tara/sit/<id> (aria-label="Begin sitting · {title}", data-telemetry-event="tara_sit_started", data-telemetry-surface="tara_hub"), and a text Link "Begin · {N} min" plus a byline eyebrow (teacher · narrated · pathLabel · session NN of M) and an LDots progress strip (lines 109–170). Then a rule and "The course · sittings" list — one Link per data.sittings[] row to /tara/sit/<id>, aria-labelled "Replay/Begin/Preview sitting {index}: {title}" by row state (lines 174–224).
    • Right columnTeachers grid: one row per data.teachers[] (initial avatar, name, "{sittings} sittings · {tone}", decorative glyph) — the rows are presentational <div>s, not links (lines 231–281). Its responsive left border replaces the former standalone vertical rule and becomes a horizontal rule before Teachers on mobile. Then a rule + "Breath ritual · daily" LCard with the ritual title, duration/pattern chips, and a "Begin" chip Link to /meditate/breathwork (built via buildHydratedWebDomainRoutePath('tara', …), aria-label="Begin daily breath ritual: {title}", lines 287–306). Then, when data.nisabaPassageCue is present, a "Read deeper in Nisaba" cue Link to nisabaPassageCue.href (aria-label="Read the related passage in Nisaba", lines 307–330).
  • Below the grid: <TaraLiveClassBookingCard /> (line 333)
  • Footer: legal / status links per shell

States#

  • LoadinggetTara() is awaited server-side and no local loading.tsx exists at this segment; the route blocks until the room model is ready
  • First-time visitor to Tara — onboarding overlay or domain primer
  • Returning, daily sit available — today's sit is prominent and matches the real /v1/tara/room model
  • Returning, no sit today — calm empty state with course continuation
  • Streak active — streak chip visible
  • Streak broken — recovery copy ("welcome back"); no shaming
  • Error (recoverable) — Tara data fetch fails; retry; sibling rails still load
  • Offline — last-loaded Tara view rendered from BFF cache; transcripts/audio pinned for current sit if user opted in
  • Gated — if Tara content requires Lilith policy approval, gated content marked clearly with reason
  • Standalone PWA — cold-launch lands here cleanly if relaunch target was /tara

Interactions#

Current E2E evidence:

  • apps/oshun/web/e2e/tara-hub.spec.ts/tara signed-in hub render, real BFF room read, metadata, presentational subnav, default fresh-path sitting links (here + next), begin CTA into /tara/sit/[id], static teacher rows, breathwork handoff, Nisaba handoff, standalone PWA relaunch, and mobile overflow safety
  • apps/oshun/web/e2e/tara-live-class-booking.spec.ts — web live-class booking card, lineage disclosure, payment receipt, calendar entry, and lineage-fund receipt route
  • apps/oshun/bff/src/tara/room.test.ts — BFF overlay rules for completed sittings (done/Replay), first unfinished (here/Begin), skipped completion, teachers, ritual, and Nisaba cue
  • Today / Courses / Teachers / Library (tabs)
    • Function: presentational tabs with active="today"; the four items have no href in source (lines 67–75) — they do not navigate today
    • Keyboard: tab order matches DOM source order
    • Screen reader: announces label only

Today's sit card#

  • Begin play button (circular Link)
    • Function: navigates to /tara/sit/<data.todaysSit.id> (lines 128–149)
    • Screen reader: aria-label="Begin sitting · {title}"
    • Touch target: 56×56 (circular)
    • Telemetry: data-telemetry-event="tara_sit_started", data-telemetry-surface="tara_hub"
  • "Begin · {N} min" text link (Link)
    • Function: same target — /tara/sit/<data.todaysSit.id> (lines 152–157); N = Math.round(durationSeconds / 60)
  • Byline eyebrow — non-interactive; teacher · narrated · pathLabel · session NN of M
  • LDots progress strip — presentational; count = pathTotal, current = sessionIndex − 1

"The course · sittings" list#

  • Sitting row (Link per data.sittings[])
    • Function: navigates to /tara/sit/<row.id> (lines 189–221)
    • Screen reader: aria-label is state-dependent — "Replay sitting {index}: {title}" (done), "Begin sitting {index}: {title}" (here), or "Preview sitting {index}: {title}" (locked)
    • Glyph: done · here · locked; the "here" row gets a tinted background
    • Browser E2E covers the default fresh-path here and next states. The done/Replay overlay is covered at the real BFF room-model layer and should get a seeded browser-progress slice when completion seeding is made deterministic for /tara.

Teachers grid#

  • Teacher row (presentational <div>, not a link)
    • Function: visually shows initial avatar, name, "{sittings} sittings · {tone}", and a decorative glyph (lines 233–280) — there is no /tara/teacher/<id> link from the hub
    • Screen reader: announces as static text

Breath ritual · daily#

  • "Begin" chip (LChip inside a Link)
    • Function: navigates to /meditate/breathwork (href built via buildHydratedWebDomainRoutePath('tara', '/meditate/breathwork', { origin: 'home' }), lines 296–304)
    • Screen reader: aria-label="Begin daily breath ritual: {title}"
  • Duration / pattern chips — presentational LChips

Read deeper in Nisaba (cross-domain cue)#

  • Passage cue (Link, only when data.nisabaPassageCue is set)
    • Function: navigates to data.nisabaPassageCue.href (lines 313–328)
    • Screen reader: aria-label="Read the related passage in Nisaba"
    • Label: data.nisabaPassageCue.label

Live-class booking#

  • <TaraLiveClassBookingCard /> (line 333) — see the component for its own booking interactions

Cross-domain bridges#

Verify presence and behavior of any link/quick-action that hops into another domain (e.g., a Tara session that ends with a Nisaba reading).

  • Cross-domain CTA (if present)
    • Function: navigates to other domain
    • Telemetry: crossdomain_

Data & contracts#

  • Reads: getTara() from @/lib/lilith-data/tara
  • Writes: ritual start events flow to Psyche session envelope
  • Realtime: session envelope for active sit (separate route)
  • Caching: server fetch; revalidate per getTara policy; SW caches the rendered HTML for offline reload
  • Auth/role check: middleware enforces signed-in

Cross-references#

  • Shell: shell/01-app-shell.md
  • Sibling Tara routes (one file per route in this folder):
    • (list grows as files are authored)
  • Domains-namespace alternate: /domains/tara/* — see coverage
  • Cross-domain partners:
  • Feature spec: V1/features.md
  • Architecture: V1/ARCHITECTURE.md
  • E2E coverage:
    • apps/oshun/web/e2e/tara-hub.spec.ts
    • apps/oshun/web/e2e/tara-live-class-booking.spec.ts
    • apps/oshun/web/e2e/home-tara-centering-continuity.spec.ts
  • BFF/unit coverage:
    • apps/oshun/bff/src/tara/room.test.ts
    • apps/oshun/bff/src/routes/domain-stubs-postgres.test.ts
  • Journeys traversing Tara:

Open questions / known gaps#

  • Enumerate every sub-route under /tara/* and /domains/tara/*; verify which surfaces are V1-shipping vs. internal
  • Add a seeded browser-progress slice for completed Tara sittings so done/Replay rows are proven in the web hub, not only in BFF room-model tests
  • Confirm whether streak data is read from Arete or Tara — affects the cross-domain bridge story
  • Document the practice-picker flow (separate file if it's a route)
  • Confirm Lilith tone policy gates that apply to Tara teacher/practice content (see V1/features.md#lilith-persona-policy)