V1 Web PWA · Surface walkthrough

Arete · habits

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

walked
8sections7 minread

On this page

Context. surface customer · domain arete · route /arete · auth signed-in · source apps/oshun/web/src/app/arete/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; 2026-06-23 review-closure read-back verified through arete-review-close.spec.ts. 2026-06-27 Codex Arete hub addendum — signed-in /arete first-run and seeded mixed habit states against the real BFF room model, canonical metadata, Lilith subnav links, intention row and new-ritual navigation, evening prompt, week stats, coach affordances, and standalone PWA relaunch/mobile overflow safety are covered by the E2E references below.

Purpose#

The Arete domain hub — "the small daily rituals — and a humane coach who watches the patterns." A polished Lilith room with member habits for the week, evening prompt, week stats, Lilith's weekly coach note, and the latest weekly-review closure when the member has saved or closed a review. The page awaits getArete() and renders <AreteRoom> — analogous to /tara. See V1/features.md.

Entry points#

  • Shell nav: LCustomerNav "Today" — Arete room mounts with active="today"
  • Domain card on /DomainCardGrid cell → /arete
  • Domain switcher in shell header / /switcher
  • Home rail: Arete practice section (HomeAretePracticeSection) — "See plan" / "Continue habit"
  • PWA shortcut (if defined in manifest.json shortcuts[])
  • Direct URL / bookmark — yes (auth required)
  • Deep link from notification — Arete habit reminder push

Layout regions#

page.tsx is a thin server component: await getArete()<AreteRoom data={data} />. AreteRoom (see apps/oshun/web/src/components/lilith/rooms.tsx, line 341) is an LWebShell composition.

  • Header: LCustomerNav active="today" (Today is highlighted)
  • Sub-nav: LSubNav active="habits" with items Habits · Journal · Weekly review · Patterns
  • Domain masthead row: eyebrow "Arete · habits", display title "The small daily rituals.", AI disclosure "Coaching · patterns, never streaks"
  • Main (left column, 1.5fr desktop; one-column mobile stack): intention rows (one per habit) — label, streak chip, "Done today" / "Pending" tag, 7-day cell bar, kebab arrow . On tablet/phone the row reflows so the 7-day bar spans the row and the page does not create document-level horizontal overflow.
  • Below intentions: "+ New ritual" ghost button, evening-prompt block (headline + citation + journal text card + tag chips)
  • Aside (right column, 1fr desktop; stacked below the main column on mobile): "This week" card (completed / total big numeral, intention count line, bestStreak / atRisk / started fields)
  • Weekly-review continuity card (right column, conditional): renders when data.reviewClosure exists; selector data-arete-review-continuity-state="week-closed|kept-private"; links back to /arete/review
  • Coach card (right column): "The coach · this week" with data.coachNote body, "Approve" / "Not this week" buttons, eyebrow "Two adjustments offered · accept or wait until next review"

States#

  • LoadinggetArete() is awaited server-side; no client loading.tsx under /arete, so server blocks until data ready
  • Returning user, populated — intention rows render from the member's real stored habits and check-ins via /v1/arete/room
  • Mixed completion — some intentions show "Done today" (green/ok) others "Pending" (muted)
  • Empty intentionsdata-arete-room-empty explains that proposed habits from the habits page will appear here
  • Review closed/saved — conditional weekly-review continuity card reads the latest reviewClosure from /v1/arete/room
  • Error (recoverable) — failed BFF reads return areteUnavailable(); the room renders honest empty stats and an unavailable coach note
  • Offline — no explicit offline handling in component; relies on SW shell cache
  • Gated — N/A: no auth or feature-flag gate visible in page.tsx
  • Standalone PWA — cold-launch lands here if relaunch target is /arete; nav chrome renders within LWebShell

Interactions#

Current E2E evidence:

  • apps/oshun/web/e2e/arete-hub.spec.ts/arete signed-in hub render, real BFF room read, canonical metadata, first-run empty state, seeded mixed Done/Pending habits, subnav hrefs, intention-row detail links, new-ritual navigation, evening prompt/tags, week stats, coach affordances, standalone PWA relaunch, and mobile overflow safety
  • apps/oshun/web/e2e/arete-streak-recovery.spec.ts — humane recovery/streak journey, Arete engagement read-back on /arete, durable coach decision approve/decline/undo, and plan propagation
  • apps/oshun/web/e2e/arete-review-close.spec.ts — weekly-review close/save writes, arete_weekly_review_closed browser telemetry, /v1/arete/review/closed persistence, and /arete continuity-card read-back from /v1/arete/room
  • apps/oshun/web/e2e/arete-create-habit.spec.ts and apps/oshun/web/e2e/arete-habits-bff.spec.ts — habit proposal UI and real BFF habit/create/check-in persistence
  • Habits / Journal / Weekly review / Patterns (tab links)
    • Function: all four items carry an href (lines 351–354) and navigate — Habits → /domains/arete/habits, Journal → /domains/arete/journal, Weekly review → /arete/review, Patterns → /arete/patterns; active="habits" highlights the current tab
    • Keyboard: tab order matches DOM source order
    • Screen reader: announces label only; no role="tablist" on the wrapper
    • Telemetry: none wired in AreteRoom

Intention rows (one per habit)#

  • Habit row (grid row)
    • Function: visually shows habit label, streak eyebrow, today-status text, 7-day cell bar, and a trailing link
    • Click target: the is a next/link <Link href="/domains/arete/habits?habit=<label>" aria-label="Open {label} detail"> (lines 443–456) — it navigates to the habit detail, encoding the habit label as a query param
    • Screen reader: the link announces "Open {label} detail"; the rest of the row has no role / aria-label and announces as static text
    • Touch target: the link is small (≤ 14 px font); flag for review

"+ New ritual"#

  • "+ New ritual" (LBtn kind="ghost" size="md" href="/domains/arete/habits?wizard=loop")
    • Function: navigates to /domains/arete/habits?wizard=loop (line 460), opening the loop-habit wizard
    • Keyboard: standard link/button focus
    • Telemetry: none

Evening prompt card#

  • Card body — displays eveningPrompt.journalText and tag chips (LChip)
    • Function: presentational; no click handlers
  • Tag chipsdata.eveningPrompt.tags rendered as LChip spans

"This week" stats card#

  • weekStats.completed / total (large numeral)
    • Function: presentational
  • bestStreak, atRisk, started (LField rows)
    • Function: presentational key/value lines

Weekly-review continuity card#

  • Latest review closure (data-arete-review-continuity)
    • Function: appears only when /v1/arete/room includes reviewClosure sourced from the member-scoped areteReviewStore
    • States: data-arete-review-continuity-state="week-closed" renders "Week closed"; kept-private renders "Private letter saved"
    • Click target: "Review the week" link returns to /arete/review
    • Coverage: arete-review-close.spec.ts closes the week, verifies arete_weekly_review_closed, verifies /v1/arete/review/closed, returns to /arete, and asserts this card

Coach card#

  • Coach note body — renders data.coachNote (the at-risk-Sunday-reading observation from fixture)
  • Approve / "Not this week" (live in <AreteCoachActions />, line 554 — see apps/oshun/web/src/components/lilith/AreteCoachActions.tsx)
    • Function: fully wired client component. "Approve" (LBtn kind="primary" size="sm") has onClick={approve}; "Not this week" (LBtn kind="ghost" size="sm") has onClick={decline}. The decision POSTs to /v1/arete/coach/decision and reads back through /v1/arete/room.
    • States: once decided, the buttons are replaced by a confirmation eyebrow — "Approved · two adjustments queued for the next review" (ok color) or "Set aside for now · the letter returns on Sunday" — each with an undo button that resets the decision (writeStored('pending')). The wrapper carries data-coach-decision="pending|approved|declined" for E2E selectors.
    • Read-on-mount: the server-provided coachDecision initializes the island so a returning user sees their prior choice.
    • Telemetry: none

Data & contracts#

  • Reads: getArete() from @/lib/lilith-data/arete fetches /v1/arete/room and returns AreteData ({ intentions, weekStats, coachNote, coachActionLabel, coachDecision, reviewClosure, eveningPrompt }). The BFF computes habits and weekly stats from stored habits/check-ins and folds in the latest member-scoped review closure when present.
  • Writes: the coach decision (Approve / "Not this week") POSTs /v1/arete/coach/decision via AreteCoachActions. Weekly-review close/save writes originate on /arete/review (/v1/arete/review/close) and read back here through /v1/arete/room. "+ New ritual" and the intention-row navigate (no write).
  • Realtime: none
  • Caching: authenticated server fetches use cache: 'no-store' in bffGet; anonymous public reads keep a short revalidate window
  • Auth/role check: relies on app-level middleware (no per-route check in page.tsx)

Cross-references#

Open questions / known gaps#

  • Add a dedicated /arete recoverable-error browser slice that proves the areteUnavailable() room when the BFF room read fails; the route has no local error.tsx
  • Relationship between /arete (polished Lilith room) and /domains/arete/habits (large tabbed AreteHabitSystem) is unclear — the latter looks like a deeper internal workspace; document which ships in V1