Conversation
Introduce a shared PWA version helper and use versioned URLs for the manifest, service worker registration path, and apple-touch-icon links so iOS and Safari are forced to see updated metadata and icons. Update the service worker to precache during install, delete old named caches during activate, claim clients immediately, and read cached responses only from the current cache instead of falling back to stale entries from previous cache names. Mark manifest, service worker, and icon endpoints as non-cacheable in the service worker so browser metadata refreshes are not blocked by app-level caching. Register the service worker with updateViaCache set to none and trigger an explicit registration update on load to reduce stale worker reuse. Replace the empty public apple-touch-icon files with a real PNG so iOS home screen icons no longer resolve to invalid placeholder files.
There was a problem hiding this comment.
Pull request overview
This PR aims to reduce stale PWA metadata/assets on iOS/Safari by introducing a shared PWA version helper, versioning key PWA URLs (manifest, service worker registration, icons), and tightening service worker caching/refresh behavior.
Changes:
- Add
pwa_version/versioned_pathhelper and apply it to manifest, service worker registration, and apple-touch-icon links. - Update the service worker to precache on install, clean up caches on activate, and avoid reading from old cache names.
- Replace placeholder apple-touch-icon files with real PNGs.
Reviewed changes
Copilot reviewed 5 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| public/apple-touch-icon.png | Adds a real apple-touch icon PNG asset. |
| public/apple-touch-icon-precomposed.png | Adds a real precomposed apple-touch icon PNG asset. |
| app/views/pwa/service-worker.js.erb | Updates cache versioning, install/activate lifecycle, and cache lookup behavior. |
| app/views/pwa/manifest.json.erb | Versions manifest icon URLs via versioned_path. |
| app/views/layouts/application.html.erb | Adds versioned manifest/SW path metadata and apple-touch-icon link tags. |
| app/javascript/application.js | Registers SW with updateViaCache: 'none' and forces registration.update(). |
| app/helpers/application_helper.rb | Introduces PWA version constant + versioned_path helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "/icon.png", | ||
| "/apple-touch-icon", | ||
| "/favicon.ico" |
There was a problem hiding this comment.
/favicon.ico is marked non-cacheable, but the layout uses an event-scoped favicon path (e.g. /<event>/favicon.ico). Those requests won’t match this entry and may still be cached by the SW. Consider matching pathname.endsWith('/favicon.ico') or adding the scoped path(s) so favicon refresh isn’t blocked.
| const keys = await caches.keys(); | ||
| await Promise.all(keys.filter((key) => key !== cacheName).map((key) => caches.delete(key))); | ||
| await precache(); |
There was a problem hiding this comment.
During activate, this deletes every Cache Storage entry for the origin except the current cacheName. That’s fine if this SW is the only Cache Storage user, but it will also wipe any unrelated caches (present or future). Consider scoping deletions to caches created by this SW (e.g., a name prefix) instead of deleting all non-matching keys.
| <link rel="apple-touch-icon" sizes="180x180" href="<%= versioned_path('/icon.png') %>"> | ||
| <link rel="apple-touch-icon-precomposed" href="<%= versioned_path('/icon.png') %>"> |
There was a problem hiding this comment.
The layout’s apple-touch-icon / apple-touch-icon-precomposed links point to the versioned /icon.png, but this PR also adds public/apple-touch-icon.png and public/apple-touch-icon-precomposed.png. Either update these links to reference the new apple-touch-icon files (and version them) or drop the unused files to avoid confusion and ensure iOS is actually requesting the intended endpoints.
| <link rel="apple-touch-icon" sizes="180x180" href="<%= versioned_path('/icon.png') %>"> | |
| <link rel="apple-touch-icon-precomposed" href="<%= versioned_path('/icon.png') %>"> | |
| <link rel="apple-touch-icon" sizes="180x180" href="<%= versioned_path('/apple-touch-icon.png') %>"> | |
| <link rel="apple-touch-icon-precomposed" href="<%= versioned_path('/apple-touch-icon-precomposed.png') %>"> |
| if ('serviceWorker' in navigator) { | ||
| window.addEventListener('load', () => { | ||
| navigator.serviceWorker.register('/service-worker.js') | ||
| const serviceWorkerPath = document.querySelector('meta[name="pwa-service-worker-path"]')?.content || '/service-worker.js' |
There was a problem hiding this comment.
Missing semicolon after the serviceWorkerPath declaration; the repo’s ESLint config extends semistandard, which enforces semicolons and will flag this.
| const serviceWorkerPath = document.querySelector('meta[name="pwa-service-worker-path"]')?.content || '/service-worker.js' | |
| const serviceWorkerPath = document.querySelector('meta[name="pwa-service-worker-path"]')?.content || '/service-worker.js'; |
| "/map", | ||
| "/beacon", | ||
| "/beacons", | ||
| "/triggers", | ||
| "/session", |
There was a problem hiding this comment.
Routes like map/beacon/beacons/triggers are under the /:event_name scope (e.g. /2026/map), but nonCacheableResources lists them as /map, /beacon, etc. With the new exact/startsWith("${path}/") check, the event-scoped URLs won’t match and will still be cached. Consider matching by suffix (e.g. endsWith('/map')) or generating the scoped paths explicitly.
| "/auth" | ||
| "/auth", | ||
| "/icon.png", | ||
| "/apple-touch-icon", |
There was a problem hiding this comment.
nonCacheableResources includes /apple-touch-icon, but the conventional iOS icon URLs are /apple-touch-icon.png and /apple-touch-icon-precomposed.png. With the new pathname matcher, requests for the .png files won’t match this entry and may still be cached. Add the .png paths (or adjust matching) if the goal is to keep these non-cacheable.
| "/apple-touch-icon", | |
| "/apple-touch-icon", | |
| "/apple-touch-icon.png", | |
| "/apple-touch-icon-precomposed.png", |
| "/manifest", | ||
| "/service-worker", |
There was a problem hiding this comment.
The non-cacheable list includes /manifest and /service-worker, but this app uses /manifest.json and /service-worker.js (via format: :json/:js). With the new exact/startsWith("${path}/") matcher, those .json/.js requests won’t be treated as non-cacheable. Include the .json/.js variants or relax the matcher to cover them.
| "/manifest", | |
| "/service-worker", | |
| "/manifest", | |
| "/manifest.json", | |
| "/service-worker", | |
| "/service-worker.js", |
| const fetchResponsePromise = fetch(request).then(async (networkResponse) => { | ||
| if (networkResponse.ok) { | ||
| const cache = await caches.open(cacheName); | ||
| cache.put(request, networkResponse.clone()); | ||
| } |
There was a problem hiding this comment.
cacheFirstWithRefresh will attempt cache.put(request, …) for any request type. Cache.put only supports GET/HEAD; PATCH/POST requests (e.g., Turbo form submissions to routes not in nonCacheableResources) will throw and can break those requests. Add a guard to bypass caching for non-GET requests (or at least before calling cache.put).
Introduce a shared PWA version helper and use versioned URLs for the manifest, service worker registration path, and apple-touch-icon links so iOS and Safari are forced to see updated metadata and icons.
Update the service worker to precache during install, delete old named caches during activate, claim clients immediately, and read cached responses only from the current cache instead of falling back to stale entries from previous cache names.
Mark manifest, service worker, and icon endpoints as non-cacheable in the service worker so browser metadata refreshes are not blocked by app-level caching.
Register the service worker with updateViaCache set to none and trigger an explicit registration update on load to reduce stale worker reuse.
Replace the empty public apple-touch-icon files with a real PNG so iOS home screen icons no longer resolve to invalid placeholder files.