home.mdarete-coaching.mdarete-goal.mdarete-plan.mdarete-streak.mdarete.mddomains-arete-habits.mddomains-arete-journal.mdJourney flow#
Generated from the authored steps below — click a node to jump to that section.
The cross-view flow from "user wants to add a new ritual" through "habit
created, first check-in recorded, streak begins, home rail reflects it." The
journey traverses both the polished Lilith room (/arete) and the deeper
internal habit-system surface (/domains/arete/habits), because the polished
hub's + New ritual link is the natural starting point while the multi-step
creation flow lives in the internal namespace. Catches drift between the two
namespaces, the local-vs-server persistence boundary in the internal habit
system, and the streak-cannot-start case when the create-write never persists
across surfaces. The polished /arete/habits/new proposal lane is also covered
as the adjacent "held for coach review" path: its browser form fields are mapped
to the same real BFF habit contract, but the proposed habit does not start a
streak until coach review.
Personas#
- First-habit user — signed in with no Arete intentions; the BFF-backed room
renders the explicit
data-arete-room-emptystate and points to the habits page - Adding-Nth-habit user — already has 1–4 intentions; the polished
+ New ritualbutton is the natural entry; the deeper habit-system surface exists at/domains/arete/habits - Coach-suggested user — accepts or defers a Lilith coach-card proposal;
both decisions persist through
/v1/arete/coach/decision - Recovery-resuming user — broken streak; new habit should not be punished
by past quiet days (see
customer/04-arete/arete-streak.mdpromise copy)
Pre-conditions#
- Signed-in session
getArete()reading/v1/arete/roomintoAreteData({ intentions, weekStats, coachNote, eveningPrompt }); an unavailable feed returns an honest empty room rather than invented streak fixtures, percustomer/04-arete/arete.md- The internal
/domains/arete/habitssurface is reachable; client component importssampleHabits,sampleHabitStacks,sampleIdentityStatements,sampleHabitAnalyticspercustomer/04-arete/domains-arete-habits.md - Service worker active; offline behaviour deferred to
shell/03-pwa-behavior.md
Steps#
1. Land on home and pick the Arete entry#
- User on
/after sign-in -
HomeAretePracticeSectionrail visible with "Continue habit" / "See plan" affordances percustomer/02-home-discovery/home.md -
DomainCardGridshows the Arete domain card - Click the Arete card →
/arete - Alternatively click "See plan" →
/arete/plan(verifycustomer/04-arete/arete-plan.mdentry points), or useg-chord / shell-nav "Today" tab - Verify:
customer/02-home-discovery/home.mdArete practice section
2. Arete hub renders#
page.tsx awaits getArete() → <AreteRoom data={data} />. The room mounts
LCustomerNav active="today" and LSubNav active="habits" with items Habits ·
Journal · Weekly review · Patterns.
- Page server-blocks on
getArete()(noloading.tsxunder/arete) - Sub-nav highlights "Habits"
- Domain masthead reads "Arete · habits", display title "The small daily rituals."
- Existing intention rows, when present, render a "Done today" / "Pending" tag and seven-day cell bar; a fresh member instead sees the empty state
- "+ New ritual" link is visible at the bottom of the intentions list and
targets
/domains/arete/habits?wizard=loop - Coach card visible on the right with
data.coachNote, "Approve" / "Not this week" buttons - Verify:
customer/04-arete/arete.mdlayout regions
3. Pick the create entry point#
Three valid entry points; each lands the user in a different state.
- Entry A —
+ New ritualbutton on/arete— links to/domains/arete/habits?wizard=loop. Fixed 2026-06-23:/domains/arete/habits/page.tsxnow readssearchParamsand initializesAreteHabitSysteminloop_wizard, so the handoff lands in the wizard instead of dropping to the dashboard. - Entry B — Coach card "Approve" / "Not this week" — wired 2026-05-26
via
AreteCoachActionsclient component (apps/oshun/web/src/components/lilith/AreteCoachActions.tsx). Approve shows the "queued for the next review" confirmation; Not-this-week shows "Set aside for now". Both decisions POST/v1/arete/coach/decision, read back through/v1/arete/room, and can be undone to pending. - Entry C — Each intention row's
↗arrow is now a<Link>to/domains/arete/habits?habit=<label>so the per-habit detail surface can read it. The polished↗was a static<span>until 2026-05-26. - Entry D —
Propose a habiton/arete/habits— links to/arete/habits/new, verifies the label/cadence/domain/why form contract in the polished browser surface, then posts those same values through authenticated real HTTP toPOST /v1/arete/habitsand reloads/arete/habitsto prove the server-rendered page reads the BFF habit back. This is intentionally a proposal path, not the immediate Cue→Routine→Reward active-habit wizard.
4. Enter the habit creation wizard (HabitLoopWizard)#
Inside /domains/arete/habits, the dashboard exposes nine sub-views via the
view state union:
dashboard | loop_wizard | four_laws | stacking | identity | keystone | streaks | analytics | reminders | detail
per
customer/04-arete/domains-arete-habits.md.
- Tap the "New Habit" entry in
HabitDashboard, or land via/domains/arete/habits?wizard=loop - State transitions:
viewflips fromdashboardtoloop_wizard;<HabitLoopWizard onBack onCreate />mounts - Wizard steps cover the shipped Atomic-Habits loop scaffolding: cue
(
CueType), routine description/duration/two-minute version, and reward (RewardType, immediate reward, long-term benefit) - Identity tie-in: from the same workspace, the final wizard step lists
sampleIdentityStatements; the user can attach one identity statement to the new habit, the selected id is persisted inuseAreteStore, andIdentityPanelreads it back under the matching identity percustomer/04-arete/domains-arete-habits.md. - Confirm scope, ritual phrasing, frequency, reminder cadence
- Back affordance:
onBackreturns toview === 'dashboard' - Verify:
customer/04-arete/domains-arete-habits.mdview branches andHabitLoopWizardbehaviour
5. Save the habit#
Per
customer/04-arete/domains-arete-habits.md
the internal workspace now writes the same BFF habit-create contract that feeds
the polished /arete room, then mirrors the BFF id into the local store for the
same-browser dashboard/Home handoff.
- Submit the wizard
- Wizard builds a
HabitDataobject from Cue/Routine/Reward, POSTs/v1/arete/habits, stores the returnedhabitIdin the Arete Zustand store (localStorage["oshun.arete"]), closes, and returns toview === 'dashboard'. If an identity statement was selected, the outgoingwhyItMatterssummary includes it and the local habit also keepsidentityStatements: [identityId]. - New habit row appears in
HabitDashboard - Browser wizard POSTs to
/v1/arete/habits; the polished/aretehub reads the same habit back through/v1/arete/room, including from a second browser context with no Arete localStorage seeded - The polished
/arete/habits/newproposal form's browser field contract covers label, cadence, domain source, and rationale; Playwright uses the same values in an authenticated realPOST /v1/arete/habits, verifies the held-for-review response, confirms authenticatedGET /v1/arete/habitsreads the proposed habit back withstreakDays: 0andtodayKept: false, and reloads/arete/habitsto prove the page renders the live BFF row. - Telemetry:
arete_habit_createdfires after the browser wizard receives a successful BFFhabitId, carrying the persisted id, label, cadence, source surface, cue type, duration, and identity-binding metadata.
6. Return to the polished /arete hub#
The polished room and the internal habit system remain separate surfaces, but the created habit now shares a BFF-backed source of truth.
- Navigate to
/aretevia shell-nav "Today" or direct URL - Intention rows render — the just-created habit appears from
/v1/arete/room - The row's detail link points back into
/domains/arete/habits?habit=... - Verify:
customer/04-arete/arete.mdData & contracts —/aretereads real member habits and week stats from/v1/arete/room
7. First check-in#
- On
/arete, locate the new habit row read back through/v1/arete/room - In the polished hub, the
↗action is a detail link to/domains/arete/habits?habit=<label>; it is not a check-in control. The check-in affordance therefore lives in the internal surface today - On
/domains/arete/habitsdashboard, tap the per-habit toggle for the wizard-created habit - The toggle POSTs
/v1/arete/habits/:habitId/check-inwithstatus: "done", applies the BFFstreak.currentDays/completedTodayresult touseAreteStore, and changes the dashboard toggle to the rest-day amend affordance - Verify:
customer/04-arete/domains-arete-habits.mdInteractions → "Per-habit toggle"
8. Streak begins#
- After the first check-in, the habit's streak transitions from 0 to 1 in the dashboard/local continuity store from the BFF response
- Return to
/arete; the intention row showsDone today,1-day streak, and the weekly KPI reads1 / 7completed across 1 ritual - Open
/aretein a second browser context using the same real member session but nooshun.aretelocal continuity store; the intention row still showsDone today,1-day streak, and1 / 7via the fresh authenticated/v1/arete/roomBFF read - Navigate to
/arete/streak(direct URL; the per-view filecustomer/04-arete/arete-streak.mdflags that/areteintention rows show a streak eyebrow but nohrefis wired) - The phone-shell
AreteStreaksurface reads the live/v1/arete/streakrollup: a fresh member renders an honest0 days, and the real first check-in path reads back1 dayon/arete/streak - The 42-cell heatmap on
/arete/streakmarks today's cell withtodaystate (ink fill with 2px accent border) — the per-view file flags that today-state colour isinkwith accent border - Promise copy "Two rest days a week are part of the practice" remains
visible — humane recovery requirement per
customer/04-arete/arete-streak.md
9. Return to home and confirm rail update#
- Click shell-nav Home (or
g h) -
HomeAretePracticeSectionrail now includes the new locally created habit as the active continuation commitment, even when the Arete Home BFF endpoint is unavailable - The primary continuation CTA targets the Arete habits lane (verify against
customer/02-home-discovery/home.mdArete practice section) - After the first check-in, the live Home Arete rail reads the local
continuity store back as
1d streak • done today -
HomeFootingSummaryStrip([data-kpi-grid]) reflects the same local Arete check-in by adding the pending same-day completion to the live Home practice-streak metric; legacyKpiGridremains preview-only percustomer/02-home-discovery/home.md - Verify:
customer/02-home-discovery/home.mdArete practice section
10. Coach-card observation (lagged)#
The Sunday letter on /arete/coaching proposes adjustments based on patterns
Lilith named. New habits should not trigger an adjustment until at least one
week of data is in.
- Navigate to
/arete/coaching(direct URL — the per-view filecustomer/04-arete/arete-coaching.mdflags that the coach card from/areteis not wired to here) - The letter does not yet mention the new habit (week-old data requirement)
- On the next Sunday boundary, Lilith may surface an adjustment in the letter
Post-conditions#
- A new habit row exists in the BFF habit store, is mirrored into the local continuity store, and is visible on both the internal dashboard and polished Arete room
- Streak invariant: first check-in produces streak = 1 (not 0, not 2)
- Home
HomeAretePracticeSectionrail reflects the locally created habit as the current continuation commitment for the same browser profile - No double-write: navigating between
/areteand/domains/arete/habitsand back does not create duplicate habits - Telemetry:
arete_habit_createdis verified for the BFF-backed wizard create. The broader sequence remains candidate instrumentation:arete_hub_viewed→arete_habit_create_started→arete_habit_checkin_first→arete_streak_started
Failure modes to verify#
- Formerly inert "+ New ritual" control on
/arete— fixed 2026-05-26 viahref="/domains/arete/habits?wizard=loop"on the LBtn. If the link regresses, the polished room cannot start the create flow. - Formerly inert coach-card decisions — fixed 2026-05-26 via the
AreteCoachActionsclient component (5-test suite). Decisions POST/v1/arete/coach/decisionand read back through/v1/arete/room. - Wizard Create was a no-op — fixed 2026-06-23.
Create Habitnow builds a habit, POSTs it to/v1/arete/habits, persists the returned BFF id touseAreteStore, returns to the dashboard, renders the new row, and reads back on the polished/aretehub. - Home rail ignores the wizard-created habit — fixed 2026-06-23.
HomeAretePracticeSectionpassesuseAreteStore.habitsintobuildHomeAretePracticeModel; the model treats active local habits as Home commitments, promotes the newest local habit into the continuation card, and routes the primary CTA to the Arete habits lane. This is same-browser local read-back only, not a BFF/cross-device sync claim. - First check-in stayed local-only for wizard-created habits — fixed
2026-06-23. Persisted wizard habits now POST
/v1/arete/habits/:habitId/check-in, apply the BFF streak/today result to the dashboard anduseAreteStore, read back asDone today/1-day streak/1 / 7on/arete, and surface1d streak • done todayon the live Home Arete rail. The live Home footing strip also adds the pending same-day local Arete completion to its practice-streak metric while profile sync is still pending. - Local-only sample-habit toggles in
/domains/arete/habits—sampleHabitscheck-ins mutate component state only because their fixture ids do not exist in the member's BFF habit store; Playwright now asserts the seeded toggle flipsaria-pressed, restores on a second click, emits no/v1/arete/habits/:habitId/check-inPOST, and creates no persisted local habit. - Namespace drift between
/areteand/domains/arete/habitsfor newly created habits — fixed 2026-06-23. The wizard writes/v1/arete/habitsand the polished room reads/v1/arete/room; seeded sample rows remain local to the internal workspace. - Formerly inert
/arete/streak"Adjust" and "Begin" controls — both are wired as navigation links (/arete/plan?intent=adjustand/tara/sit/when-the-mind-wanders); no direct streak-mutation write exists on the streak surface -
LSubNavitems lackedhref— fixed 2026-05-26. AreteRoom's sub-nav items now carry hrefs: Habits →/domains/arete/habits, Journal →/domains/arete/journal, Weekly review →/arete/review, Patterns →/arete/patterns. - No
loading.tsxorerror.tsxon/arete— per-view file flags this; a failinggetArete()falls through to the nearest ancestor boundary (or global error) - Streak increment race for wizard-created habits — fixed 2026-06-23.
The persisted-habit toggle writes the BFF check-in first, then
/aretereads the same check-in via/v1/arete/room. Seeded sample habits remain internal-only and are not represented on/arete. - Polished proposal form field contract was not tied to the real BFF habit
contract — fixed 2026-06-27.
/arete/habits→Propose a habitnow has Playwright coverage that drives/arete/habits/new, verifies the browser field semantics, posts the same payload through authenticated real HTTP, reads the proposed habit back through authenticatedGET /v1/arete/habits, and reloads the browser page to prove live server-rendered read-back.
E2E coverage#
apps/oshun/web/e2e/arete-create-habit.spec.ts— the habit dashboard/domains/arete/habits(the HabitLoopWizard opens via "New Habit" and Back returns to the dashboard;/domains/arete/habits?wizard=looplands directly in the wizard; Cue→Routine→Reward POSTs the real BFF habit-create route, persists the returned BFF habit id locally, emits browser telemetry eventarete_habit_createdwith the persisted habit id and habit-loop metadata, renders the new row, persists a selectedsampleIdentityStatementsid locally, asserts the outgoing BFFwhyItMattersidentity line, and reads the created habit back inIdentityPanel, POSTs the first check-in to/v1/arete/habits/:habitId/check-in, applies the returned streak locally, reads backDone today/1-day streak/1 / 7on polished/arete, opens a second browser context with the same real member session and nooshun.aretelocal store to prove/areterehydrates the checked-in habit from/v1/arete/room, reads back1d streak • done todayon Home Arete, and reads the checked-in habit's live overall rollup back on/arete/streakas1 day; the same return-to-Home path assertsHomeFootingSummaryStrip([data-kpi-grid]) increments the practice-streak metric from the browser's current profile streak plus the pending local Arete check-in; the seeded per-habit toggle is still driven as a fixture-only boundary — a click inverts itsaria-pressedstate, a second click restores it, no BFF check-in POST leaves the browser, and no persisted local habit is created); the polished/arete/habitsproposal lane (Propose a habit→/arete/habits/newfield contract → same payload real BFF POST → authenticated BFF GET read-back with zero streak/no kept-today state → browser/arete/habitslive row read-back); and the/arete/streaksurface (no-punish promise copy, live zero-streak count + gently-kept lede for a fresh member, 42-cell six-week heatmap with one accessibletodaymarker at cell 41, legend, and the wiredAdjust→/arete/plan?intent=adjust/Begin→/tara/sit/when-the-mind-wandersresumption affordances).apps/oshun/web/e2e/arete-full-journey.spec.ts— drives the older broad Arete path, including the controlled accepted-response "held for coach review" acknowledgement plus coach Approve / "Not this week" decision + undo. The proposal form's real-BFF read-back now lives inarete-create-habit.spec.ts.apps/oshun/web/e2e/arete-habits-bff.spec.ts— the genuine server habit-create engine, over real HTTP against the running BFF:POST /v1/arete/habitspersistence + GET round-trip, and the422 habit_label_too_shortvalidation rejection. The same spec now proves the live check-in loop and guardrails: create/check-in 401 auth gates, invalid check-in status 422, owner-only check-in with non-enumerating 404 for another member, no-store mutation responses, durabledoneread-back intotodayKept/streakDays, and same-day amendment to an explicit rest day (skip→grace) with the list returning totodayKept: false.- Coverage depth: partial — see
coverage.md. - Uncovered: cross-device check-in sync is now covered for the polished
/areteBFF read model, and/arete/streaknow reads the live/v1/arete/streakrollup for both fresh-zero and first-check-in states. Home's Arete continuation remains intentionally same-browser local continuity, and legacyKpiGridhabits-this-week coverage is not asserted becauseKpiGridis not part of the live Home surface. Remaining partial-depth items are the older manual checklist gaps for reminder-cadence/scope confirmation, lagged Sunday coaching observation, and the/aretemissingloading.tsx/error.tsxboundary. The polished-hub intention rows are also exercised byarete-full-journey.spec.ts.
Per-view files touched by this journey#
customer/02-home-discovery/home.md— Arete practice rail entry and return pointcustomer/04-arete/arete.md— polished Lilith room,+ New rituallink, coach cardcustomer/04-arete/arete-plan.md— "See plan" link from home railcustomer/04-arete/arete-streak.md— streak surface after first check-incustomer/04-arete/arete-goal.md— goal-to-habit binding (habit "feeds" a goal)customer/04-arete/arete-coaching.md— lagged coach feedback on the new habitcustomer/04-arete/domains-arete-habits.md—AreteHabitSystem,HabitLoopWizard, identity statementscustomer/04-arete/domains-arete-journal.md— optional habit-related journal entry after check-in
Cross-references#
- Feature spec:
V1/features.md#arete - Architecture:
V1/ARCHITECTURE.md#customer-web--appsoshunweb - Related journeys:
tara-daily-ritual.md— streak interplay with Tara sit completioninstall-as-pwa.md— relaunch target may include/arete/streak
- Shell:
- Component sources:
apps/oshun/web/src/app/arete/page.tsx— polished hub entryapps/oshun/web/src/components/lilith/rooms.tsx—AreteRoomapps/oshun/web/src/components/lilith/arete.tsx—AretePlan,AreteStreak,AreteGoal,AreteCoachingapps/oshun/web/src/components/domains/arete/AreteHabitSystem.tsx— internal workspace + nine subviewsapps/oshun/web/src/lib/lilith-data/arete.ts—getArete()apps/oshun/web/src/lib/arete/arete-simulation-data.ts—sampleHabitsapps/oshun/web/src/lib/arete/arete-types.ts—HabitData,IdentityStatement,CueType,RewardType, etc.
Open questions / known gaps#
- Confirm whether
/areteand/domains/arete/habitsshare a store in V1 for created habits — the wizard POSTs/v1/arete/habitsand/aretereads back through/v1/arete/room; internal sample rows remain local - Connect the browser
HabitLoopWizardto the actual habit-create endpoint (POST /v1/arete/habits) - Document whether the internal namespace's local-store wizard is meant to
graduate into the polished
/arete/habits/newBFF-backed create flow: it now shares the same BFF create route while retaining the richer local Cue/Routine/Reward metadata for same-browser Home continuity - Wire coach-card Approve / Not this week decisions to the BFF — decisions
POST
/v1/arete/coach/decisionand read back through/v1/arete/room - Confirm whether the streak increment is a synchronous client mutation or a
BFF round-trip — persisted wizard habits POST
/v1/arete/habits/:habitId/check-in; seeded sample rows remain local-only - Document the identity-statement → habit binding contract: a wizard-created
habit may attach one
IdentityStatementid fromsampleIdentityStatements; the selected statement is included in the BFFwhyItMatterssummary, and the id is stored in the local Arete continuity store and shown byIdentityPanel. The current BFF habit route does not expose a first-class identity column.