V1 Web PWA · Journey

Journey: Broken Arete streak to humane recovery

A source-reconciled journey record for V1 Web PWA — shipped behavior, state boundaries, failure modes, and the automation evidence available today.

automated · 1 spec verdict: partial· 2026-05-29
9sections16 minread

On this page
Journey at a glance
ActorsDaily-practice user, one missed dayDaily-practice user, three quiet daysUser with seven+ quiet daysUser who explicitly declined accountability promptsUser in a crisis frame
Automation1 E2E spec(s) · verdict: partial· 2026-05-29

Journey flow#

Generated from the authored steps below — click a node to jump to that section.

flowchart TD s1["1. User opens Arete on a quiet day"] s2["2. User opens the streak surface"] s3["3. After three quiet days, recovery surfaces"] s4["4. Recovery surface renders the humane copy"] s5["5. User chooses an offer"] s6["6. Plan re-scoping (optional)"] s7["7. Streak resumes without shaming"] s8["8. Weekly review captures the pattern"] s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 --> s8 click s1 href "#1-user-opens-arete-on-a-quiet-day" click s2 href "#2-user-opens-the-streak-surface" click s3 href "#3-after-three-quiet-days-recovery-surfaces" click s4 href "#4-recovery-surface-renders-the-humane-copy" click s5 href "#5-user-chooses-an-offer" click s6 href "#6-plan-re-scoping-optional" click s7 href "#7-streak-resumes-without-shaming" click s8 href "#8-weekly-review-captures-the-pattern" s8 --> v(["verdict: partial"]) click v href "../results/arete-streak-recovery.html" style v stroke:#d6a531,color:#d6a531

The streak goes quiet. After three quiet days, Lilith checks in — with a small offer, not a debt. Verifies that the Humane Streak and Recovery Policy holds end-to-end: the recovery surface uses the coach persona's hard ban on "shaming language, escalating-stake framing, punitive streak rhetoric" (V1/features.md#persona-taxonomy), the streak surface continues to render with the existing "promise" copy, and the recovery offers are framed as "not a debt" — not as catch-up debt to be repaid.

Personas#

  • Daily-practice user, one missed day — within the 24h grace window; no visible change yet, no recovery prompt
  • Daily-practice user, three quiet days — qualifies for the recovery check-in surface at /arete/recovery
  • User with seven+ quiet days — qualifies for the deep re-engagement flow with goal re-evaluation
  • User who explicitly declined accountability prompts — drift detection must still flag to the coaching summary, never to leaderboards or social per the no-shame floor
  • User in a crisis frame — recovery prompts and humane check-ins are suspended in favor of safety surfaces; verifies the crisis policy supersedes the Arete recovery flow

Pre-conditions#

  • User signed in; middleware permits /arete, /arete/streak, and /arete/recovery
  • getArete() reads the member's /v1/arete/room state and returns an honest unavailable room rather than fallback streak fixtures when the feed fails (see customer/04-arete/arete.md)
  • getAreteStreak() returns AreteStreakData with streakCount, streakNote, weekHeatmapStartDateIso, weekHeatmap, promise, todaysInvitation (see customer/04-arete/arete-streak.md)
  • getAreteRecovery() returns AreteRecoveryData with quietDaysLabel, welcomeHeadline, welcomeBody, noticed, offers, journalPlaceholder (see customer/04-arete/arete-recovery.md)
  • Lilith coach persona is the active persona on Arete surfaces per V1/features.md#persona-taxonomy; tone band is reflective (V1/features.md#tone-band-catalog)
  • No Lilith crisis frame is active (the crisis policy supersedes — see the failure modes below)

Steps#

1. User opens Arete on a quiet day#

  • Navigate to /areteAreteRoom renders the intention rows, week stats, evening prompt, and "The coach · this week" card (per customer/04-arete/arete.md)
  • Each intention row shows a streak chip eyebrow and a 7-day cell bar
  • On a single quiet day, the streak chip still reads the prior count; 24h grace window applies per V1/features.md#humane-streak-and-recovery-policy
  • Copy MUST NOT say "you broke your streak" anywhere — this is the no-shame floor

2. User opens the streak surface#

  • Click into the streak surface at /arete/streak — per customer/04-arete/arete-streak.md, the page wraps a PhoneFrame inside an LWebShell with the masthead kicker "A practice, gently kept" and title "The streak."
  • The lede card carries the promise copy: "A streak should never punish a Sunday off. Two rest days a week are part of the practice. Three quiet days in a row, and Lilith will check in — never before."
  • The phone canvas renders the day numeral, the streak count, the streakNote ("Two quiet days are folded into the count, as we agreed."), the 42-cell heatmap, and the "promise" callout card
  • Bottom action row shows Adjust (ghost, hrefs to /arete/plan?intent=adjust) and Begin (primary, hrefs to /tara/sit/when-the-mind-wanders) — wired 2026-05-26.
  • Heatmap cell colors derive from state (kept = accent, half, rest, today = ink with 2px accent border, else rule2), and each cell exposes a date + state accessible label — verified 2026-06-24.
  • No copy on the streak surface uses guilt or punishment language

3. After three quiet days, recovery surfaces#

The trigger is the recovery-prompts rule in V1/features.md#humane-streak-and-recovery-policy: "after 3, recovery-focused content (Tara recovery practice, Arete plan re-scope)".

  • On the user's next visit, the recovery surface at /arete/recovery is reachable (per customer/04-arete/arete-recovery.md Entry points: a push notification, a link from /arete/streak, or a home rail "soft return" CTA)
  • The real /v1/arete/streak recovery engine honors the grace threshold: two missed windows stay in grace_window with an intact disposition and "Recover with one repetition"; the third missed window crosses into freeze_window with protected disposition and "Protect the streak and return" (arete-streak-recovery, 2026-06-29). The browser /arete/recovery route itself is still static/reachable rather than gated by this engine state.
  • The masthead reads "The soft return" / "Welcome back."
  • The lede block describes the surface: "Three days went quiet. Lilith names what she observed (the recital, the long workdays), then offers three returns at three sizes. None are a debt."

4. Recovery surface renders the humane copy#

  • Phone canvas shows the quietDaysLabel accent eyebrow ("three quiet days")
  • Welcome headline italic + body lead/emphasized/rest spans render
  • "What Lilith noticed" callout card with accent left bar uses data.noticed (the recital, the long workdays — names what's been observed without inferring blame)
  • "An offer · not a debt" stack — three offer cards from data.offers (first one styled as primary)
  • One-sentence written option ("what was it like?") renders a real labelled textarea, character count, submit gate, and saved/error status; Save note POSTs the browser-entered note to /v1/arete/recovery/journal — verified 2026-06-26.
  • Bottom action row has Begin · 6 min primary, linked to the first return offer's Tara sit target — verified 2026-06-24.

5. User chooses an offer#

  • The three offers correspond to three sizes (e.g., "A 6-min sit, the walk only, or a written one-sentence" per the page caption)
  • User taps one — the 6-minute offer navigates into the real Tara sit route (/tara/sit/when-the-mind-wanders?origin=arete-recovery) — verified 2026-06-24.
  • The destination session writes partial/completion and streak engagement records when completed from ?origin=arete-recovery: the Tara player posts to /v1/tara/sittings/:id/complete, the BFF creates/reuses the member-scoped Tara recovery return Arete habit, records a partial or done check-in, and /v1/arete/room reads the engaged streak back — verified 2026-06-24.
  • User chooses the written one-sentence return — the textarea stays disabled until the note is long enough, posts { note } through the real BFF, creates/reuses the member-scoped Written recovery return Arete habit, records a partial check-in, and /v1/arete/room reads the engaged streak back — verified 2026-06-26.
  • Tara player start from ?origin=arete-recovery now posts a durable BFF start receipt to /v1/tara/sittings/:id/start; the BFF records a goal3_stub_tara_session_start row and dual-writes the canonical v1_tara_ritual_session start event. The browser e2e reads the receipt back through /v1/tara/sittings/:id/sessions — verified 2026-06-30.
  • Per the Status semantics a "decline" is logged with reason and does NOT count, does NOT break streak; written/Tara "partial" returns now count as engagement.
  • No catch-up debt is implied; no "make up 3 days" framing

6. Plan re-scoping (optional)#

If the recovery has been used multiple times, the assistant proactively offers to lower difficulty or shrink scope per V1/features.md#humane-streak-and-recovery-policy:

  • The coach card on /arete ("Two adjustments offered · accept or wait until next review") presents the proposed re-scope as an invitation, not a prescription — verified 2026-06-24.
  • Approve / Not this week buttons gate the change: browser e2e seeds a real habit + check-in, writes the decision through /v1/arete/coach/decision, and reads it back through /v1/arete/room — verified 2026-06-24.
  • If the user picks Not this week, the suggestion defers; the card persists the deferral across /arete reload, exposes undo, and the coaching copy does NOT escalate the prompt — verified 2026-06-24.
  • If the user approves the offered re-scope, /arete/plan?intent=adjust reads the same real /v1/arete/room decision and carries the approved coach adjustment into the plan invitation and next intention row — verified 2026-06-26.
  • Per the Friction, Interventions, and Pattern Analysis, the pattern is captured in the weekly coaching summary, never on leaderboards or shared social

7. Streak resumes without shaming#

  • After the user completes an offer, the Arete engagement streak read-back updates on /arete through /v1/arete/room — verified 2026-06-24.
  • streakNote rewrites to reflect the quiet days "folded in" — "Two quiet days are folded into the count, as we agreed."
  • No "you broke your streak" copy is rendered anywhere — the no-shame floor holds across the heatmap, the streak card, the coach card, and the weekly review

8. Weekly review captures the pattern#

Per V1/features.md#weekly-review-and-reflection:

  • On the next weekly review at /arete/review, the structure is "celebrate / notice / choose / invite" — never "audit / scold / penalize"
  • The quiet days appear under "notice" with empathy framing, not under "celebrate" (false positivity) or anywhere shaming
  • Skipping the weekly review is itself logged but never penalized

Post-conditions#

  • The recovery engine persists its grace_window or freeze_window result; completed Tara and written returns persist member-scoped Arete engagement
  • The coaching-summary write and the promise that recovery state never reaches leaderboards or social surfaces remain requirements until their downstream read-back is covered
  • Telemetry / persistence trail: arete_recovery_surface_shown and arete_recovery_offer_chosen remain client-side telemetry expectations; starting the Tara destination from origin=arete-recovery now durably writes a Tara ritual-session start event through /v1/tara/sittings/:id/start and reads it back through /v1/tara/sittings/:id/sessions; completing that same destination durably writes arete_session_completed / arete_session_partial as an Arete check-in (done / partial) and reads it back on /arete. Saving the written one-sentence return now durably writes Written recovery return as an Arete partial check-in and reads it back on /arete.
  • Plan re-scope (if accepted) is reflected on /arete/plan and on the next intention rows

Failure modes to verify#

  • Recovery return writes streak engagement after destination completion — offer cards and Begin · 6 min navigate to real return surfaces; the recovery page itself stays navigation-only, while the Tara completion POST with origin=arete-recovery records the Arete partial / done engagement and /v1/arete/room reads it back. The same browser flow now records the Tara started-event first and reads it back through /v1/tara/sittings/:id/sessions — verified 2026-06-30.
  • Written recovery note records partial engagement — the one-sentence option is a real textarea + submit control; it posts to /v1/arete/recovery/journal, records Written recovery return as a member-scoped partial Arete check-in, and /v1/arete/room reads it back — verified 2026-06-26.
  • Streak surface actions are navigation-onlyAdjust and Begin on /arete/streak navigate to /arete/plan?intent=adjust and the Tara sit player; they do not directly mutate rest-day selection or today's streak record — verified 2026-06-26.
  • Approved re-scope propagates into plan surfacesApprove on /arete persists through the real BFF, /arete/plan?intent=adjust reads the approved coach adjustment, and the next intention row carries the same label — verified 2026-06-26.
  • Shaming copy leaks in — any of: "you broke your streak", "make up X days", "your streak is at risk", "behind schedule"; all are hard bans on the coach persona per V1/features.md#persona-taxonomy
  • Recovery surface fires too early — the BFF recovery engine now proves the "never before" side of the rule at the boundary: real habits/check-ins with two missed windows remain in grace_window/intact, while the third missed window crosses to freeze_window/protected with the recovery action (arete-streak-recovery, 2026-06-29). The route-level browser surface is still static, so this is trigger-engine coverage rather than a routed prompt-gating assertion. The rule is "after 3 quiet days" per V1/features.md#humane-streak-and-recovery-policy; the streak surface explicitly says "Lilith will check in — never before".
  • Recovery surface fires during a crisis frame — per V1/ARCHITECTURE.md#crisis-frame-cascade and the Lilith policy hierarchy, the crisis policy supersedes persona, lineage, pedagogy, and grounding policies; Arete recovery and humane check-ins are suspended in favor of the safety surface (per crisis-aware-tone-policy.md)
  • quietDaysLabel not pluralised — the fixture passes a plain string ("three quiet days"); per the per-view file Open questions, no 1 quiet day vs 3 quiet days plural-aware handling is visible; verify the production string handles 1 / 2 / 3 / 4+ correctly
  • Empty data.offers — would render the "An offer · not a debt" header with no cards; per the per-view file an empty-state design is missing; verify behavior
  • Drift signal leaks to social or leaderboards — per the no-shame floor and the Drift detection rule, drift is flagged only to the user with empathy framing and to the coaching summary; never to leaderboards or social — verify the pattern doesn't appear on /activity, /messages, or any shareable surface
  • Push notification at midnight on day 3 — per the Lilith voice and cadence policy, the recovery should NOT push at unsocial hours; user quiet-hour settings must be respected (see shell/05-notifications.md); verify the nyx_event_reminder cadence rules don't accidentally apply to Arete recovery
  • User explicitly declined accountability — declined accountability means no push and no in-app prompt beyond what the recovery surface itself provides on visit; the assistant must not initiate

E2E coverage#

  • apps/oshun/web/e2e/arete-streak-recovery.spec.ts/arete/recovery humane soft-return framing (the "What Lilith noticed" callout, the "An offer · not a debt" offers, a gentle resumption affordance) and the /arete/streak "A practice, gently kept." masthead — both asserting the no-shame floor (no "broke your streak" / "you failed" / "disappoint" copy) — plus the wired streak resumption affordances (Adjust/arete/plan?intent=adjust, Begin/tara/sit/when-the-mind-wanders), framing the return as an invitation rather than a debt. The spec's new offer-navigation test (step 5) asserts all three offer hrefs, then clicks the 6-minute offer and Begin · 6 min into the real Tara sit route, so the primary offer-selection → return-surface navigation is driven instead of asserted as inert. The streak test also asserts the 42 heatmap cells expose date + state labels for assistive technology. The same spec now seeds a real BFF habit + check-in to surface the /arete coach card, then verifies Approve, Not this week, and undo write through /v1/arete/coach/decision, read back through /v1/arete/room, survive a room reload, and keep the no-shame floor intact. The newest recovery-completion test clicks the 6-minute offer into the real Tara player, starts the sitting, asserts the live /v1/tara/sittings/when-the-mind-wanders/start write is accepted, reads the start receipt back through /v1/tara/sittings/when-the-mind-wanders/sessions, scrubs below the 80% threshold, ends the sitting, asserts the live /v1/tara/sittings/when-the-mind-wanders/complete write is accepted, then polls /v1/arete/room and renders /arete to prove the resulting Tara recovery return engagement reads back as a 1-day streak with no shaming copy. The newest streak-action guard clicks Adjust and Begin from /arete/streak, asserts the plan and Tara destinations, verifies the Tara player remains not-started, and reads /v1/arete/room before/after each navigation to prove these actions do not mutate Arete engagement state. The newest approved-re-scope leg records Approve through /v1/arete/coach/decision, reloads /arete/plan?intent=adjust, and asserts the plan invitation plus next intention row both carry the approved coach action from the real room state. The newest written-return leg fills the one-sentence textarea, verifies short-note submit gating, captures the browser payload to /v1/arete/recovery/journal, forwards it to the live BFF, and then reads /v1/arete/room plus /arete to prove the resulting Written recovery return partial engagement is visible without shaming copy. The latest timing test seeds two isolated real member records through /v1/arete/habits and /v1/arete/habits/:habitId/check-in: one with two missed windows and one with three. It then reads /v1/arete/streak to prove the former remains grace_window / intact and the latter crosses into freeze_window / protected, with exact missed-window labels and humane action copy.
  • Coverage depth: partial — see coverage.md.
  • Uncovered: the /arete/recovery offers + Begin · 6 min are now real navigable affordances (faithful hrefs to /tara/sit / /arete/plan, driven by the offer-navigation test), and the written one-sentence return now records partial engagement through the live BFF, so step 5 is no longer inert/product-blocked. The destination session-completion -> streak-record read-back is covered through the live BFF. The BFF trigger-engine timing is covered for the two-missed-window grace case and third-missed-window recovery threshold. The recovery-origin Tara session-start write is now covered through a BFF start receipt and read-back. What remains uncovered is a browser-visible route/notification gate that only opens recovery after that engine threshold, and the crisis-supersedes-recovery failure mode; cross-domain Tara streak consistency lives in the Tara journeys.

Per-view files touched by this journey#

Cross-references#

Open questions / known gaps#

  • Wire destination session completion back to Arete so the recovery offer records partial / completed and streak engagement state — verified 2026-06-24.
  • Add a durable session-start write for recovery-origin Tara sittings — verified 2026-06-30 via /v1/tara/sittings/:id/start + /v1/tara/sittings/:id/sessions.
  • Cover the BFF recovery trigger timing for the two-missed-window grace boundary vs the third missed window — verified 2026-06-29 via real /v1/arete/streak.
  • Add a rest-day picker / streak-record write behind /arete/streak's Adjust and Begin navigation paths, if this surface is meant to mutate the streak directly.
  • Carry an approved coach card re-scope into /arete/plan and the next intention rows — verified 2026-06-26.
  • Add plural-aware quietDaysLabel rendering (1 quiet day vs 3 quiet days) — see per-view file Open questions.
  • Document the exact push cadence and quiet-hours behavior for Arete recovery (no notification at midnight, even on day 3).
  • Define the empty-state for the "An offer · not a debt" section when data.offers is empty.