# Journey: Arete weekly review (Sunday)

Sunday's push or in-app reminder arrives, the user opens `/arete/review`, walks
the four acts (look back / acknowledge / pivot / commit), checks the week's KPI
tiles and journal lines, sees the continuity card, and commits a plan for the
next week. Tests the cadence reminder, the four-act composition, the KPI/journal
sidebar, and Lilith's recovery/streak tone policy.

## Personas

- **Sunday-reminder user** — receives the configured Sunday-evening push per
  [`V1/features.md`](../../V1/features.md#weekly-review-and-reflection) ("Sunday
  evening default; skipping is itself logged but never penalized")
- **Skipped-last-week user** — closed late or missed; this Sunday's review must
  not shame the skip per the no-shame floor
- **Multi-quiet-day user** — has had quiet days; the review's `kept`, `partial`,
  `rest`, and `quiet` metrics must remain descriptive, never punitive
- **Tenant-bound institutional user** — institutional review cadence may differ;
  the Sunday default is configurable per tenant
- **Power-user reviewer** — prefers the deeper internal surface at
  `/domains/arete/plan-review`

## Pre-conditions

- Signed-in session; middleware permits `/arete`, `/arete/review`,
  `/arete/coaching`
- `getAreteReview()` returns `AreteReviewData` ({ weekLabel, acts, kpi, days });
  BFF mapping comment marks `/arete/reviews/:weekId`
- Notification permission previously granted (push delivery walked in
  [`install-as-pwa.md`](./install-as-pwa.md) §13)
- Lilith `coach` persona on Arete with `reflective` tone band per
  [`V1/features.md#persona-taxonomy-tone-band-catalog-and-crisis-recovery-journey`](../../V1/features.md#persona-taxonomy-tone-band-catalog-and-crisis-recovery-journey)
- No crisis frame active (crisis policy supersedes)

## Steps

### 1. Sunday push or in-app reminder fires

- [x] Account-visible reminder category: `/profile/notifications` renders
      `arete-weekly-review` with `Cadence · weekly`, backed by the real Arete
      domain reminder preference; `profile-notifications-index` patches
      `/v1/notifications/preferences/domains/arete` and proves the weekly-review
      category turns off without turning general reminders off.
- [x] In-app fallback banner: `/arete` reads the same real Arete reminder
      preference through `/v1/arete/room`, renders the Sunday-evening
      `arete-weekly-review` banner when the in-app modality is enabled, and
      removes it after the Arete reminder preference is disabled.
- [ ] OS push delivery per cadence config (default: Sunday evening), if granted
- [x] Reminder copy: humane copy ("a quiet moment, the week kept"); never "you
      missed your review" / "you broke your streak"
- [ ] Push target: `/arete/review`; tap → service worker `notificationclick`
      listener focuses the existing tab or cold-launches (see
      [`install-as-pwa.md`](./install-as-pwa.md) §13)
- [x] In-app target: banner CTA opens `/arete/review`
- [x] Telemetry: `arete_review_reminder_shown` emits after the BFF-backed banner
      mounts on `/arete`

### 2. Arrive on `/arete/review`

`page.tsx` is a thin server component: `await getAreteReview()` →
`<AreteReview>`, inside `LWebShell` with `LCustomerNav active="today"`.

- [ ] Page server-blocks on `getAreteReview()` (no `loading.tsx`)
- [ ] Masthead reads "Arete · sunday review" / `data.weekLabel` / "four acts ·
      twenty minutes", display title "Sunday. _Look back, gently._", lede about
      Celebrate · Notice · Choose · Invite
- [ ] **Verify**:
      [`customer/04-arete/arete-review.md`](../customer/04-arete/arete-review.md)
      layout regions

### 3. Walk Act I — Celebrate (look back)

Four acts stack vertically on the left (1.4fr). Each act renders a header row
(eyebrow / title / `LCode` duration), a mixed-emphasis body, and a two-column
grid of list items.

- [x] Act I header eyebrow reads "act I · what held" (accent if `a.accent`)
- [x] Body composes `a.bodyParts` — mixed text with `emphasized` (accent italic)
      and `chip` (highlight bg) inline spans
- [x] List items in rounded outlined cards; copy must not shame — celebrate
      names what went well per
      [`V1/features.md`](../../V1/features.md#weekly-review-and-reflection)

### 4. Act II — Acknowledge / Notice

- [x] Act II header: "act II · what slipped"; body names patterns without
      inviting shame per the no-shame floor (same rule as
      [`arete-streak-recovery.md`](./arete-streak-recovery.md) §4)
- [x] List items are named patterns (e.g., "Wednesday dip", "evening practice
      slipped")

### 5. Act III — Choose / Pivot

- [x] Act III header: "act III · one change"; body asks the user to choose what
      to commit to next week per
      [`V1/features.md`](../../V1/features.md#weekly-review-and-reflection)
- [x] List items are candidate commitments; per-view file flags no per-act
      completion state today (user cannot mark Choose individually)

### 6. Act IV — Invite / Commit

- [x] Act IV header: "act IV · into the week"; body invites release per
      [`V1/features.md`](../../V1/features.md#weekly-review-and-reflection)
- [x] List items are release / commitment statements

### 7. KPIs + journal lines (right column)

- [x] "The week, kept" renders all four `data.kpi` metrics in one flexible row;
      each tuple `[k, v, c]` renders big serif numeral coloured by `c` (`accent`
      / `ink` / muted via `kpiColor` helper)
- [x] "Mood thread" SVG (aria-hidden) draws a smooth quadratic curve across
      mon–sun; caption renders `data.moodCaption` so the named weekly pattern
      can adapt with the review data
- [x] "What you wrote" lists `data.days[i] = [day, txt]` with date code + italic
      text; no per-entry link to the journal route

### 8. Continuity card and next-week plan

Per [`V1/features.md`](../../V1/features.md#weekly-review-and-reflection)
("continuity card, next-practice recommendations, plan adjustments saved with
user approval"), the review output composes into a continuity card.

- [x] Footer note: "The review is yours; Lilith never alters a sentence. She
      might suggest a phrase in the margin."
- [x] Click the margin-suggestion reveal; a data-backed phrase appears beside
      the footer note, `aria-expanded` flips to true, and the rendered review
      body remains unchanged
- [x] Click "Save as private letter" (`AreteReviewClose` button); client POSTs
      `/v1/arete/review/close` with `{ weekLabel, action: 'saved-private' }` and
      renders `data-arete-review-close-state="kept-private"`
- [x] Click "Close the week" (`AreteReviewClose` button); client POSTs
      `/v1/arete/review/close` with `{ weekLabel, action: 'closed' }` and
      renders `data-arete-review-close-state="week-closed"`. The same persisted
      closure now feeds `/arete` through `/v1/arete/room` as a continuity card;
      richer plan adjustments remain subject to user approval per feature spec
- [x] Telemetry: after the real BFF close/save response succeeds,
      `AreteReviewClose` emits `arete_weekly_review_closed` with
      `surface: 'arete_review'`, `sourcePath: '/arete/review'`, `weekLabel`,
      `action`, `state`, and `closureId`

### 9. Return to `/arete` and verify continuity

- [x] Navigate back to `/arete`; continuity card reads back the latest
      `week-closed` / `kept-private` review closure from `/v1/arete/room`
- [ ] Home rail `HomeAretePracticeSection` reflects adjustments per
      [`customer/04-arete/arete.md`](../customer/04-arete/arete.md) data
      contract (BFF mapping notes `/arete coaching.latest-suggestion`,
      `/arete evening-prompt.today`)
- [ ] Lilith's letter at `/arete/coaching` may surface follow-up patterns named
      during the review (lagged — see
      [`arete-create-habit.md`](./arete-create-habit.md) §10 for the coaching
      cadence)

## Post-conditions

- Four acts rendered with mixed-emphasis bodies and list cards
- KPI tiles, mood SVG, day-by-day journal lines rendered without shaming
  language
- If "Close the week" pressed: closure persists to `/v1/arete/review/closed` and
  reads back on `/arete`; richer next-week plan adjustments remain an explicit
  product gap
- If either terminal footer action succeeds: the browser emits
  `arete_weekly_review_closed` with the exact persisted closure action/state
- A shown in-app reminder emits `arete_review_reminder_shown`; the product
  requirement to log a skipped review without penalizing it remains open

## Failure modes

- [x] **"Save as private letter" / "Close the week" are wired** —
      `AreteReviewClose` replaced the inert footer buttons; both actions POST
      `/v1/arete/review/close` and render BFF-backed status (`kept-private` /
      `week-closed`)
- [ ] **No per-act completion state** — user cannot mark Celebrate / Notice /
      Choose / Invite individually
- [ ] **No journal input on the review page** — `data.days` is read-only; "Save
      as private letter" persists the review closure, not editable journal text
- [x] **Mood-thread caption hardcoded** — fixed 2026-06-24:
      `AreteReviewData.moodCaption` feeds the visible caption, with component
      coverage proving a custom caption replaces the default fixture text
- [x] **Margin-suggestion affordance missing** — fixed 2026-06-24:
      `AreteReviewMarginSuggestion` reveals `data.marginSuggestion` with
      `aria-expanded`/`aria-controls` and does not mutate the review body
- [ ] **Sunday push not granted** — if push declined, the in-app banner is the
      only entry; user may miss the reminder
- [ ] **Crisis frame active** — recovery / weekly-review prompts suspended;
      safety surface takes priority per
      [`crisis-aware-tone-policy.md`](./crisis-aware-tone-policy.md)
- [ ] **Shaming copy regression** — any prose using "you broke your streak" or
      "you missed your review" violates the no-shame floor (same rule as
      [`arete-streak-recovery.md`](./arete-streak-recovery.md))

## E2E coverage

Spec:
[`apps/oshun/web/e2e/arete-weekly-review.spec.ts`](../../apps/oshun/web/e2e/arete-weekly-review.spec.ts)
— as of 2026-06-25, the four-act specimen is asserted through stable
`data-arete-review-*` anchors for page counts, masthead, every act/body/list
cell, every KPI tile/value/color, the mood chart/caption, all seven journal
rows, and the footer action region.

Spec:
[`apps/oshun/web/e2e/arete-review-reminder.spec.ts`](../../apps/oshun/web/e2e/arete-review-reminder.spec.ts)
— step 1's locally-verifiable reminder entry is now driven end-to-end: the test
patches the real Arete notification preference into in-app fallback mode, loads
`/arete`, asserts the BFF-backed Sunday-evening reminder card, proves humane
copy/no-shame wording, captures `arete_review_reminder_shown`, clicks through to
`/arete/review`, then disables the Arete reminder preference and proves the
banner disappears.

Spec (wave-7, close interaction):
[`apps/oshun/web/e2e/arete-review-close.spec.ts`](../../apps/oshun/web/e2e/arete-review-close.spec.ts)
— step 8 is now driven end-to-end: the `AreteReviewClose` island replaced the
inert `<LBtn>`s, so "Close the week" POSTs `/v1/arete/review/close` and renders
a **week-closed** confirmation, and "Save as private letter" POSTs the same
route to render **kept-private**. Both terminal actions now assert the
`arete_weekly_review_closed` analytics event after the real BFF response. The
four-act review body above it stays render-only; only the close/save row is
wired.

| Step | Test name                                                                    | Status                                                                         |
| ---- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| 2    | page loads with masthead week-label, four-act lede, and Lilith tone identity | covered via page/masthead/count anchors                                        |
| 3    | Act I — Celebrate: eyebrow, title, mixed-emphasis body, list cards           | covered via act/body/list anchors                                              |
| 4    | Act II — Notice: eyebrow, pattern body, list items                           | covered via act/body/list anchors                                              |
| 5    | Act III — Choose: eyebrow, body, list items                                  | covered via act/body/list anchors                                              |
| 6    | Act IV — Invite: eyebrow, body, list items                                   | covered via act/body/list anchors                                              |
| 7    | KPI tiles render all four labels and numeric values                          | covered via per-KPI value/color anchors                                        |
| 7    | Mood-thread SVG aria-hidden; data-backed mood caption present                | covered via mood chart/caption anchors                                         |
| 7    | Journal day lines render all seven days with italic entry text               | covered via per-row day/text anchors                                           |
| 8    | Footer note present; Save-as-letter and Close-the-week buttons present       | covered via footer/action anchors                                              |
| 8    | Margin suggestion reveal toggles a data-backed phrase without body mutation  | covered                                                                        |
| 8    | Close the week → POST /v1/arete/review/close → week-closed confirmation      | covered (`arete-review-close.spec.ts`)                                         |
| 8    | Save as private letter → POST /v1/arete/review/close → kept-private          | covered (`arete-review-close.spec.ts`)                                         |
| 8    | Close/save telemetry emits `arete_weekly_review_closed` after BFF success    | covered (`arete-review-close.spec.ts`)                                         |
| 9    | Return to `/arete` and verify weekly-review continuity card                  | covered (`arete-review-close.spec.ts` via `/v1/arete/room` read-back)          |
| —    | No-shame floor: shaming phrases absent from rendered page                    | covered                                                                        |
| 1    | Account notification index exposes the Arete weekly-review reminder category | covered (`profile-notifications-index`: real Arete preference PATCH + reload)  |
| 1    | In-app reminder fallback on `/arete`                                         | covered (`arete-review-reminder.spec.ts`: real preference PATCH + telemetry)   |
| 1    | Sunday OS push delivery                                                      | not covered — push delivery requires OS notification infra                     |
| 9    | Home rail / coaching follow-up plan adjustments                              | not covered — richer review-derived plan adjustment payload is not modeled yet |

**Coverage depth**: partial — see [`coverage.md`](./coverage.md).
`/arete/review` is a server-rendered specimen (`getAreteReviewFixture`) for the
four-act body. The specimen layer is now row/cell-level: the browser test checks
route counts, masthead text, every act's identity/title/duration/body-part kind,
every list item, every KPI value/color, the mood chart/caption, all seven
journal rows, and the footer action region through stable anchors rather than
broad page text. The close interaction is wired separately: the
`AreteReviewClose` island replaced the inert buttons, so "Close the week" and
"Save as private letter" POST `/v1/arete/review/close`, render week-closed /
kept-private, emit `arete_weekly_review_closed` with the persisted
action/state/week label/closure id, and return to `/arete` to assert the
persisted closure through `/v1/arete/room` (`arete-review-close.spec.ts`). The
same `/v1/arete/room` response now also carries the member's
`weeklyReviewReminder` snapshot from the real notification preference store, and
`arete-review-reminder.spec.ts` proves the in-app fallback card, no-shame copy,
`arete_review_reminder_shown`, `/arete/review` CTA, and preference-off removal.
The four-act content still has no per-act interaction, but the journey is no
longer shallow — its render specimen, terminal close action, close telemetry,
reminder entry, and Arete-hub read-back are all covered, and the mood-thread
caption is a review-data contract instead of JSX literal copy. The footer
margin-suggestion promise is also wired as a data-backed reveal interaction.

**Uncovered**: step 1's actual Sunday OS push scheduler/delivery (the account
notification index and `/arete` in-app fallback are covered, but OS notification
delivery remains separate infra); the four-act body's per-act completion state
(no per-act `onClick`); review-derived Home rail / coaching follow-up plan
adjustments. The margin suggestion, close/save action, reminder impression,
close telemetry, and `/arete` continuity read-back are now covered end-to-end.

> **Note — the `/arete/review` route is not inert.** The same route also mounts
> `AreteReviewDraftRun` (`apps/oshun/web/src/components/lilith/arete.tsx`), a
> **fully wired** agent surface that POSTs the real `/v1/agentic/runs` lifecycle
> (request → approve → `queued` / discard → `cancelled`) against the live BFF.
> That surface is a **different feature** from the four-act weekly review and is
> covered by
> [`agentic-pipeline-runnow-arete.spec.ts`](../../apps/oshun/web/e2e/agentic-pipeline-runnow-arete.spec.ts)
> (the
> [agentic-pipeline-customer-invocation](./agentic-pipeline-customer-invocation.md)
> journey). So "no write path is wired on `/arete/review`" would be wrong. The
> four-act review's close/save footer is also wired through `AreteReviewClose`;
> remaining gaps are the static act body and richer plan-adjustment read-back.

## Per-view files touched by this journey

- [`customer/04-arete/arete-review.md`](../customer/04-arete/arete-review.md) —
  four acts, KPI sidebar, footer action row
- [`customer/04-arete/arete.md`](../customer/04-arete/arete.md) — return point
  with continuity card
- [`customer/04-arete/arete-coaching.md`](../customer/04-arete/arete-coaching.md)
  — Lilith's letter where review-named patterns lag-surface
- [`customer/02-home-discovery/home.md`](../customer/02-home-discovery/home.md)
  — `HomeAretePracticeSection` reflects review-derived plan adjustments

## Cross-references

- Feature spec:
  [`V1/features.md` § Weekly Review and Reflection](../../V1/features.md#weekly-review-and-reflection)
- Lilith tone policy (streak / recovery): see
  [`arete-streak-recovery.md`](./arete-streak-recovery.md) for the full
  no-shame-floor walk; the same `coach` persona + `reflective` band applies per
  [`V1/features.md#persona-taxonomy-tone-band-catalog-and-crisis-recovery-journey`](../../V1/features.md#persona-taxonomy-tone-band-catalog-and-crisis-recovery-journey)
- Related journeys: [`arete-streak-recovery.md`](./arete-streak-recovery.md),
  [`crisis-aware-tone-policy.md`](./crisis-aware-tone-policy.md),
  [`install-as-pwa.md`](./install-as-pwa.md) — Sunday push walks the same
  notification path
- Component source: `apps/oshun/web/src/components/lilith/arete.tsx`
  (`AreteReview`)
- Close BFF routes: `apps/oshun/bff/src/routes/domain-stubs.ts`
  (`POST /v1/arete/review/close`, `GET /v1/arete/review/closed`)
- Data: `apps/oshun/web/src/lib/lilith-data/arete-depth.ts`
  (`getAreteReviewFixture`)
- Internal alternate: `/domains/arete/plan-review` per
  [`customer/04-arete/domains-arete-plan-review.md`](../customer/04-arete/domains-arete-plan-review.md)

## Open questions / known gaps

- [x] Add `/arete` continuity-card read-back coverage after
      `/v1/arete/review/close`; the closure persists via
      `/v1/arete/review/closed` and the hub continuity card now reads it through
      `/v1/arete/room`
- [ ] Confirm the cadence config surface — where does the user set Sunday vs
      another day per the feature spec's "configurable" promise? The account
      notification index now shows the weekly-review reminder category and the
      `/profile?path=notifications` handoff, but no day-of-week selector is
      modeled yet.
- [ ] Document the plan-adjustment approval flow — `V1/features.md` requires
      "plan adjustments saved with user approval"; where is the per-adjustment
      Approve / Defer affordance?
- [x] Wire the margin-suggestion affordance, or remove the claim — fixed by
      `AreteReviewMarginSuggestion`
- [x] Confirm the continuity card on `/arete` reads from the same BFF
      review-closure store as `/v1/arete/review/closed` — namespace drift risk
      per the [`arete-create-habit.md`](./arete-create-habit.md) finding
- [ ] Document the per-tenant cadence override path for institutional reviewers
