Skip to content

[6/10] Make Catalyse installable as a PWA - #358

Merged
hturnbull93 merged 2 commits into
mainfrom
pwa
Oct 24, 2026
Merged

hturnbull93 merged 2 commits into
mainfrom
pwa

Conversation

@hturnbull93

@hturnbull93 hturnbull93 commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Screenshots: https://fedora.taila91c66.ts.net:8444/docs/20261021-134259-pr-358-installable-app-and-offline-page.html

Makes Catalyse installable on a phone's home screen, with an offline page when there is no connection, and offers a refresh when a newer version has been deployed. No data or API changes.

Manifest and icons

  • app/manifest.ts (served at /manifest.webmanifest, linked automatically by Next): name, short_name "Catalyse", display: standalone, start_url: /dashboard, scope: /, 192/512 icons and a 512 maskable icon.
  • app/apple-icon.png (180px) gives iOS its apple-touch-icon; appleWebApp metadata sets the home-screen title.
  • The layout's viewport.themeColor sets a light (#ffffff) and dark (#1f2937) theme-color, matching the header's surface colour.
  • Icons are rendered from the existing app/icon.svg with Playwright's Chromium: the round logo for the 192/512 "any" icons, and a full-bleed orange square with white pause bars (inside the maskable safe zone) for the maskable and Apple icons.

Service worker

  • public/sw.js intercepts navigations only, network-first. It falls back to a cached /offline.html only when the network request fails, so it never serves stale app code after a deploy. API calls, JS/CSS chunks and signed-in pages are never intercepted or cached; the only cached entry is the offline page. Old caches are deleted on activate.
  • components/ServiceWorker.tsx registers it with updateViaCache: 'none', in production builds only. /sw.js is also served Cache-Control: no-cache.
  • public/offline.html is a self-contained page (inline styles and logo) that follows the app's light/dark choice and has a "Try again" link that reloads the page it replaced.

New version prompt

  • next.config.ts sets deploymentId to RAILWAY_GIT_COMMIT_SHA, which the Dockerfile now passes into the build. Next writes it on <html> as data-dpl-id, adds ?dpl= to asset URLs, and reloads (rather than client-navigates) a page that navigates into a newer deploy.
  • components/UpdatePrompt.tsx checks the existing version.get when the app comes back into view and every ten minutes. When the server's commit differs from the page's, it shows "A new version of Catalyse is available." with Refresh (reloads) and Later (hides it until a newer deploy still). It never reloads on its own, so nothing half-typed is lost.
  • Without a deployment id (a local build, dev reports 'dev'), the page takes the first version the server reports as its own.
  • Works the same in the installed app and a browser tab: the service worker never caches app code, so a reload always gets the new version.
  • Later, a WebSocket can announce a deploy instead of the ten-minute check.

Sign-in

  • Auth is a token in localStorage per origin. A standalone app on the same origin shares that storage on Android. The e2e test signs in, goes offline, comes back and reaches the volunteer-only Projects page still signed in.

Tests

  • Unit components/UpdatePrompt.test.tsx: hidden when versions match; shown when the page is older; first-seen fallback without a deployment id; Later holds until a newer deploy. The default reload can only be clicked, not observed: jsdom's location.reload is a silent no-op and can't be spied, so the asserting test passes its own reload.
  • Unit: manifest fields and that every icon file exists (app/misc.test.tsx); registration in production, none outside it, none without navigator.serviceWorker, and a warning when registration fails (components/ServiceWorker.test.tsx).
  • E2E 52-installable-app.spec.ts: the home page links the manifest, apple-touch-icon and both theme colours, and every manifest icon is served as PNG. With the worker in control, going offline shows the offline page (snapped), reconnecting and pressing Try again brings back Projects, and Cache Storage then holds only /offline.html. Passed 8 runs in a row on its own.
  • The worker is live across the whole e2e suite, since it is a production build. Every test gets a fresh browser context, so nothing carries over between tests, and the full suite passes.

Decisions

  • start_url is /dashboard: a signed-in volunteer lands on their home, and a signed-out one is sent to /login as usual.
  • A manifest has a single theme_color, so it uses the light value. Dark mode comes from the <meta name="theme-color" media=…> pair. That pair follows the OS setting, not the in-app theme toggle.
  • The service worker is a hand-written file in public/, not Serwist or next-pwa: it is about 30 lines and adds no dependency. CACHE must be renamed when offline.html changes so installed copies pick up the new page (a comment in sw.js says this).
  • No custom install button: the browser's own prompt (Android) and Share → Add to Home Screen (iOS), as the Next.js PWA guide recommends.
  • The icon-rendering script is not committed. It was a one-off render of app/icon.svg, and public/icons/ holds its output.

Manual checks on real phones (not possible here)

  • Android Chrome: the install prompt or menu "Install app" appears; the installed app opens standalone on /dashboard with the right icon (maskable, not letterboxed) and status-bar colour in light and dark.
  • Android: sign in in the browser, then launch from the home screen and check you're still signed in. Sign in inside the installed app and check it holds after closing and reopening.
  • iOS Safari: Share → Add to Home Screen shows the orange icon and the title "Catalyse"; it opens standalone. iOS keeps a home-screen app's storage separate from Safari's, so expect to sign in once inside the installed app, then check it stays signed in after closing and reopening.
  • Airplane mode, then open the app or navigate: the offline page shows; reconnect and press Try again.
  • After a deploy, reopen the installed app: the new version prompt appears within a few seconds of it coming into view; Refresh loads the new version.
  • Check Railway exposes RAILWAY_GIT_COMMIT_SHA at build time (the page's <html> should carry data-dpl-id). Without it the prompt falls back to the first version seen, which still works but misses a deploy that lands between the page loading and its first check.

Closes #348

🤖 Generated with Claude Code

@hturnbull93 hturnbull93 changed the title Make Catalyse installable as a PWA [7/12] Make Catalyse installable as a PWA Oct 21, 2026
@hturnbull93 hturnbull93 changed the title [7/12] Make Catalyse installable as a PWA [6/10] Make Catalyse installable as a PWA Oct 22, 2026
@hturnbull93
hturnbull93 force-pushed the threaded-replies branch 2 times, most recently from 24beed6 to 337bd4b Compare October 23, 2026 09:16
@hturnbull93
hturnbull93 force-pushed the pwa branch 4 times, most recently from 15c267d to 088eefc Compare October 23, 2026 23:10
A web app manifest with home-screen icons rendered from app/icon.svg (the
round logo at 192px and 512px, a full-bleed square for the maskable and
Apple icons), and theme colours for light and dark.

A service worker, registered in production builds only, sends navigations
network-first and falls back to a cached offline.html only when the
network fails; nothing else is intercepted or cached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hturnbull93
hturnbull93 changed the base branch from threaded-replies to main October 24, 2026 13:34
@hturnbull93 hturnbull93 reopened this Oct 24, 2026
@railway-app

railway-app Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the catalyse-pr-358 environment in PauseAI Catalyse

Service Status Web Updated
web ❌ Build Failed (View Logs) Web Oct 24, 2026 at 1:59 pm UTC

UpdatePrompt checks version.get when the app comes back into view and every
ten minutes, and shows "A new version of Catalyse is available" with Refresh
and Later when the server's commit differs from the page's. The page knows
its own deploy from Next's deploymentId, set to RAILWAY_GIT_COMMIT_SHA (now
passed into the Docker build); without one, as in a local build, it takes the
first version the server reports. Later hides the prompt until a newer deploy
still. deploymentId also makes Next reload, rather than client-navigate, a
page that navigates into a newer deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hturnbull93
hturnbull93 merged commit cdf5443 into main Oct 24, 2026
3 of 4 checks passed

This branch had an error being deployed

1 failed deployment
PauseAI Catalyse / catalyse-pr-358 — 9bd3abff Deployed Oct 24, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make Catalyse installable as a PWA

1 participant