V1 Web PWA · Journey

Journey: Arete Living Offering create

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

automated · 5 specs verdict: partial· 2026-05-29
9sections14 minread

On this page
Journey at a glance
ActorsFirst-offering userReturning composerCoach-mode pickerAt-risk userTenant-bound user
Automation5 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. Land on the offering surface"] s2["2. Capture the intention"] s3["3. Lilith crisis pre-screen"] s4["4. Coach scoring (framework selector)"] s5["5. Score renders into the composition"] s6["6. Keep — the private artifact"] s7["7. Optional — share"] s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 click s1 href "#1-land-on-the-offering-surface" click s2 href "#2-capture-the-intention" click s3 href "#3-lilith-crisis-pre-screen" click s4 href "#4-coach-scoring-framework-selector" click s5 href "#5-score-renders-into-the-composition" click s6 href "#6-keep--the-private-artifact" click s7 href "#7-optional--share" s7 --> v(["verdict: partial"]) click v href "../results/arete-living-offering-create.html" style v stroke:#d6a531,color:#d6a531

A user states an intention on /arete/offering, previews the fixture-backed 4–8 minute composition, asks the framework-aware coach for guidance, and then keeps or sends the offering after Lilith's safety pre-screen. The walk does not claim that the coach response dynamically rewrites the score grid: those are separate shipped interactions. It verifies that the offering, safety, coach, receipt, and member-scoped read-back paths do not expose the private intention outside the originating user's records.

Personas#

  • First-offering user — has Arete cadence but has never composed an offering; lands on /arete/offering empty (the fixture currently still renders a populated draft — see Failure modes)
  • Returning composer/arete/offering resumes a draft
  • Coach-mode picker — toggles framework between general / cbt / act / stoic / reflective before letting the score render
  • At-risk user — input prose carries a distress signal; Lilith intercepts via the Crisis-aware tone policy before the score composes
  • Tenant-bound user — declared residency must scope both the coach BFF and the kept artifact

Pre-conditions#

  • Signed-in session; middleware permits /arete/offering, /arete/coaching, /profile/safety
  • getAreteOffering() returns AreteOfferingData ({ sceneTimeline, scoreOptions, toneSwatch, sharingRules }); BFF mapping comment marks /arete/offerings/:id (see customer/04-arete/arete-offering.md)
  • Lilith policy substrate wired into the keep/send action surface per V1/features.md#crisis-aware-behavior
  • Coach BFF POST /v1/arete/coach/responses reachable (route in apps/oshun/bff/src/routes/arete.ts); direct chat route in apps/oshun/web/src/components/domains/arete/AreteAICoach.tsx and local fallback composer in apps/oshun/web/src/components/domains/arete/AreteCoach.tsx
  • No Lilith crisis frame currently active

Steps#

1. Land on the offering surface#

  • Navigate to /arete/offering; page.tsx awaits getAreteOffering()<AreteOffering> inside an LWebShell with LCustomerNav active="today"
  • Masthead reads "Arete · the living offering" / "An intention, composed into a small scene." with the 4–8 minute lede
  • Verify: customer/04-arete/arete-offering.md layout regions

2. Capture the intention#

  • User enters intention prose in the "I · The intention" card; the shipped surface now renders a real textarea with [data-arete-offering-intention-input], and the scene preview line follows edited prose
  • User edits the recipient and occasion in Section V; the sharing controls update the masthead summary, send label, and BFF-backed action island state before keep/send

3. Lilith crisis pre-screen#

Same hook walked in crisis-aware-tone-policy.md §1–§3 applied to Arete intent capture.

  • On keep/send submit, AreteOfferingActions calls /v1/lilith/adapter/safety-assessment?role=safety with the browser intention and region: 'US' before writing /v1/arete/offerings/keep
  • If signal ≥ threshold: the keep/send write aborts, no offering is persisted, and [data-arete-offering-safety-interrupt] mounts in the action region with crisis resources and a /profile/safety link. The full durable crisis-frame activation/audit cascade remains covered by crisis-aware-tone-policy.md, not by this client-side preflight.
  • If adjacent (below threshold): substrate applies the reflective tone band — coach scoring leans reflective even if user picked another
  • Clean path: substrate marks intention safe; flow continues to the draft/send keep route

4. Coach scoring (framework selector)#

  • User picks framework (CBT / ACT / Stoic / Reflective / general) directly on /arete/offering through [data-arete-offering-coach-framework-option]; the panel exposes the selected BFF framework on [data-arete-offering-coach-panel]
  • Client POSTs { framework, userMessage } to /v1/arete/coach/responses; BFF calls composeAreteCoachResponse(input) from apps/oshun/bff/src/arete/coach-responses.ts — deterministic keyword-routed templates (CBT separates situation/thought/feeling; Stoic invokes dichotomy of control; ACT pivots on values; Reflective asks for a one-paragraph honest pause)
  • On non-2xx: /arete/offering renders [data-arete-offering-coach-error], clears stale preview copy, leaves the preview button enabled, and a second click can retry into the live BFF
  • Cross-ref: V1/features.md § Living Offerings; coverage.md finding #9 documents this pipeline

5. Score renders into the composition#

  • User can change the selected III · Score option in the 3-column data.scoreOptions grid; the initial selection is seeded from the selected fixture option, and the active option exposes aria-pressed
  • Coach response still does not dynamically compose the /arete/offering score grid; the route renders fixture-backed score options
  • II · Scene preview (16/9 simulated video, "▶" glyph) renders with [data-arete-offering-scene-label] derived from sceneTimeline[0] and [data-arete-offering-scene-preview-line] derived from the current intention
  • Scene timeline strip (waveform SVG, scrub head at 12%), IV · Materials 3x3 swatch grid driven by data.toneSwatch with tile select + drag/drop + Move earlier / Move later reorder controls, V · Sharing recipient/occasion controls + data.sharingRules rows all render
  • Cue-privacy promise card (accent-bordered) carries the "working copy is destroyed" copy, "Read the full promise" opens [data-arete-offering-promise-detail], and "See the C2PA receipt" calls /v1/arete/offerings/receipt to render a member-scoped receipt envelope with C2PA/cue-privacy/private-intent hashes

6. Keep — the private artifact#

  • Click "Save draft" (AreteOfferingActions button); client pre-screens the browser intention through Lilith, then POSTs /v1/arete/offerings/keep with { recipient, occasion, action: 'draft', intention } and renders data-arete-offering-state="draft-kept"
  • Expected: write to Arete offerings store with draft state per V1/features.md#living-offerings ("kept in their gallery as a personal artifact, and re-renderable forever from the underlying score")
  • Cue-privacy promise: intent layer scoped to userId; BFF must not surface intent prose in any aggregate
  • Telemetry (when wired): arete_offering_kept

7. Optional — share#

  • Click "Send to {recipient} · {occasion}" (AreteOfferingActions button); client pre-screens the browser intention through Lilith, then POSTs /v1/arete/offerings/keep with { recipient, occasion, action: 'send', intention } and renders data-arete-offering-state="delivered"
  • Expected: share decision persists to the Arete offerings store and can be read back from /v1/arete/offerings/sent; policy still needs to be reconciled with the Living Scenes shareability matrix per V1/features.md#living-scenes; "watch once · on a single device" enforcement per the sharing rules
  • Telemetry (when wired): arete_offering_shared

Post-conditions#

  • Intention crisis-pre-screened at keep/send time; either a blocking result terminates in the action-region safety interruption, or the substrate marks the intention safe and the write proceeds
  • A score composed for the picked framework — different frameworks produce measurably different prose; BFF outage renders an honest recoverable preview error instead of stale or simulated score copy
  • If Save: offering record is accepted by the Arete offerings store and the UI renders draft-kept; the signed-in /arete/offerings gallery reads the member-scoped live row back with the private intention preview
  • If Send: share record persists and is returned by /v1/arete/offerings/sent; the signed-in /arete/offerings gallery also reads the sent row back from the live store with the selected recipient and occasion; watch-policy / expiry enforcement remains an open gap against the Living Scenes shareability matrix
  • Private intent layer scoped to originating user — the keep/send preview is member-scoped, and the C2PA receipt envelope hashes the raw intention instead of rendering it back in the browser

Failure modes#

  • Save draft / Send are wiredAreteOfferingActions replaced the inert footer buttons; both actions POST /v1/arete/offerings/keep and render BFF-backed status (draft-kept / delivered)
  • Save draft / Send write returns 5xx — the action island renders [data-arete-offering-error][role="alert"], keeps the active control enabled, and a second browser click retries the same intention through the real BFF to draft-kept or delivered
  • Read promise / See C2PA receipt are wired — the promise button toggles [data-arete-offering-promise-detail]; the receipt button POSTs the current recipient, occasion, intention, and selected score to /v1/arete/offerings/receipt, renders receipt-issued, C2PA manifest id, cue-privacy hash, and private-intent hash, and does not render raw intention prose in the receipt panel
  • Recipient and occasion selection — Section V recipient/occasion inputs update the send target, the browser POST body, /v1/arete/offerings/sent readback, and the /arete/offerings gallery row
  • Dynamic masthead / scene summary — the masthead summary follows edited recipient + occasion, the scene label is derived from sceneTimeline[0], and the scene preview line follows edited intention
  • Score selection state — score is no longer hardcoded by index; the client control seeds from the selected scoreOptions field and updates on click
  • Crisis pre-screen bypassed if substrate not wired — keep/send now calls the Lilith safety assessment route before writing; a blocking crisis-like intention mounts [data-arete-offering-safety-interrupt] and makes no /v1/arete/offerings/keep call
  • Safety preflight unavailable — a non-2xx /v1/lilith/adapter/safety-assessment response renders [data-arete-offering-error][role="alert"], leaves Save draft / Send enabled for retry, and still makes no /v1/arete/offerings/keep call
  • Pre-submit composition can still preview crisis prose — the live scene preview and fixture score grid update before the keep/send safety preflight; /arete/offering still needs a product decision on whether score preview should also be halted before action submit
  • Coach BFF returns 5xx on /arete/offering — the coach-preview panel renders [data-arete-offering-coach-error], clears any stale response, leaves the preview button enabled, and retries the same intention + framework into the live BFF
  • Direct coach page BFF returns 5xx/domains/arete/coach renders an honest "coach could not be reached" bubble and no fake suggestion chips
  • Framework mismatchCoachMode includes goals / habits / motivation which all fall through to general per modeToBffFramework; user picks "Goals" expecting goal-specific scoring and gets the general lens
  • Material reorder controls — the caption now matches real tile selection plus drag/drop and Move earlier / Move later controls
  • Arete binary C2PA signing remains render-pipeline scoped — the offering surface now has a real BFF receipt envelope, but the route still does not invoke the Living Scenes/Aja media renderer to sign concrete output bytes

E2E coverage#

  • apps/oshun/web/e2e/arete-living-offering-create.spec.ts — steps 1, 2 & 5: the /arete/offering composition surface — masthead + Lilith identity, the editable intention textarea (I) feeding the scene preview line, scene preview (II) + timeline with a scene label derived from sceneTimeline[0], score grid (III) including click-to-select state, materials swatch (IV) including selected-tile reorder via Move earlier / Move later, sharing controls (V) including recipient/occasion edits that update the masthead summary and send target, and the cue-privacy promise including the full-promise reveal.
  • apps/oshun/web/e2e/arete-offering-actions.spec.ts (wave7)steps 6–7 (keep / send) at the real BFF layer: "Save draft" pre-screens the browser-entered intention through the real /v1/lilith/adapter/safety-assessment?role=safety BFF route, POSTs clean intentions to /v1/arete/offerings/keep, renders draft-kept with the private intention preview, and "Send" POSTs the same route to render delivered. The crisis branch enters a blocking intention, observes the real browser POST /v1/lilith/adapter/safety-assessment?role=safety request carrying the auth-context bearer and exact { userMessage, region: 'US' } payload, verifies the safety interruption + 988 resources + /profile/safety link, and asserts no keep/send write happens. The same spec forces a safety-assessment 503 and verifies the action island fails closed before any keep/write. The spec reads the persisted member-scoped offering back from the BFF and asserts the sent intention preview is present only on that member's record. It also drives both buttons from the browser and reloads /arete/offerings, proving the signed-in gallery renders the live draft and sent rows with their member-scoped intention previews. The Save-draft / Send buttons are no longer inert — they drive the keep/share write end-to-end. The spec also forces one 503 from /v1/arete/offerings/keep for each footer action, asserts the visible alert + enabled retry state, then retries through the real BFF and verifies the draft-kept / delivered preview. It also edits recipient + occasion, asserts the browser POST body, reads the selected values back from /v1/arete/offerings/sent, and verifies the live /arete/offerings gallery row. The same spec now drives "See the C2PA receipt" from the browser, forwards it to the real /v1/arete/offerings/receipt BFF route, verifies the receipt id/C2PA manifest/cue-privacy/private-intent hashes, proves the selected recipient, occasion, and score feed the receipt, and asserts the raw private intention is not rendered in the receipt panel or direct BFF receipt JSON.
  • apps/oshun/web/e2e/arete-coach-bff.spec.ts (new)step 4 (framework-aware coach scoring) at the real BFF layer: POST /v1/arete/coach/responses returns genuinely framework-DISTINCT guidance (Stoic dichotomy-of-control vs CBT thought-vs-fact vs ACT defusion, asserted against the engine's per-framework markers), echoes the framework, falls back to general for an unknown framework, and gates on auth (401) + a non-empty message (400 missing-user-message). Real HTTP via the request fixture, not a fixture.
  • apps/oshun/web/e2e/arete-coach-ui.spec.ts (new)step 4 at the direct browser UI layer: authenticated /domains/arete/coach starts with an empty chat, sends the typed prompt to the real BFF as { framework: 'reflective', userMessage }, renders the reflective coach response and four suggestion chips, and covers a forced 503 as an honest recoverable coach bubble with no fake suggestions.
  • apps/oshun/web/e2e/arete-offering-coach-preview.spec.ts (new)step 4 on the actual /arete/offering surface: fills the offering intention, selects the Stoic framework, asserts the browser POST body sent to /v1/arete/coach/responses, forwards that request to the real BFF, renders the framework-specific dichotomy-of-control response plus four suggestions, then forces one 503 and verifies the recoverable error before a retry renders the live ACT response.
  • Coverage depth: partial — the offering composition body now has real intention, dynamic scene preview copy, score-selection, material-reorder, and recipient/occasion controls that update the masthead summary, and keep/send actions are wired; see coverage.md.
  • Uncovered: adjacent/below-threshold tone-band handoff, pre-submit score-preview safety gating, and Arete binary media signing beyond the receipt envelope remain open. Steps 2, 3 keep/send preflight, and 6–7 are now covered — the intention textarea plus recipient/occasion controls feed "Save draft" / "Send", the safety assessment now goes directly from the browser to the live Lilith BFF with the auth-context bearer, clean intentions POST /v1/arete/offerings/keep (draft-kept / delivered) per arete-offering-actions.spec.ts, and the live /arete/offerings gallery reads those private rows back, including a forced 503 + successful retry for both Save draft and Send, so they are no longer inert. Step 5 now also covers score selection and material reorder on the offering surface. The framework-aware coach scoring (step 4) is now covered at the BFF layer by arete-coach-bff.spec.ts (the route + framework-distinct scoring), at the direct browser UI layer by arete-coach-ui.spec.ts, on the actual /arete/offering surface by arete-offering-coach-preview.spec.ts, plus the engine unit spec apps/oshun/web/src/components/domains/arete/__tests__/AreteCoach.test.tsx.

Per-view files touched by this journey#

Cross-references#

  • Feature spec: V1/features.md § Living Offerings
  • Coach pipeline: WALKTHROUGH/matrix/coverage.md finding #9 — framework-aware BFF endpoint shipped 2026-05-25
  • Related journeys: crisis-aware-tone-policy.md (pre-screen step in full), arete-create-habit.md (sibling Arete flow)
  • BFF route: apps/oshun/bff/src/routes/arete.ts (POST /v1/arete/coach/responses)
  • Offering BFF routes: apps/oshun/bff/src/routes/domain-stubs.ts (GET /v1/arete/offerings, POST /v1/arete/offerings/keep, GET /v1/arete/offerings/sent)
  • Coach composer: apps/oshun/bff/src/arete/coach-responses.ts
  • Client coach: apps/oshun/web/src/components/domains/arete/AreteCoach.tsx (BFF call + local fallback)
  • Offering component: apps/oshun/web/src/components/lilith/AreteOfferingWorkspace.tsx

Open questions / known gaps#

  • Add gallery/read-back coverage for the draft and sent paths; the /arete/offerings gallery now reads the authenticated member's live store rows and arete-offering-actions.spec.ts asserts both private intention previews from the browser (2026-06-26)
  • Add recipient/occasion selection coverage; Section V edits now feed the live keep/send POST and gallery read-back (2026-06-27)
  • Confirm whether the intent prose passes through Lilith synchronously at /arete/offering submit time; keep/send now calls the safety-assessment route before any keep write (2026-06-27)
  • Decide whether the pre-submit scene preview and fixture score grid should also be gated by Lilith before the user clicks keep/send
  • /arete/offering can pick the coach framework directly through the coach-preview panel; users do not need to visit /domains/arete/coach first for step-4 scoring
  • Verify keep/share records honour tenant residency — no residency context in composeCoachHandler's request body
  • The receipt surface is documented and wired through /v1/arete/offerings/receipt; it returns a receipt id, manifest id, cue-privacy hash, and private-intent hash without echoing the raw intention
  • Bind the receipt to concrete rendered media bytes through the Living Scenes/Aja release pipeline; the current envelope is not proof of binary media signing
  • Confirm the scope of the "private intent layer never leaves the originating user" guarantee — what aggregations are permitted?