Journey at a glance
ActorsMobile-first signed-in userDesktop returning userAnonymous browser visitor
Automation9 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. Anonymous first browser visit"]
s2["2. Consent and prompt ordering"]
s3["3. Install education and trigger surface"]
s4["4. Native install prompt"]
s5["5. Standalone cold launch"]
s6["6. Offline read continuity"]
s7["7. Offline writes and reconnect"]
s8["8. Service-worker update"]
s9["9. Push notification leg"]
s10["10. Sign-out and member-data cleanup"]
s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 --> s8 --> s9 --> s10
click s1 href "#1-anonymous-first-browser-visit"
click s2 href "#2-consent-and-prompt-ordering"
click s3 href "#3-install-education-and-trigger-surface"
click s4 href "#4-native-install-prompt"
click s5 href "#5-standalone-cold-launch"
click s6 href "#6-offline-read-continuity"
click s7 href "#7-offline-writes-and-reconnect"
click s8 href "#8-service-worker-update"
click s9 href "#9-push-notification-leg"
click s10 href "#10-sign-out-and-member-data-cleanup"
s10 --> v(["verdict: partial"])
click v href "../results/install-as-pwa.html"
style v stroke:#d6a531,color:#d6a531
The cross-view flow from first browser visit through installed standalone launch, offline continuity, service-worker update, push routing, and sign-out cleanup. This journey exists to catch bugs that per-view checklists miss because the individual screens can look correct while the PWA runtime is stale.
Personas#
- Mobile-first signed-in user — primary path; Android Chrome direct install and iOS Safari manual Add to Home Screen instructions.
- Desktop returning user — Chromium or Edge install from the browser prompt,
address bar, or
/welcome/download. - Anonymous browser visitor — installs before signing in, then cold-starts into the welcome redirect flow.
Pre-conditions#
- App is served over HTTPS or
localhost. - Service workers are enabled unless the failure-mode test explicitly blocks them.
- Chromium/Edge/Android uses the browser
beforeinstallpromptevent; Safari on iPhone/iPad uses Share -> Add to Home Screen and never exposesbeforeinstallprompt. - There is no app-side "N qualifying interactions" counter in V1. The custom
prompt appears only after the browser fires
beforeinstallprompt, and the manual/welcome/downloadtrigger dispatches the same saved event.
Steps#
1. Anonymous first browser visit#
- Browser navigation to
/redirects an unauthenticated visitor to/welcome?redirect=%2F. -
/welcomeis public, hydrated, titled for OSHUN, and exposes the auth entry plus primary public CTA. -
/manifest.jsonis linked once from the document head. -
/sw.jsresolves with a JavaScript content type when service workers are allowed. - Manifest install assets resolve: 192 and 512 icons, maskable variants, and four screenshots across narrow and wide form factors.
- Mobile web metadata is present: viewport, theme color, and
apple-mobile-web-app-capable=yes.
2. Consent and prompt ordering#
- Cookie consent is handled by the first-time visitor flow and compliance specs.
-
PwaBootstrapsuppresses service-worker update prompts until consent is resolved so the update overlay does not compete with the cookie banner. -
PwaInstallPromptis not consent-gated in V1; it is driven by the browser's install event and its own 14-day dismissal cooldown. - Consent-unresolved update suppression is browser-driven in
pwa-install-update-offline.spec.ts.
3. Install education and trigger surface#
-
/welcome/downloadis public and explains desktop Chrome/Edge, Android Chrome, and iPhone/iPad Safari install paths. - Safari copy states that Safari uses the native Share flow instead of
beforeinstallprompt. - Safari steps tell the user to choose Add to Home Screen and keep Open as Web App enabled when offered.
- The Web PWA card explains that Chrome/Edge can show a direct prompt and Safari on iPhone/iPad uses Share -> Add to Home Screen.
- The page previews four installed-workspace screenshots and exposes a live "Install from this browser" trigger.
4. Native install prompt#
-
beforeinstallpromptis captured,preventDefault()is called, and[data-pwa-prompt][data-pwa-state="available"]renders. - Clicking "Install from this browser" dispatches the shared install trigger event.
- Clicking the custom install control calls the deferred browser prompt.
- Accepted prompt outcome transitions to
[data-pwa-prompt][data-pwa-state="installed"]. - Dismissed prompt outcome or explicit "Not now" stores
oshun_pwa_install_dismissedand suppresses the prompt across reloads for the cooldown window. - Standalone runtime detection records the installed state and suppresses the available prompt.
5. Standalone cold launch#
- Manifest
start_urlis/?surface=pwa,displayisstandalone, anddisplay_overridepreferswindow-controls-overlaybefore standalone. -
detectStandalonePwa()accepts both(display-mode: standalone)and the iOSnavigator.standaloneflag. -
mockStandaloneLaunch()verifies signed-in cold-start restoration from/?surface=pwa. - A signed-in standalone relaunch restores the saved target, for example
/domains/nisaba?origin=home. - An unauthenticated standalone relaunch redirects to
/welcomewith the saved redirect preserved. - A cold start with an already-active service worker restores the route,
stays prompt-free, and emits
pwa_update_appliedwithapplyMode=silent-cold-start.
6. Offline read continuity#
- Authenticated warmup populates the offline shell cache for Home, Explore, Activity, and Library.
- Those shell routes remain navigable after
context.setOffline(true). -
/healthzfailing whilenavigator.onLine === truesurfaces the offline banner through the real connectivity probe. - A captive-portal-shaped
200 text/html/healthzresponse is rejected and keeps the offline banner visible. - A genuine BFF health sentinel clears the banner on the online/retry check.
- The real service worker serves a cached shell for an uncached deep
navigation while offline, and the hydrated client renders
<PwaOfflineFallback>. -
<PwaOfflineFallback>includes retry, Home/Explore/Activity/Library shortcuts, recent cached content when available, and an empty-state copy when no recent records exist. - Nisaba offline-pinned BFF responses are readable inside the 24-hour window and expire outside it.
7. Offline writes and reconnect#
- Library collection toggles can persist locally while the browser is offline.
- Saving a live Nisaba search result while offline writes
oshun.web.library.saved-items.v1and shows the "Saved locally / sync will retry" hint. - Tara reflection submit while offline queues a local reflection item and renders the queued sync state.
- A real service-worker
OSHUN_QUEUE_ACTIONArete check-in persists to IndexedDB, survives an offline reload, replays through Chromium Background Sync, writes the live BFF, and drains the queue. - Service-worker queue status emits
pwa_sync_queued; update prompts with queued writes render a queued-write note before refresh.
8. Service-worker update#
- A staged waiting worker renders
PwaUpdatePromptafter consent resolves. - Standard shell routes show the "Workspace update ready" copy and a "Later" deferral that keeps the route stable.
- A "Later" dismissal is session-scoped and re-surfaces on the next cold launch while the worker is still waiting.
- Reading routes use reading-safe copy and "Keep reading"; dismissal keeps the live Nisaba reading route stable.
- "Refresh now" posts
OSHUN_SKIP_WAITING, receivescontrollerchange, reloads, clears the prompt, and keeps the route stable. - Mid-passage scroll position is preserved after an explicit reading-route update refresh.
- Multiple open tabs apply the staged update once, preserve their own routes, and do not loop stale chunk errors.
- Real
sw.jsactivation purges stale OSHUN caches while preserving current and foreign caches. - An in-flight Arete check-in that already reached the BFF remains durable across the update reload.
- A lost skip-waiting response surfaces the manual "Reload now" escape.
9. Push notification leg#
- Profile notification settings can register and unregister this browser as
a
web-pushdevice token through the/v1/device-tokenswire shape. - Device registration failures report an error and never claim registered.
- No-VAPID deployments fail closed and hide the register button.
- The actual
public/sw.jspush runtime handles a Nyx payload, preserves theactionPath, and callsshowNotificationwith title, body, icon, badge, tag, vibration, and notification data. -
notificationclickcold-opens the target route when no window exists. -
notificationclickfocuses an existing app window and postsOSHUN_NOTIFICATION_TAPwhen a window is already open.
10. Sign-out and member-data cleanup#
-
clearPwaRelaunchTarget()removes the relaunch localStorage key and expires the relaunch cookie. -
requestServiceWorkerMemberDataClear()postsOSHUN_CLEAR_MEMBER_DATAto the controlling service worker. -
sw.jsclears member-owned BFF/media caches and queued offline mutations when it receivesOSHUN_CLEAR_MEMBER_DATA. - The next unauthenticated standalone launch routes through
/welcomeinstead of leaking a protected target.
Post-conditions#
- The installed-shell contract is valid: manifest, icons, screenshots, standalone start URL, service worker, and launch metadata all resolve.
- A browser-accepted install suppresses the custom prompt; a dismissal remains suppressed for the defined 14-day cooldown.
- Signed-in relaunch restores only a sanitized saved target; signed-out relaunch preserves that target through the welcome/auth gate.
- Warmed shell routes and eligible pinned data have an explicit offline path; unsupported deep data renders the offline fallback rather than a false live state.
- Waiting-worker updates are deferable and route-safe, with queued writes disclosed before refresh.
- Sign-out clears the relaunch pointer, member caches, and queued member writes from the controlling service worker.
Failure modes#
- Service-worker registration blocked — the runtime reports the failure and leaves the browser experience usable without claiming offline support.
- Install prompt dismissed — cooldown storage suppresses repeated prompting across reloads.
- Consent unresolved while an update waits — only the consent surface wins; the update prompt appears after accept or reject resolves consent.
- Captive portal reports online — HTML from
/healthzfails the health sentinel and the offline banner stays visible. - Skip-waiting acknowledgement is lost — a manual reload action appears instead of leaving the UI indefinitely applying.
- Multiple tabs receive one update — coordination prevents stale-chunk loops while each tab preserves its route.
- Offline mutation pending during update — the prompt discloses queued work, and an already-accepted BFF write remains durable across reload.
- Push registration unavailable — missing VAPID or a failed device-token write never paints a registered state.
- Sign-out followed by standalone cold launch — protected relaunch data is cleared before the public welcome path opens.
E2E coverage#
apps/oshun/web/e2e/pwa-smoke.spec.tscovers public first visit, manifest and asset validity, SW asset availability, mobile metadata, download/install education including Safari A2HS copy, native install-prompt trigger and accepted state, authenticated and unauthenticated standalone relaunches, and reading-route update apply/deferral.apps/oshun/web/e2e/pwa-install-update-offline.spec.tscovers install dismissal cooldown, standard update deferral/re-prompt/ready transition, consent-unresolved update suppression, offline recent-content fallback, and uncached deep-route fallback controls.apps/oshun/web/e2e/pwa-lifecycle-deepening.spec.tscovers refresh-now apply, multi-tab reload coordination, real cache purge, silent cold-start telemetry, in-flight writes, queued-write update prompts, offline banner/retry, and reduced-motion update spinner behavior.apps/oshun/web/e2e/pwa-failure-modes.spec.tscovers SW-registration-blocked rendering, update/offline overlay co-mount, captive-portal false-online rejection, stuck apply manual reload, and the controllerchange reload-loop guard.apps/oshun/web/e2e/offline-shell-routes.spec.ts,offline-write-deepening.spec.ts, andoffline-background-sync.spec.tscover shell route availability, real SW uncached-document fallback, BFF health probing, local offline edits, local sync hints, and real Background Sync replay.apps/oshun/web/e2e/push-registration.spec.tsandapps/oshun/web/e2e/nyx-service-worker-notification.spec.tscover browser push device registration and service-worker push/click routing.
Per-view files touched by this journey#
shell/03-pwa-behavior.mdshell/04-auth-session.mdshell/05-notifications.mdcustomer/00-public/welcome.mdcustomer/02-home-discovery/home.md
Cross-references#
- Feature spec:
V1/features.md - Architecture:
V1/ARCHITECTURE.md - Manifest:
apps/oshun/web/public/manifest.json - Service worker:
apps/oshun/web/public/sw.js
Open questions / known boundaries#
- OS-level installation and uninstall from the Android/iOS launcher cannot be completed by Playwright; the suite asserts the browser event boundary, manifest contract, installed prompt state, and standalone launch behavior.
- Safari Add to Home Screen cannot expose a headless browser install prompt; the automated contract is the manual Safari copy and standalone detection branch.
- Storage-quota eviction is covered at policy/code level where available, but there is no deterministic cross-browser quota-exhaustion E2E gate.
- Lighthouse PWA scoring is not the release gate in this journey; manifest, assets, service worker, offline, relaunch, and update behavior are covered by targeted checks with clearer failure attribution.