---
path: /arete/patterns
surface: customer
domain: arete
auth: signed-in
source: apps/oshun/web/src/app/arete/patterns/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'
---

# Arete · the patterns

## Purpose

"Twelve weeks, in cross-section." Three views of practice — hour × day heatmap
of when the practice happens, habit-correlation table of what travels together,
and mood-by-hour curve. Renders
`<AretePatterns data={await getAretePatterns()} />`.

## Entry points

- **From `/arete` sub-nav "Patterns" tab** — the `LSubNav` item carries
  `href="/arete/patterns"` and navigates here
- **From `/arete/coaching`** — Lilith's letter references "patterns Lilith
  named" but uses its own sidebar list, not a link to this page
- **Direct URL / bookmark** — yes (signed-in)
- **From notification / weekly digest** — "your patterns for the week" deep link

## Layout regions

`page.tsx` is a thin server component: `await getAretePatterns()` →
`<AretePatterns>`. Component lives at
`apps/oshun/web/src/components/lilith/arete.tsx` line 1549 — an `LWebShell` with
`LCustomerNav active="today"`.

- **Header**: `LCustomerNav active="today"`
- **Masthead** (`ArMast`): left "Arete · the patterns", mid "last 12 weeks",
  right "time × day · habit × habit · mood × hour", big "The week, in
  _cross-section._", lede about the three views
- **Main, left (1.2fr)**:
  - I · When the practice happens — 7-column × 16-row heatmap grid (hours 06–21
    × mon–sun). Cell intensity is **computed inline from hardcoded arithmetic**
    in the component (see Open questions): 07:00 weekday is 0.85 opacity, 12:00
    weekday is 0.55, 21:00 weekday is 0.6, etc. Caption: "The mornings are
    fierce; the lunchtime walk thins on Wed and Thu…"
- **Aside, right (1fr)**:
  - II · What travels together — list of `data.correlations` rows; each is a
    `[habitA, habitB, value, note]` tuple rendered with habit A ↔ habit B label,
    signed mono value (accent for `+…`, brown for `−…`), and italic note
  - III · Mood × hour — `aria-hidden` SVG line chart; "after the sit" and "after
    the read" callouts pinned at fixed x positions
- **Footer row** (`borderTop: 1px solid L.ink`):
  - IV · What this is for — 3-column grid of `data.purposeNotes` cards (title
    eyebrow + body)

## States

- [ ] **Loading** — server-awaited; no `loading.tsx` at this route
- [ ] **Populated (default fixture)** — heatmap is non-empty (hardcoded cells),
      correlations list renders rows, mood SVG draws line
- [ ] **Heatmap empty** — N/A in practice — cell intensity is computed from
      arithmetic, not from `data.correlations` or any other input
- [ ] **Correlations empty** — empty `data.correlations` would leave the II
      section empty (no empty-state copy)
- [ ] **Purpose notes empty** — empty `data.purposeNotes` would yield empty
      footer grid (no empty-state copy)
- [ ] **Error (recoverable)** — relies on parent error boundary
- [ ] **Offline** — no explicit handling
- [ ] **Gated** — N/A
- [ ] **Standalone PWA** — renders cleanly within `LWebShell`

## Interactions

### Heatmap cells

- [ ] **Heatmap cell** — colored aspect-ratio block per (hour, day) pair
  - Function: presentational only; no hover tooltip, no click
  - Screen reader: no `aria-label`; the entire grid is visual

### Correlation row

- [ ] **Correlation row** — habit A · ↔ · habit B · value · note
  - Function: presentational only
  - Color of value: accent (`+…`) or `#7a4a3c` brown (`−…`)

### Mood × hour chart (SVG)

- [ ] **Mood SVG** — `aria-hidden="true"`; line path, four x-axis ticks (07:00,
      12:00, 17:00, 21:00), two annotated dots
  - Function: presentational only — no text equivalent

### Purpose-note card (per `data.purposeNotes`)

- [ ] **Purpose card** — accent title eyebrow + italic body
  - Function: presentational only

## Data & contracts

- **Reads**: `getAretePatterns()` from `@/lib/lilith-data/arete-depth` — returns
  `AretePatternsData` ({ correlations, purposeNotes }). BFF mapping comment:
  `getAretePatterns ← /arete/patterns/:scope`.
- **Writes**: none
- **Realtime**: none
- **Caching**: server fetch, default Next.js cache
- **Auth/role check**: app-level middleware

## Cross-references

- Domain hub: [`arete.md`](./arete.md)
- Related siblings: [`arete-review.md`](./arete-review.md) (the weekly mood
  chart re-uses similar visual idiom),
  [`arete-coaching.md`](./arete-coaching.md) (patterns Lilith names)
- Domains-namespace alternate:
  [`domains-arete-progress.md`](./domains-arete-progress.md) — internal
  progress-maps workspace covers similar territory more interactively
- Component source: `apps/oshun/web/src/components/lilith/arete.tsx` (line 1549,
  `AretePatterns`)
- Data: `apps/oshun/web/src/lib/lilith-data/arete-depth.ts`
  (`getAretePatternsFixture`)
- Feature spec: [`V1/features.md`](../../../V1/features.md#arete)

## Open questions / known gaps

- [ ] The heatmap cell values are **computed inline from hardcoded arithmetic**
      (e.g., `if (hour === 7 && d < 6) v = 0.85`) rather than driven by `data` —
      wiring to live signal data is still TODO
- [ ] No hover tooltip on heatmap cells, no text-equivalent for screen readers
- [ ] Mood SVG is `aria-hidden` with no text fallback
- [ ] "after the sit" / "after the read" annotations on the mood chart are
      hardcoded x/y positions, not data-driven
- [ ] Caption "The mornings are fierce; the lunchtime walk thins on Wed/Thu…" is
      hardcoded prose, not data-driven
- [ ] How does this relate to `/domains/arete/progress` — that surface is more
      interactive (filters, assistant handoff); document V1-shipping vs internal
