---
path: /domains/tara/programs
surface: customer
domain: tara
auth: signed-in
source: apps/oshun/web/src/app/domains/tara/programs/page.tsx
status: walked
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-29 targeted Playwright
  coverage added for browser lists, selected-card detail, programId deep links,
  missing programId fallback, local enrollment, current/locked day rows, day
  detail, header back, milestone state, and scoped axe scans. Evidence:
  apps/oshun/web/e2e/tara-programs.spec.ts'
---

# Tara — programs browser

## Purpose

Browser for multi-day / multi-week Tara meditation programs. Lists enrolled "My
programs" and all available programs; selecting one opens `ProgramDetailView`
with daily structure, meditation, quote, intention, activities, milestone
markers, and a progress bar.

## Entry points

- **Tara hub** — verify the cross-link from `/tara`
- **Deep link with program selection** — `?programId=<id>` pre-selects a program
  via `useBrowserSearchParams().get('programId')`
- **Direct URL / bookmark** — yes
- **Domain back-stack** — `router.back()` wired into `onBack`

## Layout regions

`page.tsx` is a `'use client'` thin wrapper that:

1. Reads `programId` from `useBrowserSearchParams()`
2. Renders
   `<TaraProgramsBrowserPage onBack={() => router.back()} initialProgramId={initialProgramId} />`

Inside `TaraProgramsBrowserPage` (`TaraCollections.tsx`):

- **Sticky header** — back button, "Programs" heading, "Multi-day structured
  journeys" subtitle, Calendar icon
- **My programs section** —
  `myPrograms = SIMULATED_PROGRAMS.filter(enrolled === true)`; only visible when
  at least one enrollment exists
- **All programs grid** — `SIMULATED_PROGRAMS` cards
- **Program detail view** — when `selectedProgram !== null`, replaces the
  browser with `<ProgramDetailView>` showing day-by-day plan, milestones, and
  progress

## States

- [x] **Browser (no selection)** — `selectedProgram === null`; My programs + All
      programs visible
- [x] **Detail view (selected via card)** — `selectedProgram !== null`; browser
      hidden
- [x] **Detail view (deep link)** — `initialProgramId` matches a program; the
      effect on `[initialProgramId]` rehydrates selection on URL change
- [x] **Initial program id missing** — `SIMULATED_PROGRAMS.find(...) === null`;
      falls through to the browser
- [ ] **No enrolled programs** — My programs section hidden
- [x] **Milestones reached** — static milestone reached/unreached state in
      `ProgramDetailView`
- [x] **Reduced motion** — shared Playwright fixture emulates reduced motion;
      scoped browser/detail axe scans run against settled roots

## Interactions

### Header

- [x] **Back** (button) — `router.back()` via `onBack`

### My programs section

- [x] **Program card** — sets `selectedProgram`; reveals detail view
- [x] **Progress bar inside card** — non-interactive `progress * 100` fill

### All programs grid

- [x] **Program card** — same selection behavior

### Program detail view (`ProgramDetailView`)

- [x] **Back inside detail** — clears `selectedProgram`
- [x] **Day row** — opens the day's meditation, quote, intention, activities
- [ ] **Mark day complete** — no shipped control exists to flip
      `ProgramDay.completed`
- [ ] **Milestone celebration** — crossing a milestone is not currently
      route-drivable because day completion cannot be changed in the UI
- [x] **Enroll** — non-enrolled program starts locally and locks future days
- [ ] **Un-enroll** — no shipped un-enroll control exists

## Data & contracts

- **Reads**:
  - `SIMULATED_PROGRAMS` (in-file fixture from `TaraCollections.tsx`)
- **Writes**: enrollment + day completion state are local to the component today
- **Realtime**: _None._
- **Caching**: client-only
- **Auth/role check**: shell middleware
- **Query param**: `programId` (string | null) — read once, applied via
  `useEffect([initialProgramId])`

## Current E2E evidence

- `apps/oshun/web/e2e/tara-programs.spec.ts` opens `/domains/tara/programs`,
  verifies the no-selection browser state, two enrolled cards in "My Programs",
  three cards in "All Programs", card enrollment/current-day/progress state, and
  selection into `tara-prog-001`.
- The same spec verifies `tara-prog-001` detail state, reached and unreached
  milestones, current/completed/locked day rows, the day-4 detail view's quote,
  intention, meditation, activities, locked day non-entry, and detail back.
- It opens `/domains/tara/programs?programId=tara-prog-002` to prove deep-link
  hydration, then `/domains/tara/programs?programId=tara-prog-missing` to prove
  missing IDs fall back to the browser.
- It opens non-enrolled `tara-prog-003`, clicks Start Program, verifies local
  enrollment/current-day update and future-day locking, and opens day 1.
- It verifies header `router.back()` by navigating from `/domains/tara` to
  `/domains/tara/programs`, then clicking the route header back button.
- It runs scoped axe scans against `[data-tara-programs-browser]` and
  `[data-tara-program-detail="tara-prog-001"]`.

## Cross-references

- Sibling Tara routes:
  - [`tara.md`](./tara.md),
    [`domains-tara-analytics.md`](./domains-tara-analytics.md),
    [`domains-tara-collections.md`](./domains-tara-collections.md),
    [`domains-tara-courses-id.md`](./domains-tara-courses-id.md),
    [`domains-tara-search.md`](./domains-tara-search.md),
    [`domains-tara-sounds.md`](./domains-tara-sounds.md),
    [`domains-tara-teachers.md`](./domains-tara-teachers.md),
    [`domains-tara-teachers-id.md`](./domains-tara-teachers-id.md)
- Component sources:
  - `apps/oshun/web/src/components/domains/tara/TaraCollections.tsx`
    (`TaraProgramsBrowserPage` line 2013)
- Feature spec: [`V1/features.md#tara`](../../../V1/features.md#tara)

## Open questions / known gaps

- [ ] Confirm whether deep-link `?programId=` should update the URL when the
      user selects a program from a card (current page reads the param on mount
      but does not propagate selection back to the URL)
- [ ] Document the persistence target for enrollment + day completion. Current
      enrollment is local component state; no day-complete write exists.
- [ ] Confirm the BFF endpoint for programs once it lands; today
      `SIMULATED_PROGRAMS` is local fixture data
- [ ] Verify milestone celebration accessibility if a real day-complete action
      ships; current UI only displays static reached/unreached milestone chips.
