---
path: /domains/tara/analytics
surface: customer
domain: tara
auth: signed-in
source: apps/oshun/web/src/app/domains/tara/analytics/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; 2026-06-29
  targeted Playwright coverage added for real BFF-backed empty and populated
  analytics states, auth-hydration retry, range switching, CSV export,
  chart/title semantics, UTC weekday heatmap labels, reduced-motion CSS, scoped
  axe scans, and route back-stack. Evidence:
  apps/oshun/web/e2e/tara-analytics.spec.ts'
---

# Tara — analytics dashboard

## Purpose

The Tara analytics surface under `/domains/tara/*`. It renders the member's real
sit-completion analytics from `/v1/tara/analytics`: minutes, sessions,
completion rate, most-practiced sittings, UTC weekday/hour heatmap, and practice
rhythm calendar, with a CSV export for the active range. It no longer fabricates
seeded-random practice history for members with no data.

## Entry points

- **Tara hub** — no current source link from `/tara` to this route was found in
  the focused 2026-06-29 pass; treat the hub affordance as an open linkage gap
- **Direct URL / bookmark** — yes
- **Domain back-stack** — `router.back()` is wired into the dashboard's `onBack`
  prop (provided by this page wrapper)
- **No middleware-specific gating** — the page is a `'use client'` wrapper with
  no route-local auth guard; the BFF analytics endpoint is authenticated

## Layout regions

`page.tsx` is a `'use client'` thin wrapper that renders
`<TaraAnalyticsDashboard onBack={() => router.back()} />`.

Inside `TaraAnalyticsDashboard` (`apps/.../TaraAnalyticsDashboard.tsx`):

- **Header / back button** — back affordance (`onBack`) at top-left
- **Summary stat row** — Clock / Target / Flame / TrendingUp lucide icons with
  numeric headline values
- **Meditation minutes bar chart** — CSS-driven daily bars derived from the
  member's real completion rows
- **Session completion ring** — SVG donut/ring (`stroke-dashoffset` animated via
  `tara-dash-ring-fill` keyframes)
- **Most practiced sittings** — horizontal CSS bars from `bySitting`
- **Time-of-day heatmap** — UTC Sunday-Saturday × 24-hour grid
- **Practice rhythm calendar** — daily markers across the active window
- **CSV export action** — downloads the visible window of metrics

## States

- [x] **Loaded with real completions** — seeded E2E users complete Tara sits via
      the real `/v1/tara/sittings/:id/complete` BFF route; the dashboard then
      reads `/v1/tara/analytics` and mirrors the same totals, streak, category,
      heatmap, and daily-point model
- [x] **Auth-hydration retry** — dashboard retries the initial unauthenticated
      no-token race before settling; authenticated users do not get a false
      empty dashboard from an early 401
- [x] **Empty member** — `/v1/tara/analytics` returns `hasData: false`; the UI
      renders "No practice yet" instead of fabricated history
- [x] **Empty practice day** — zero-minute bars and rhythm cells remain in the
      generated contiguous range; the populated E2E confirms a real practiced
      day and exported zero rows around it
- [x] **Window switcher** — This week / This month / All time buttons update
      `data-time-range` and `aria-pressed`
- [x] **CSV export success** — active range downloads
      `tara-analytics-<range>-<YYYY-MM-DD>.csv` with header
      `Date,Minutes,Sessions,Completed,Total`
- [x] **Reduced motion** — `prefers-reduced-motion: reduce` collapses dashboard
      animations/transitions; E2E verifies bar animation duration is <= 1ms
- [x] **Auth gate** — BFF reads require auth; route-local page guard remains the
      customer shell/middleware boundary
- [ ] **BFF/database failure** — failed fetch keeps the honest empty model, but
      there is no explicit user-facing error state

## Interactions

### Header / back

- [x] **Back** (button)
  - Function: calls `router.back()` (provided as `onBack` from this page)
  - Aria-label: "Back to previous page"

### Charts (interactive)

- [x] **Bar / cell hover titles** — minute bars and heatmap cells expose native
      `title` text with date/hour and real minutes/session counts
- [x] **Window selector** — This week / This month / All time segmented buttons
      update the active range
- [x] **CSV export** (`Download` icon button) — triggers a CSV file download of
      the active window
- [ ] **Keyboard tooltip alternative** — chart titles are native hover text, not
      a focusable disclosure pattern

## Data & contracts

- **Reads**: BFF `GET /v1/tara/analytics`
  - Source table: real sit completions recorded by
    `POST /v1/tara/sittings/:id/complete`
  - Model builder: `apps/oshun/bff/src/tara/analytics.ts`
  - UI mapping: `daily`, `bySitting`, and `weekdayHour`
- **Writes**: no server writes from the dashboard; CSV export writes a local
  browser download only
- **Realtime**: _None._
- **Caching**: BFF replies `cache-control: no-store`; page is a client component
- **Auth/role check**: BFF `collectionsAuthGuard` plus customer shell/middleware
- **Telemetry**: _None observed in this view._

## Current E2E evidence

- `apps/oshun/web/e2e/tara-analytics.spec.ts` creates fresh signed-in users,
  reads `/v1/tara/analytics` directly, and verifies `hasData: false` renders the
  empty state with no stat cards.
- The same spec records three real sit completions through
  `/v1/tara/sittings/:id/complete`, re-reads `/v1/tara/analytics`, and proves
  the browser dashboard matches the returned totals: 27 minutes, 3 sessions,
  1-day rhythm, 100% completion, practiced day, per-sitting category rows, and
  weekday/hour heatmap cell.
- It verifies range selection (`This week`), CSV download filename and content,
  reduced-motion animation duration, minute/heatmap title semantics, scoped axe
  scans for empty and populated states, and route-header back navigation.

## Cross-references

- Sibling Tara routes (this folder):
  - [`tara.md`](./tara.md) — polished Tara hub
  - [`domains-tara-collections.md`](./domains-tara-collections.md)
  - [`domains-tara-courses-id.md`](./domains-tara-courses-id.md)
  - [`domains-tara-programs.md`](./domains-tara-programs.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/TaraAnalyticsDashboard.tsx`
- Feature spec: [`V1/features.md#tara`](../../../V1/features.md#tara)

## Open questions / known gaps

- [x] Dashboard reads from real `/v1/tara/analytics`; seeded-random dashboard
      history has been removed.
- [x] CSV export contract documented and tested:
      `Date,Minutes,Sessions,Completed,Total`
- [ ] Confirm the relationship between `/domains/tara/analytics` and the
      polished `/tara` surface — source search found no current hub link
- [ ] Decide whether BFF/database failures should render an explicit error panel
      instead of the same honest empty state
- [ ] Decide whether chart hover titles need a keyboard-accessible tooltip or
      data-table alternative
