Context. surface customer · domain arete · route /arete/goal · auth signed-in · source apps/oshun/web/src/app/arete/goal/page.tsx
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
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 inAretePlan(nohrefwired; 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.habitsrows (label, frequencyLCode, progress text, percent in mono accent; current fixture: three habits) - IV · Journal · selected — list of
data.journalentries (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-onlytext 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
- V · The arc — 90-day SVG chart (today marker at day 47, projected line to
day 90) plus
States#
- Loading — N/A: server-awaited; no
loading.tsxat this route - 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
AreteGoalDatabut renders the 90-day arc unconditionally; "completed" visual variant is not present in source - Error (recoverable) — relies on parent error boundary
- Offline — real
sw.jstreats/arete/goalas an exact shell route; after warmup, the signed-in portrait replays while the browser context is offline with no generic fallback header - Gated — N/A within the component; route remains signed-in via app-level middleware/session seeding
- 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)#
- 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#
- "Block on calendar" (button
LBtn kind="ghost" size="sm")- Function: no
onClickhandler in source;LBtnrenders it as a disabled placeholder witharia-disabled="true",data-placeholder="true", and the placeholder tooltip - Telemetry: none
- Function: no
- "Open notebook" (button
LBtn kind="primary" size="sm")- Function: no
onClickhandler in source; same disabled placeholder contract as above - Telemetry: none
- Function: no
Adjustment rows (per data.adjustments)#
- "Not now" (button
LBtn kind="ghost" size="sm")- Function: no
onClickhandler in source; disabled placeholder
- Function: no
- "Apply" (button
LBtn kind="primary" size="sm")- Function: no
onClickhandler in source; disabled placeholder
- Function: no
Arc chart (SVG)#
- 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-onlysummary 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— returnsAreteGoalData({ 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.jsexact shell route allowlist includes/arete/goalso warmed documents replay offline - Auth/role check: app-level middleware
Cross-references#
- Domain hub:
arete.md - Plan / Review siblings:
arete-plan.md,arete-review.md - Domains-namespace alternate:
domains-arete-goals.md— theAreteGoalSysteminternal 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 - E2E:
apps/oshun/web/e2e/arete-goal-portrait.spec.tscovers the route-level read contract, placeholder controls, accessible arc summary, mobile standalone layout, and real service-worker offline replay
Open questions / known gaps#
- None of the action buttons (Block on calendar / Open notebook / Not now /
Apply) have
onClickhandlers inAreteGoal; they now render and test as disabledLBtnplaceholders 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 - 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