V1 Web PWA · Surface walkthrough

Shell: PWA behavior

A per-surface walkthrough of the V1 Web PWA: layout, states, interactions, data, and cross-references.

unspecified
15sections8 minread

On this page

Source: apps/oshun/web/public/manifest.json, apps/oshun/web/public/sw.js, apps/oshun/web/src/components/PwaBootstrap.tsx, apps/oshun/web/src/components/PwaInstallPrompt.tsx, apps/oshun/web/src/components/PwaInstallTriggerButton.tsx, apps/oshun/web/src/components/PwaUpdatePrompt.tsx, apps/oshun/web/src/components/PwaOfflineFallback.tsx, apps/oshun/web/src/components/OfflineBanner.tsx, apps/oshun/web/src/lib/pwa-runtime.ts, apps/oshun/web/src/lib/pwa-service-worker.ts, apps/oshun/web/src/lib/offline-shell-routes.ts, and apps/oshun/web/src/lib/pwa-recent-content.ts.

The PWA story - install, offline, update, sync, relaunch, and push routing - is the spine of "PWA" in the product name. Walk this whenever the manifest, sw.js, or PwaBootstrap changes.

Manifest#

/manifest.json

  • name and short_name are OSHUN.
  • description names the one-shell/six-domain product promise.
  • id is /, start_url is /?surface=pwa, and scope is /.
  • display is standalone; display_override is ["window-controls-overlay","standalone"].
  • theme_color and background_color are #f1ebdd.
  • Categories include health, education, lifestyle, and productivity.
  • Icons include 192 and 512 pixel any and maskable PNGs.
  • Screenshots include two narrow 390x844 and two wide 1440x960 assets.
  • Shortcuts cover Tara, Veritas, Nyx, Arete, Nisaba, and Metis.
  • Install assets are resolved by pwa-smoke.spec.ts.

Install prompt#

PwaInstallPrompt.tsx + PwaInstallTriggerButton.tsx

  • Eligibility is event-driven: the custom prompt appears when the browser fires beforeinstallprompt.
  • The component calls preventDefault(), stores the deferred browser event, and renders [data-pwa-prompt][data-pwa-state="available"].
  • The manual /welcome/download trigger dispatches oshun-trigger-pwa-install, which calls the saved prompt event.
  • Accepted prompt outcome records install state and renders the installed guidance state.
  • Explicit dismiss or dismissed browser outcome writes oshun_pwa_install_dismissed; reloads suppress the prompt during the 14-day cooldown.
  • Standalone runtime detection records install state from (display-mode: standalone) and suppresses the available prompt.
  • iPhone/iPad Safari is documented through the manual Share -> Add to Home Screen copy on /welcome/download; Safari does not expose beforeinstallprompt.
  • There is no V1 app-side "N qualifying interactions" threshold; the browser decides when beforeinstallprompt fires.

Service worker lifecycle#

sw.js + PwaBootstrap.tsx + lib/pwa-service-worker.ts

  • registerServiceWorker() registers /sw.js at scope / on mount when the browser exposes navigator.serviceWorker.
  • Registration failures are caught, logged, and reset the one-shot guard so a later mount can retry.
  • With service workers blocked by the browser, the authenticated shell still renders instead of white-screening.
  • A waiting worker surfaces PwaUpdatePrompt only after cookie consent is resolved.
  • The update prompt distinguishes standard routes from reading routes and uses route-specific copy.
  • "Refresh now" posts OSHUN_SKIP_WAITING; controllerchange emits pwa_update_applied and reloads.
  • "Later" / "Keep reading" dismisses only the current prompt token; a cold launch with the same waiting worker re-prompts.
  • OSHUN_SW_UPDATE messages move the prompt to the ready state.
  • A lost skip-waiting response surfaces the manual reload escape before the silent fallback reload.
  • Multi-tab update application reloads each open route once and preserves route identity.
  • activate deletes stale oshun-* caches while preserving current version and foreign caches.

Cache strategies#

sw.js defines static, runtime, BFF, and media caches with CACHE_VERSION = 'v7'.

  • Static assets include the app shell, public install routes, manifest, icons, and screenshots.
  • Runtime navigation fallback uses cached shell HTML for supported offline shell paths.
  • BFF responses use stale-while-revalidate with the general BFF max-age policy.
  • Nisaba offline reads use NISABA_OFFLINE_BFF_MAX_AGE_MS (24 hours) for offline-pinned scholarly content.
  • Media uses MEDIA_CACHE with MAX_MEDIA_ENTRIES = 200; runtime cache eviction uses MAX_RUNTIME_ENTRIES = 100.
  • Auth/session paths are denied from cache and fail closed while offline.
  • Cache misses on offline deep navigations render the static service-worker fallback and hydrate into PwaOfflineFallback when the client is available.

Offline behavior#

OfflineBanner.tsx, PwaOfflineFallback.tsx, and offline-shell-routes.ts

  • navigator.onLine === false or a failed genuine /healthz probe surfaces <OfflineBanner>.
  • Captive-portal 200 text/html health responses are rejected and keep the banner visible.
  • A genuine BFF health payload clears the banner on retry/online.
  • Warmed Home, Explore, Activity, and Library shells stay navigable offline.
  • Warmable prefixes include /library, /domains, /d, /app, and /workspace.
  • Cached BFF and Nisaba offline records render when within their freshness windows.
  • Uncached reads render PwaOfflineFallback with retry, cached shell shortcuts, and recent cached records.
  • Recent content is persisted by persistPwaRecentContent for authenticated route visits.
  • Offline local edits and queueable writes show explicit local/sync-pending states; they do not silently fail.

Background sync#

  • queueBackgroundAction() posts OSHUN_QUEUE_ACTION to the controlling service worker.
  • sw.js persists queued actions in IndexedDB oshun-sync-queue / pending-actions.
  • Chromium Background Sync dispatch replays queued actions in order.
  • Successful replay posts OSHUN_SYNC_COMPLETE, updates queue count, writes the live BFF, and drains IndexedDB.
  • Queue status posts OSHUN_SYNC_QUEUE_STATUS, which drives pwa_sync_queued telemetry.
  • A pending queue count renders the update prompt's queued-write note so the user knows refresh will preserve held writes.

Relaunch target#

lib/pwa-runtime.ts

  • OSHUN_PWA_RELAUNCH_TARGET_KEY

  • OSHUN_PWA_RELAUNCH_TARGET_COOKIE

  • persistPwaRelaunchTarget

  • readPwaRelaunchTarget

  • resolveStandaloneLaunchTarget

  • Authenticated route changes persist a sanitized relaunch target in localStorage and the oshun-pwa-relaunch cookie.

  • Signed-in standalone launches from /?surface=pwa restore the saved target.

  • Anonymous standalone launches route to /welcome with the saved redirect preserved.

  • Expired or unauthenticated protected relaunches remain under the auth redirect contract.

  • Sign-out/session-clear removes the relaunch target and asks the service worker to clear member-owned caches and queued writes.

Offline shell warmup#

lib/offline-shell-routes.ts + lib/pwa-recent-content.ts

  • buildOfflineShellWarmRoutes() always includes Home, Explore, Activity, and Library.
  • Current and recent warmable routes are normalized with sanitizeRedirectPath.
  • ?surface=pwa is stripped before offline route normalization.
  • warmOfflineShellRoutes() waits for service-worker readiness/controller when possible, then fetches normalized HTML routes with x-oshun-surface.
  • Warmup failures are best-effort and collapse to zero warmed routes instead of breaking the shell.

Standalone PWA detection#

  • (display-mode: standalone) is detected.
  • iOS navigator.standalone === true is detected.
  • getBrowserSurface() returns pwa only for standalone runtime, otherwise web.
  • document.documentElement.dataset.oshunSurface tracks the current browser surface.
  • Standalone launch restoration is session-safe and route-safe.

Push notifications#

sw.js, profile notification settings, and device-token BFF routes.

  • Profile notification settings register this browser through the Push API boundary and POST a stringified web-push subscription as a web-push token.
  • Unregister DELETE carries the same subscription token.
  • Registration failure leaves the row in error state with retry available.
  • Missing VAPID config reports unavailable and hides the register action.
  • sw.js push calls showNotification() with title, body, icon, badge, tag, vibration, and action data.
  • notificationclick closes the notification, focuses an existing OSHUN window and posts OSHUN_NOTIFICATION_TAP, or cold-opens the target URL.

States#

  • First visit in browser: public welcome shell, manifest, mobile metadata, and service-worker asset resolve.
  • Browser install prompt available: custom install banner renders after beforeinstallprompt.
  • Installed home-screen launch: standalone detection and relaunch target are honored.
  • Update available in foreground: PwaUpdatePrompt is visible after consent.
  • Update deferred: route remains stable and prompt can re-surface next launch.
  • Update applied: prompt clears, route/scroll are preserved where applicable, and stale caches are purged.
  • Offline cached page: cached shell renders and banner is visible.
  • Offline uncached page: service-worker fallback and hydrated PwaOfflineFallback render.
  • Offline write attempt: action is queued or kept locally with user-visible pending copy.
  • Push tap: existing window focus or cold-open route is exercised at the service-worker runtime layer.

Telemetry to verify#

  • pwa_update_applied for refresh-now and silent cold-start paths.
  • pwa_sync_queued for service-worker queue status.
  • Install state is recorded in oshun.pwa.install-state; install analytics names are not currently the E2E assertion boundary.
  • Offline banner/fallback assertions use visible DOM and service-worker marker contracts.
  • Relaunch target assertions use URL restoration and auth redirect behavior.

Coverage#

Known boundaries#

  • OS-level install/uninstall and iOS home-screen confirmation are outside Playwright; tests assert the browser/PWA boundary and manual copy.
  • Cross-browser storage-quota exhaustion is not deterministic enough for an E2E gate; cache eviction policies are covered through targeted runtime and unit/policy checks.
  • Lighthouse PWA score thresholds are not currently a release gate; the manifest, install, SW, offline, relaunch, update, sync, and push contracts above are the automated signoff.

Cross-references#