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 inshell/06-keyboard-a11y.md - PWA shortcut (if defined in
manifest.jsonshortcuts[]) - Direct URL / bookmark — yes (auth required)
- Deep link from notification — Tara ritual reminder push
- Search result — domain card surfaces in
/searchfor 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", thenLSubNav active="today"with items Today · Courses · Teachers · Library (lines 67–75). These fourLSubNavitems have nohrefin 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 1frdesktop; one-column mobile stack):- Left column — today's sit card: eyebrow "Today's sit · 12 min",
LDisplaytitle (data.todaysSit.title), italic description, a circular playLinkto/tara/sit/<id>(aria-label="Begin sitting · {title}",data-telemetry-event="tara_sit_started",data-telemetry-surface="tara_hub"), and a textLink"Begin · {N} min" plus a byline eyebrow (teacher · narrated · pathLabel · session NN of M) and anLDotsprogress strip (lines 109–170). Then a rule and "The course · sittings" list — oneLinkperdata.sittings[]row to/tara/sit/<id>, aria-labelled "Replay/Begin/Preview sitting {index}: {title}" by row state (lines 174–224). - Right column — Teachers 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"LCardwith the ritual title, duration/pattern chips, and a "Begin" chipLinkto/meditate/breathwork(built viabuildHydratedWebDomainRoutePath('tara', …),aria-label="Begin daily breath ritual: {title}", lines 287–306). Then, whendata.nisabaPassageCueis present, a "Read deeper in Nisaba" cueLinktonisabaPassageCue.href(aria-label="Read the related passage in Nisaba", lines 307–330).
- Left column — today's sit card: eyebrow "Today's sit · 12 min",
- Below the grid:
<TaraLiveClassBookingCard />(line 333) - Footer: legal / status links per shell
States#
- Loading —
getTara()is awaited server-side and no localloading.tsxexists 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/roommodel - 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—/tarasigned-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 safetyapps/oshun/web/e2e/tara-live-class-booking.spec.ts— web live-class booking card, lineage disclosure, payment receipt, calendar entry, and lineage-fund receipt routeapps/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
Sub-nav (LSubNav)#
- Today / Courses / Teachers / Library (tabs)
- Function: presentational tabs with
active="today"; the four items have nohrefin source (lines 67–75) — they do not navigate today - Keyboard: tab order matches DOM source order
- Screen reader: announces label only
- Function: presentational tabs with
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"
- Function: navigates to
- "Begin · {N} min" text link (
Link)- Function: same target —
/tara/sit/<data.todaysSit.id>(lines 152–157);N = Math.round(durationSeconds / 60)
- Function: same target —
- Byline eyebrow — non-interactive; teacher · narrated · pathLabel · session NN of M
-
LDotsprogress strip — presentational;count = pathTotal,current = sessionIndex − 1
"The course · sittings" list#
- Sitting row (
Linkperdata.sittings[])- Function: navigates to
/tara/sit/<row.id>(lines 189–221) - Screen reader:
aria-labelis 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
hereandnextstates. Thedone/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.
- Function: navigates to
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
- Function: visually shows initial avatar, name, "{sittings} sittings ·
{tone}", and a decorative
Breath ritual · daily#
- "Begin" chip (
LChipinside aLink)- Function: navigates to
/meditate/breathwork(href built viabuildHydratedWebDomainRoutePath('tara', '/meditate/breathwork', { origin: 'home' }), lines 296–304) - Screen reader:
aria-label="Begin daily breath ritual: {title}"
- Function: navigates to
- Duration / pattern chips — presentational
LChips
Read deeper in Nisaba (cross-domain cue)#
- Passage cue (
Link, only whendata.nisabaPassageCueis set)- Function: navigates to
data.nisabaPassageCue.href(lines 313–328) - Screen reader:
aria-label="Read the related passage in Nisaba" - Label:
data.nisabaPassageCue.label
- Function: navigates to
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
getTarapolicy; 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/*— seecoverage - Cross-domain partners:
../04-arete/— Arete streak/recovery interplay../07-nisaba/— Nisaba reading hand-off../06-nyx/— Nyx perspective bridge from contemplation
- Feature spec:
V1/features.md - Architecture:
V1/ARCHITECTURE.md - E2E coverage:
apps/oshun/web/e2e/tara-hub.spec.tsapps/oshun/web/e2e/tara-live-class-booking.spec.tsapps/oshun/web/e2e/home-tara-centering-continuity.spec.ts
- BFF/unit coverage:
apps/oshun/bff/src/tara/room.test.tsapps/oshun/bff/src/routes/domain-stubs-postgres.test.ts
- Journeys traversing Tara:
../../journeys/install-as-pwa.md(touches Tara via PWA cold-start)
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)