feat(client): restore PWA install + offline shell via Expo (#208) #223
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!223
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "208-pwa-install-offline"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Restores PWA install + offline shell for the Expo Web bundle after #185 deleted the previous
@serwist/nextsetup.apps/client/public/{manifest.webmanifest,sw.js,offline.html,icon.svg,icon-192.png,icon-512.png}. Expo's static export copiesapps/client/public/intoapps/client/dist/, so all the new files reach the browser through the API container's[...spa]catch-all with the rightContent-Typefromapps/api/lib/spa/serve.ts(the MIME table already covered.webmanifest,.js,.html,.png,.svg— no touch needed there).apps/client/app/+html.tsx— Expo Router's document shell. Injects<link rel="manifest">,<meta name="theme-color" content="#2f6bff">,<link rel="apple-touch-icon">, and an inlinenavigator.serviceWorker.register('/sw.js')so registration fires before the entry bundle parses./manifest.webmanifest,/sw.js, and/offline.htmlto the public-route allowlist; flips the proxy test assertions that previously asserted these paths were NOT public.Service-worker approach: hand-rolled, ~150 LOC
Picked over the alternatives:
@serwist/next; the standalone build path adds a separate bundler step that the Expo client otherwise doesn't need. Not worth the complexity for one SW.Strategy:
/_expo/static/and/assets/(content-hashed, immutable)./api/*GETs, falling back to cache; non-GET/api/*bypasses the SW so writes never silently succeed against stale data./offline.html, then a synthetic 503.CACHE_VERSIONbump invalidates prior caches on activate.Files touched
apps/client/app/+html.tsx(new)apps/client/public/manifest.webmanifest(new)apps/client/public/sw.js(new)apps/client/public/offline.html(new)apps/client/public/{icon.svg,icon-192.png,icon-512.png}(ported verbatim fromapps/api/public/)apps/api/lib/auth/public-routes.tsapps/api/tests/proxy.test.tsAPI-side icons under
apps/api/public/left in place per the ticket's "out of scope" notes.Test plan
pnpm install --frozen-lockfilepnpm -F @carol/api typecheck/lint/test/openapi:check/openapi:coverage(556 passed / 107 skipped)pnpm -F @carol/api-client typecheck/lint/test/checkpnpm -F @carol/client typecheck/lint/test/export:web(manifest, sw.js, offline.html, icons all land inapps/client/dist/)podman build -t carol:208 .— clean buildcurl -Iagainst the running container returns 200 + correctContent-Typefor:/manifest.webmanifest→application/manifest+json/sw.js→application/javascript; charset=utf-8/offline.html→text/html; charset=utf-8/icon-192.png→image/pngcurl /confirms the<head>carries the manifest link, theme-color, apple-touch-icon, and the inline SW registration./offline.htmlis served when the network is killed. CI also can't test this; it has to be a human.Follow-ups worth filing
purpose: "any maskable". The artwork is a centered C on a solid blue rounded square — it survives Chrome's 40% safe-zone trim cleanly, but a designer should sign off rather than relying on visual inspection. Worth a small ticket to either confirm or ship dedicated maskable variants.CACHE_VERSIONbumps clear prior caches on activate, but there's no "a new version is available — reload?" prompt. For a self-hosted app that's tolerable, but if Carol grows a deploy cadence it'd be worth aworkbox-window-style update toast.name/short_name/descriptionstrings are hardcoded English. Ticket #208's brief calls the offline shell exempt from i18n (it's static HTML and Carol's voice rules cover it), but the manifest could reasonably be templated per locale at build time. Probably not worth doing until Carol ships in more than two languages.🤖 Generated with Claude Code
📊 Test coverage
Patch coverage: no testable lines changed.
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Soft thresholds per ADR-0019. Coverage is informational and does not block merge.
Trivy (container image)
Threshold:
high· Total findings: 121 · At/above threshold: 16.27.0, 7.28.0, 8.5.0