cv-web PWA Cache & Update Strategy

Last reviewed: 2026-05-21 (CV-195)

cv-web ships as a Progressive Web App on S3 + CloudFront under simplestruct.com/cv-web/. This document captures how the service worker caches assets, when caches invalidate, what the user sees during an update, and how the app behaves offline.

Build configuration

webpage/Simplestruct/cv-web/vite.config.ts configures vite-plugin-pwa:

Setting

Value

Effect

registerType

autoUpdate

Plugin injects the SW registration into index.html. No manual call site.

workbox.skipWaiting

true

A new SW activates immediately instead of waiting for all tabs to close.

workbox.clientsClaim

true

The new SW takes control of open pages on activate.

workbox.globPatterns

**/*.{js,css,html,ico,png,svg,wasm}

App shell is precached at install time.

workbox.runtimeCaching (wasm)

CacheFirst, 30-day expiry

manifold-3d wasm chunks cached across runs.

What gets cached

  • Precache: every static asset emitted by Vite — JS/CSS chunks, index.html, icons, sample images. Listed in dist/sw.js as __precacheManifest.

  • Runtime cache: *.wasm (manifold-3d) via CacheFirst.

  • IndexedDB (Dexie): project state, panel data, and snapshots. Lives in the browser, not the SW cache. See src/store/db.ts.

  • localStorage: sw_v4 one-shot flag used by index.html to clear legacy service workers (see “Stale SW recovery” below).

What does NOT get cached

  • OpenStreetMap Nominatim geocoding (ProjectView.tsx) — lazy, fires only when the user enters an address. Failure is silent.

  • Open-Meteo weather forecast (ProjectView.tsx) — same as above.

  • Sentry telemetry — opportunistic; failure is silent.

These external APIs are not on the offline path. The app boots and operates without any of them.

Cache invalidation — what happens after a deploy

The Deploy SimpleStruct workflow uploads new chunks to S3 and (since the Vite build emits hashed filenames) creates a new sw.js content. The sequence on the user’s next visit:

  1. Browser fetches index.html (CloudFront edge-cached briefly, typically ≤ 60 s).

  2. The injected SW registration loads the new sw.js.

  3. Workbox detects a precache manifest change, downloads the new chunks, and (because skipWaiting + clientsClaim are set) activates the new SW.

  4. The old tabs are now controlled by the new SW but still holding the old JS chunks in memory. Until reload, the user sees the previous version.

  5. The useRegisterSW hook in PwaUpdateBanner fires needRefresh. The user sees a banner: “A new version of ConstructiVision Web is available. Reload.” Click reloads with the fresh chunks.

If the banner is missed: a hard reload (Ctrl+Shift+R) or closing and reopening the tab also picks up the new build.

Offline behavior

After at least one online visit the entire app shell sits in the SW cache. Subsequent loads — including with the network disabled — boot the same way:

  • index.html is served from the SW precache.

  • All JS/CSS chunks come from the precache.

  • Dexie restores the project state from IndexedDB.

  • Last-loaded panel renders identically to an online session.

The Welcome screen “Recent Projects” list and the saved snapshots both work offline because both read from Dexie.

Features that require network are gracefully degraded:

  • Geocoding/weather lookups in ProjectView fail silently.

  • Sentry events queue locally and replay when connectivity returns.

Install flow

PwaInstallButton listens for the Chromium beforeinstallprompt event. When the browser decides the app is installable (HTTPS, valid manifest, SW active, sufficient engagement) the button appears top-right. Clicking it calls the captured prompt() and clears the state on userChoice. The button hides when the app is already running in display-mode: standalone (or navigator.standalone on iOS).

Stale SW recovery

If a previously-broken SW is stuck in the user’s browser, index.html includes a one-shot recovery: on load, it unregisters any existing SW registrations the first time a new sw_v4 localStorage key is observed. Bump the key (sw_v5, etc.) to force a one-time SW reset across the user base.

Verification checklist (manual)

Run after any change that touches caching, the SW config, or boot path:

  1. Build + serve: npm run build then npm run preview over HTTPS.

  2. First load: DevTools → Application → Service Workers → Status = activated. Application → Cache Storage → precache contains index.html, JS chunks, icons.

  3. Install: Click “Install app” → confirm prompt → confirm icon on home screen / app launcher.

  4. Offline reload: DevTools → Network → “Offline” → reload. App renders. Last-loaded panel visible.

  5. Update flow: rebuild with a one-line change. Reload preview. PwaUpdateBanner should appear within seconds. Click “Reload” → new chunks active.

  6. Standalone test: open the installed app from the home screen while offline. Same result as step 4.

References

  • vite-plugin-pwa: https://vite-pwa-org.netlify.app/

  • Workbox precache: https://developer.chrome.com/docs/workbox/modules/workbox-precaching/

  • beforeinstallprompt event: https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeinstallprompt_event

  • Source: webpage/Simplestruct/cv-web/vite.config.ts, src/components/PwaUpdateBanner.tsx, src/components/PwaInstallButton.tsx, src/utils/pwa.ts.