V1 Web PWA · Surface walkthrough

Arete · the patterns

A per-surface walkthrough of the V1 Web PWA customer surface: layout, states, interactions, data, and cross-references.

walked
8sections3 minread

On this page

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 /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 SVGaria-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
  • 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 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