Repository navigation
feat(web): make the web app an installable PWA - #193
Conversation
- Add a web app manifest with standalone display, theme colours and 192, 512 and maskable icons generated from the existing app icons, linked from a root +html.tsx - Add a dependency-free service worker: pages load network-first with an offline fallback, and content-hashed Expo bundles and assets are cached once - Register it only in production exports so development never caches Metro bundles - Serve sw.js and the manifest with no-cache and hashed bundles as immutable on Vercel - Document installation and caching in docs/web.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
| const response = await fetch(request); | ||
| if (response.ok) await cache.put(request, response.clone()); | ||
| return response; |
There was a problem hiding this comment.
Full storage breaks online loading
If browser storage is full, cache.put can fail after a successful download. networkFirst then returns an old cached page, while cacheFirst rejects the bundle request instead of returning the downloaded code. Users can get an old build or an app that fails to load even while online. Catch cache-write failures separately in both functions and still return the downloaded response. This loading failure must be fixed before merging.
Artifacts
- This executed harness serves the unchanged worker and runs isolated baseline, healthy-cache, and injected-failure cases, making the reproduction inspectable.
- This executed runner records each command, working directory, exit code, and observed output, making the verification reproducible.
- The command captured the commit, repository status, worker hash, full worker source, and its absence at base, identifying exactly what was tested.
- Chromium loaded the same HTTP fixtures without service-worker registration and received fresh HTML plus an executable bundle, establishing the no-worker baseline.
- Chromium executed the unchanged worker with normal Cache.put behavior and received fresh HTML plus an executable bundle, confirming the healthy control.
- Chromium executed the unchanged worker with injected Cache.put rejections and observed stale HTML plus failed bundle loading despite HTTP 200 fetches, supporting candidate 0.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: public/sw.js
Line: 23-25
Comment:
**Full storage breaks online loading**
If browser storage is full, `cache.put` can fail after a successful download. `networkFirst` then returns an old cached page, while `cacheFirst` rejects the bundle request instead of returning the downloaded code. Users can get an old build or an app that fails to load even while online. Catch cache-write failures separately in both functions and still return the downloaded response. This loading failure must be fixed before merging.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const SHELL = ['/', '/manifest.json', '/icons/icon-192.png', '/icons/icon-512.png']; | ||
| const IMMUTABLE = ['/_expo/static/', '/assets/']; | ||
|
|
||
| self.addEventListener('install', (event) => { | ||
| event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(SHELL)).then(() => self.skipWaiting())); |
There was a problem hiding this comment.
Installation misses required app code
Installation saves the root HTML but none of its required JavaScript bundles. registerServiceWorker runs after load, when the first visit's bundles have already loaded without the worker. Until another controlled visit, offline startup depends on the browser's separate HTTP cache. If that cache is gone, the installed app cannot open offline. This is a non-blocking offline reliability gap: save the required bundles during installation as well.
Artifacts
- This executed script serves the prepared build, records both launches and asserts bundle failures and preserved CacheStorage, reproducing the missing-code installation defect.
- This executed runner captures the reproduction command, working directory, exit code and actual output into the execution log, making the run reproducible.
- The captured successful command output includes browser network events, DOM state and CacheStorage entries for both launches, supporting the reported defect.
- Chromium records the fresh production visit and holds on the rendered onboarding UI for eight seconds, establishing the working baseline.
Screenshot of the first online visit
- The screenshot captures the first-visit page after live DOM assertions detected onboarding text and two buttons, documenting the working baseline.
- Chromium records the offline launch and holds its nonfunctional result for ten seconds after the required bundle fails, demonstrating the installation gap.
Screenshot of the nonfunctional offline launch
- The screenshot captures the offline page whose live DOM contains only “Switchify Remote” and no buttons, documenting the failed app render.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: public/sw.js
Line: 5-9
Comment:
**Installation misses required app code**
Installation saves the root HTML but none of its required JavaScript bundles. `registerServiceWorker` runs after `load`, when the first visit's bundles have already loaded without the worker. Until another controlled visit, offline startup depends on the browser's separate HTTP cache. If that cache is gone, the installed app cannot open offline. This is a non-blocking offline reliability gap: save the required bundles during installation as well.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Closes #192
Summary
public/manifest.json): name, start URL and scope/, standalone display,#050505theme and background, and 192, 512 and maskable icons inpublic/icons/. The icons come from the existing app icons; the maskable one uses the Android adaptive foreground, so it stays inside the safe zone.src/app/+html.tsx): Expo's default document plus the manifest link, theme colour, description and Apple touch icon.public/sw.js, no new dependency): pages are fetched network-first with a cached fallback, so deployments are never stuck behind the cache. Content-hashed/_expo/static/bundles and/assets/are cached once. It is registered only in production exports, sonpm run webnever caches Metro bundles.vercel.json):no-cacheforsw.jsand the manifest (served asapplication/manifest+json), and immutable caching for hashed bundles.docs/web.md.Native builds are unchanged.
Validation
npm run validate: lint, typecheck, 673 Jest tests and 21/21 Expo Doctor checks pass.npx expo export -p webserved locally, in headless Chrome:Page.getAppManifestreports no manifest errors.Page.getInstallabilityErrorsreports none./and opening/settingsstill render the app.expo start --webpage links the manifest but does not register the service worker.🤖 Generated with Claude Code