V1 Web PWA · Journey

Journey: Install as PWA

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

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

On this page
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 beforeinstallprompt event; Safari on iPhone/iPad uses Share -> Add to Home Screen and never exposes beforeinstallprompt.
  • 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/download trigger dispatches the same saved event.

Steps#

1. Anonymous first browser visit#

  • Browser navigation to / redirects an unauthenticated visitor to /welcome?redirect=%2F.
  • /welcome is public, hydrated, titled for OSHUN, and exposes the auth entry plus primary public CTA.
  • /manifest.json is linked once from the document head.
  • /sw.js resolves 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.
  • Cookie consent is handled by the first-time visitor flow and compliance specs.
  • PwaBootstrap suppresses service-worker update prompts until consent is resolved so the update overlay does not compete with the cookie banner.
  • PwaInstallPrompt is 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/download is 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#

  • beforeinstallprompt is 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_dismissed and 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_url is /?surface=pwa, display is standalone, and display_override prefers window-controls-overlay before standalone.
  • detectStandalonePwa() accepts both (display-mode: standalone) and the iOS navigator.standalone flag.
  • 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 /welcome with the saved redirect preserved.
  • A cold start with an already-active service worker restores the route, stays prompt-free, and emits pwa_update_applied with applyMode=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).
  • /healthz failing while navigator.onLine === true surfaces the offline banner through the real connectivity probe.
  • A captive-portal-shaped 200 text/html /healthz response 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.v1 and 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_ACTION Arete 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 PwaUpdatePrompt after 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, receives controllerchange, 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.js activation 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-push device token through the /v1/device-tokens wire 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.js push runtime handles a Nyx payload, preserves the actionPath, and calls showNotification with title, body, icon, badge, tag, vibration, and notification data.
  • notificationclick cold-opens the target route when no window exists.
  • notificationclick focuses an existing app window and posts OSHUN_NOTIFICATION_TAP when a window is already open.

10. Sign-out and member-data cleanup#

  • clearPwaRelaunchTarget() removes the relaunch localStorage key and expires the relaunch cookie.
  • requestServiceWorkerMemberDataClear() posts OSHUN_CLEAR_MEMBER_DATA to the controlling service worker.
  • sw.js clears member-owned BFF/media caches and queued offline mutations when it receives OSHUN_CLEAR_MEMBER_DATA.
  • The next unauthenticated standalone launch routes through /welcome instead 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 /healthz fails 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#

Per-view files touched by this journey#

Cross-references#

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.