You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Notifications in GrottoCenter only exist while the site is open. They are fetched from the API and rendered in the bell menu, so they vanish the moment the tab is closed.
This is most visible in the Android app, now published on the Play Store. It is a Trusted Web Activity wrapping the website, and its notification delegation is already configured and shipping — Android is ready to display notifications under the GrottoCenter icon in the system tray. But the website never creates any, so the delegation has nothing to relay. Someone who installs the app gets a badge on a bell they have to open the app to see, which is not what installing an app leads you to expect.
Making this work needs matching changes on both sides. This issue covers the web app; the API side is tracked in GrottoCenter/grottocenter-api#1771.
Describe the solution you'd like
The web app should be able to receive a notification pushed by the API while it is closed, display it through the operating system, and open the right page when the user taps it.
In practice that means:
Owning a service worker that can react to an incoming push and to a tap on the resulting notification
Asking the user for permission and registering their device with the API
Somewhere in the account settings to turn this on and off, next to the notification preferences that already exist there
Parameters to change
vite-plugin-pwa: generateSW → injectManifest
This is the one real structural change, and the one to be careful with.
Today the service worker is fully generated by Workbox from the config in packages/web-app/vite.config.mjs. It does precaching and nothing else, and we never write a line of it. Handling push means we need our own service worker source file, which means switching the plugin to injectManifest mode.
⚠️The current setup is a deliberate balance, documented in a long comment in vite.config.mjs.registerType: 'prompt' combined with injectRegister: false is what makes updates land safely: a new service worker waits until the user accepts, so the running page keeps the precache its lazy-loaded route chunks live in. The comment explicitly warns that changing one of these without the other produces a service worker that either never activates or breaks code-split navigation.
Moving to injectManifest means taking over the file that this behaviour lives in. Whatever we write has to preserve it. This should not be treated as flipping an option.
Registration flow
Where and when we ask for notification permission. Browsers penalise sites that prompt on page load, and a prompt the user dismisses is effectively permanent — the permission cannot be requested again without the user digging into browser settings. So the prompt has to be attached to a deliberate user action, not shown on arrival.
Account settings UI
The notification preferences page already exposes the email toggles backed by the API. The push opt-in belongs there, with the addition that it depends on a browser permission the user may have already denied — so the UI has to be able to say "you blocked this, here is how to unblock it" rather than showing a switch that silently does nothing.
Points to decide
1. What a notification looks like and does when tapped
Each notification concerns an entity — a cave, an entrance, a document, a message. Tapping it should land on that entity, and ideally focus an already-open tab rather than opening a new one every time. Worth deciding whether we also show an action button ("mark as read") or keep it to a plain tap.
2. Which text is displayed
The API resolves each recipient's language server-side when it sends notification emails. The simplest approach is for push messages to arrive with their text already translated, so the service worker just displays what it is given. The alternative — shipping translations into the service worker and rendering there — means keeping a second copy of the i18n catalogue in sync. This needs agreeing with the API side.
3. Whether the badge and the push stay consistent
The bell already tracks an unread count. If a push arrives while the app is closed, the count in the UI should reflect it on next open, and reading a notification on one device should ideally not leave it showing on another. How far we go here is a scope decision.
4. iOS
Web push works on iOS only for web apps the user has added to the Home Screen, and only from iOS 16.4 onwards. There is no iOS app, so this is the only route for iPhone users, and it needs a different explanation in the UI than the Android and desktop cases. We should decide whether to support it at launch or acknowledge it as a follow-up.
5. Desktop
Nothing about this is Android-specific — desktop Chrome and Firefox support the same mechanism. Deciding whether we present this as "app notifications" or "browser notifications" changes how we word the setting.
The Android app itself needs no change. Notification delegation, the delegation service and the runtime permission are already configured in twa/ and shipping.
Relevant files: packages/web-app/vite.config.mjs (PWA config), packages/web-app/src/components/appli/UpdatePrompt.jsx (registers the service worker today), packages/web-app/src/pages/Account/ (preferences UI), packages/web-app/src/actions/Notifications/ (existing in-app notification actions).
Is your feature request related to a problem?
Notifications in GrottoCenter only exist while the site is open. They are fetched from the API and rendered in the bell menu, so they vanish the moment the tab is closed.
This is most visible in the Android app, now published on the Play Store. It is a Trusted Web Activity wrapping the website, and its notification delegation is already configured and shipping — Android is ready to display notifications under the GrottoCenter icon in the system tray. But the website never creates any, so the delegation has nothing to relay. Someone who installs the app gets a badge on a bell they have to open the app to see, which is not what installing an app leads you to expect.
Making this work needs matching changes on both sides. This issue covers the web app; the API side is tracked in GrottoCenter/grottocenter-api#1771.
Describe the solution you'd like
The web app should be able to receive a notification pushed by the API while it is closed, display it through the operating system, and open the right page when the user taps it.
In practice that means:
Parameters to change
vite-plugin-pwa:generateSW→injectManifestThis is the one real structural change, and the one to be careful with.
Today the service worker is fully generated by Workbox from the config in
packages/web-app/vite.config.mjs. It does precaching and nothing else, and we never write a line of it. Handling push means we need our own service worker source file, which means switching the plugin toinjectManifestmode.vite.config.mjs.registerType: 'prompt'combined withinjectRegister: falseis what makes updates land safely: a new service worker waits until the user accepts, so the running page keeps the precache its lazy-loaded route chunks live in. The comment explicitly warns that changing one of these without the other produces a service worker that either never activates or breaks code-split navigation.Moving to
injectManifestmeans taking over the file that this behaviour lives in. Whatever we write has to preserve it. This should not be treated as flipping an option.Registration flow
Where and when we ask for notification permission. Browsers penalise sites that prompt on page load, and a prompt the user dismisses is effectively permanent — the permission cannot be requested again without the user digging into browser settings. So the prompt has to be attached to a deliberate user action, not shown on arrival.
Account settings UI
The notification preferences page already exposes the email toggles backed by the API. The push opt-in belongs there, with the addition that it depends on a browser permission the user may have already denied — so the UI has to be able to say "you blocked this, here is how to unblock it" rather than showing a switch that silently does nothing.
Points to decide
1. What a notification looks like and does when tapped
Each notification concerns an entity — a cave, an entrance, a document, a message. Tapping it should land on that entity, and ideally focus an already-open tab rather than opening a new one every time. Worth deciding whether we also show an action button ("mark as read") or keep it to a plain tap.
2. Which text is displayed
The API resolves each recipient's language server-side when it sends notification emails. The simplest approach is for push messages to arrive with their text already translated, so the service worker just displays what it is given. The alternative — shipping translations into the service worker and rendering there — means keeping a second copy of the i18n catalogue in sync. This needs agreeing with the API side.
3. Whether the badge and the push stay consistent
The bell already tracks an unread count. If a push arrives while the app is closed, the count in the UI should reflect it on next open, and reading a notification on one device should ideally not leave it showing on another. How far we go here is a scope decision.
4. iOS
Web push works on iOS only for web apps the user has added to the Home Screen, and only from iOS 16.4 onwards. There is no iOS app, so this is the only route for iPhone users, and it needs a different explanation in the UI than the Android and desktop cases. We should decide whether to support it at launch or acknowledge it as a follow-up.
5. Desktop
Nothing about this is Android-specific — desktop Chrome and Firefox support the same mechanism. Deciding whether we present this as "app notifications" or "browser notifications" changes how we word the setting.
Additional context
twa/and shipping.packages/web-app/vite.config.mjs(PWA config),packages/web-app/src/components/appli/UpdatePrompt.jsx(registers the service worker today),packages/web-app/src/pages/Account/(preferences UI),packages/web-app/src/actions/Notifications/(existing in-app notification actions).