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

## Purpose

Lilith's letter to the practitioner — "twice a month, on a Sunday, Lilith writes
you a short letter." Named patterns, two adjustments awaiting a nod, and a
privacy note. Marked `robots: { index: false, follow: false }` in metadata.
Renders `<AreteCoaching data={await getAreteCoaching()} />` against the Lilith
design system.

## Entry points

- **From `/arete` coach card** — the weekly coach note links here (verify; in
  source `AreteRoom`'s Approve button is not wired)
- **From `/arete/review`** — the weekly review references "Lilith might suggest
  a phrase in the margin"
- **From notification / email** — a "your letter has arrived" deep link
- **Direct URL / bookmark** — yes (signed-in)
- **Not indexed** — `robots.index = false`, so no search-engine surface

## Layout regions

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

- **Header**: `LCustomerNav active="today"`
- **Masthead** (`ArMast`): left "Arete · the letter", mid
  `from Lilith · ${letterDate}`, right "private to you · always", big "A letter,
  after the _week._", lede about twice-a-month Sunday letters
- **Main, left (1.4fr)**: the letter — paper-card with `letterCode` watermark,
  recipient name, paragraphs (with optional `emphasized` accent and `chip`
  highlight spans), `— Lilith` signoff
- **Main, right (1fr)**:
  - "Patterns Lilith named" section — list of `data.patterns` rows (name +
    `conf · 0.NN` chip + evidence body)
  - "Two adjustments — your nod" section — list of `data.adjustments` rows with
    action buttons (Not now / Tell me more / Apply for one week) for
    non-accepted, "accepted · 11:42" code chip when accepted
  - "Privacy of the letter" dashed-border card

## States

- [ ] **Loading** — server-awaited; no `loading.tsx` at this route
- [ ] **Populated (default)** — Sunday letter with paragraphs, 2+ patterns, and
      2+ adjustments (fixture provides all)
- [ ] **Adjustment accepted** — `adjustments[i].accept === true` renders
      "accepted · 11:42" code chip and hides the button row
- [ ] **Adjustment pending** — `adjustments[i].accept !== true` renders Not now
      / Tell me more / Apply for one week buttons
- [ ] **Empty patterns / adjustments** — N/A: component renders the section
      headers and maps over the arrays; an empty array would yield empty space
      (no empty state coded)
- [ ] **Error (recoverable)** — relies on parent error boundary (none here)
- [ ] **Offline** — no offline handling in component
- [ ] **Gated** — N/A
- [ ] **Standalone PWA** — renders cleanly inside `LWebShell`

## Interactions

### Pattern row (one per `data.patterns`)

- [ ] **Pattern card** — name, confidence chip (`conf · 0.NN`), evidence body
  - Function: presentational only
  - Keyboard / SR: no interactive role

### Adjustment row (one per `data.adjustments`)

- [ ] **"Not now"** (button `LBtn kind="ghost" size="sm"`)
  - Function: no `onClick` handler in source — visual only
  - Telemetry: none
- [ ] **"Tell me more"** (button `LBtn kind="ghost" size="sm"`)
  - Function: no `onClick` handler in source — visual only
- [ ] **"Apply for one week"** (button `LBtn kind="primary" size="sm"`)
  - Function: no `onClick` handler in source — visual only

These are hidden when `a.accept === true`; instead a static "accepted · 11:42"
`LCode` chip renders. The "11:42" timestamp is hardcoded in the component, not
driven by data.

## Data & contracts

- **Reads**: `getAreteCoaching()` from `@/lib/lilith-data/arete-depth` — returns
  `AreteCoachingData` ({ letterDate, letterCode, recipientName,
  letterParagraphs, patterns, adjustments }). BFF mapping comment:
  `getAreteCoaching ← /arete/letters/:id`.
- **Writes**: none wired today (no handlers on Not now / Tell me more / Apply
  for one week)
- **Realtime**: none
- **Caching**: server fetch, default Next.js cache; metadata also disables
  indexing
- **Auth/role check**: relies on app-level middleware

## Cross-references

- Shell: [`shell/01-app-shell.md`](../../shell/01-app-shell.md)
- Domain hub: [`arete.md`](./arete.md)
- Sibling Arete routes (see [`arete.md`](./arete.md) for full list)
- Cross-domain: Lilith / Sophia are the AI substrate; this surface narrates
  pattern detection rather than wiring to a model in code
- Component source: `apps/oshun/web/src/components/lilith/arete.tsx` (line 1347,
  `AreteCoaching`)
- Data: `apps/oshun/web/src/lib/lilith-data/arete-depth.ts`
  (`getAreteCoachingFixture`)
- Feature spec: [`V1/features.md`](../../../V1/features.md#arete)

## Open questions / known gaps

- [ ] All adjustment-action buttons (Not now / Tell me more / Apply for one
      week) are wired as static `LBtn` with no `onClick` — confirm whether this
      is intentional pre-V1 staging
- [ ] The "accepted · 11:42" timestamp is hardcoded in the component instead of
      pulled from `adjustment.acceptedAt` — verify when wiring real data
- [ ] Where does the "letter" actually originate — Sophia (AI substrate) or a
      domain-specific Lilith service? No imports cite Sophia in this file
- [ ] Confirm the cadence ("every other Sunday" in metadata vs. "twice a month"
      in body lede) is consistent with feature spec
