Context. surface customer · domain arete · route /arete/patterns · auth signed-in · source apps/oshun/web/src/app/arete/patterns/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
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
/aretesub-nav "Patterns" tab — theLSubNavitem carrieshref="/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.correlationsrows; 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-hiddenSVG line chart; "after the sit" and "after the read" callouts pinned at fixed x positions
- II · What travels together — list of
- Footer row (
borderTop: 1px solid L.ink):- IV · What this is for — 3-column grid of
data.purposeNotescards (title eyebrow + body)
- IV · What this is for — 3-column grid of
States#
- Loading — server-awaited; no
loading.tsxat 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.correlationsor any other input - Correlations empty — empty
data.correlationswould leave the II section empty (no empty-state copy) - Purpose notes empty — empty
data.purposeNoteswould 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#7a4a3cbrown (−…)
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— returnsAretePatternsData({ 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 - Related siblings:
arete-review.md(the weekly mood chart re-uses similar visual idiom),arete-coaching.md(patterns Lilith names) - Domains-namespace alternate:
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
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 bydata— wiring to live signal data is still TODO - No hover tooltip on heatmap cells, no text-equivalent for screen readers
- Mood SVG is
aria-hiddenwith 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