---
path: /arete/goal
surface: customer
domain: arete
auth: signed-in
source: apps/oshun/web/src/app/arete/goal/page.tsx
status: walked + e2e-covered
last_walked:
  '2026-07-01 by Codex (arete-goal-portrait.spec.ts covers the signed-in
  one-page goal portrait, fixture-backed criteria/habit/journal/adjustment read
  contract, disabled placeholder action semantics for all unwired controls,
  screen-reader summary for the aria-hidden 90-day SVG arc, mobile standalone
  no-horizontal-overflow rendering, exact `/arete/goal` service-worker shell
  caching, and real offline document replay), against commit HEAD'
  '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 goal, on one page

## Purpose

"A goal sits on one page." Why · done-is · habits that feed it · journal
excerpts · 90-day arc · the next thing. Renders
`<AreteGoal data={await getAreteGoal()} />` against the Lilith design system.

## Entry points

- **From `/arete` / `/arete/plan`** — a goal card or "Open the chapter goal ›"
  ghost button in `AretePlan` (no `href` wired; verify routing)
- **From notification** — "your next thing is due" deep link
- **Direct URL / bookmark** — yes (signed-in)
- **From `/domains/arete/goals`** — internal goal-system dashboard may link here
  (no explicit link found in source; verify)

## Layout regions

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

- **Header**: `LCustomerNav active="today"`
- **Masthead** (`ArMast`): left "Arete · goal · one true page", mid "goal of 90
  days · day 47", right "committed · private to you and your coach", big "Write
  the _chapter._", lede about one-page goal anatomy
- **Main, left (1.4fr)**:
  - I · Why — long prose paragraph ("The notebook on the ruling part has waited
    a long time…") with hardcoded body text and emphasized "_One thing, well._"
  - II · Done is — two-column grid of `data.criteria` (criterion + accent value;
    current fixture: four criteria)
  - III · Habits that feed it — list of `data.habits` rows (label, frequency
    `LCode`, progress text, percent in mono accent; current fixture: three
    habits)
  - IV · Journal · selected — list of `data.journal` entries (date eyebrow +
    italic quote)
- **Aside, right (1fr)**:
  - V · The arc — 90-day SVG chart (today marker at day 47, projected line to
    day 90) plus `sr-only` text summary for screen readers
  - "elapsed · 47 days, remaining · 43 days, pace · 39 wds/day, needed · 50
    wds/day" 2x2 grid (values hardcoded in component)
  - VI · The next thing — accent-bordered card with "tomorrow · 90 min" eyebrow,
    hardcoded "Finish §III. Walls vs. estate. ~600 words.", two buttons (Block
    on calendar / Open notebook)
  - VII · Adjustments · coach — list of `data.adjustments` (suggestion + sub)
    each with Not now / Apply buttons

## States

- [x] **Loading** — N/A: server-awaited; no `loading.tsx` at this route
- [x] **Populated (default fixture)** — fixture renders chapter-goal "day 47 of
      90", four criteria, three habits, three journal entries, and two
      adjustments
- [ ] **Empty criteria / habits / journal / adjustments** — empty arrays would
      render empty grids; no empty-state copy coded
- [ ] **Past-goal / completed** — N/A: component is parameterized by
      `AreteGoalData` but renders the 90-day arc unconditionally; "completed"
      visual variant is not present in source
- [ ] **Error (recoverable)** — relies on parent error boundary
- [x] **Offline** — real `sw.js` treats `/arete/goal` as an exact shell route;
      after warmup, the signed-in portrait replays while the browser context is
      offline with no generic fallback header
- [x] **Gated** — N/A within the component; route remains signed-in via
      app-level middleware/session seeding
- [x] **Standalone PWA** — iPhone standalone rendering is readable and has no
      horizontal overflow after responsive-page and habit-row grid fixes

## Interactions

### Habit row (one per `data.habits`)

- [x] **Habit row** — label, frequency `LCode`, progress note, percent
  - Function: presentational only — no per-row click target
  - Screen reader: announces as static text

### "The next thing" card

- [x] **"Block on calendar"** (button `LBtn kind="ghost" size="sm"`)
  - Function: no `onClick` handler in source; `LBtn` renders it as a disabled
    placeholder with `aria-disabled="true"`, `data-placeholder="true"`, and the
    placeholder tooltip
  - Telemetry: none
- [x] **"Open notebook"** (button `LBtn kind="primary" size="sm"`)
  - Function: no `onClick` handler in source; same disabled placeholder contract
    as above
  - Telemetry: none

### Adjustment rows (per `data.adjustments`)

- [x] **"Not now"** (button `LBtn kind="ghost" size="sm"`)
  - Function: no `onClick` handler in source; disabled placeholder
- [x] **"Apply"** (button `LBtn kind="primary" size="sm"`)
  - Function: no `onClick` handler in source; disabled placeholder

### Arc chart (SVG)

- [x] **90-day chart** — `aria-hidden="true"` SVG with grid, target dashed line,
      history path, today marker (day 47, "1,840 wds"), projected dashed line to
      day 90, "PROJECTED" label
  - Function: presentational — no zoom / hover
  - Screen reader: adjacent `sr-only` summary states day 47, 1,840 finished
    words, elapsed/remaining days, current pace, and words-per-day needed

## Data & contracts

- **Reads**: `getAreteGoal()` from `@/lib/lilith-data/arete-depth` — returns
  `AreteGoalData` ({ criteria, habits, journal, adjustments }). BFF mapping
  comment: `getAreteGoal ← /arete/goals/:id`.
- **Writes**: none wired (no handlers on any button)
- **Realtime**: none
- **Caching**: server fetch, default Next.js cache; `public/sw.js` exact shell
  route allowlist includes `/arete/goal` so warmed documents replay offline
- **Auth/role check**: app-level middleware

## Cross-references

- Domain hub: [`arete.md`](./arete.md)
- Plan / Review siblings: [`arete-plan.md`](./arete-plan.md),
  [`arete-review.md`](./arete-review.md)
- Domains-namespace alternate:
  [`domains-arete-goals.md`](./domains-arete-goals.md) — the `AreteGoalSystem`
  internal workspace (SMART / OKR / WOOP / 12-week wizards)
- Component source: `apps/oshun/web/src/components/lilith/arete.tsx` (line 760,
  `AreteGoal`)
- Data: `apps/oshun/web/src/lib/lilith-data/arete-depth.ts`
  (`getAreteGoalFixture`)
- Feature spec: [`V1/features.md`](../../../V1/features.md#arete)
- E2E:
  [`apps/oshun/web/e2e/arete-goal-portrait.spec.ts`](../../../apps/oshun/web/e2e/arete-goal-portrait.spec.ts)
  covers the route-level read contract, placeholder controls, accessible arc
  summary, mobile standalone layout, and real service-worker offline replay

## Open questions / known gaps

- [x] None of the action buttons (Block on calendar / Open notebook / Not now /
      Apply) have `onClick` handlers in `AreteGoal`; they now render and test as
      disabled `LBtn` placeholders rather than live affordances.
- [ ] Product decision still needed: should Block on calendar, Open notebook,
      Not now, and Apply become real persisted flows, and if so which BFF routes
      own them?
- [ ] The "Why" prose, "elapsed/remaining/pace/needed" values, and "The next
      thing" card body are hardcoded in the component, not driven by
      `AreteGoalData` — confirm whether this is fixture leakage or intentional
      editorial framing
- [x] The SVG arc is `aria-hidden`; a screen-reader text summary now supplies
      the current/projection values.
- [ ] Relationship to `/domains/arete/goals` (multi-framework goal system) —
      that surface has SMART / OKR / WOOP / 12-week-year wizards while this one
      is a single-goal portrait; document V1-shipping vs internal
