The PWA manifest in frontend/vite.config.js (the VitePWA({ manifest: ... }) block) is missing two fields, and the maskable icon entry reuses an unpadded asset.
1. No id
Without an explicit id, the browser derives the app's identity from start_url. If start_url ever changes, the installed app is treated as a different app: users get a second icon on their home screen instead of an update to the one they already installed.
2. No scope
scope currently falls back to the start_url directory. That happens to be correct today, but it is implicit — anything that changes the deploy path changes navigation containment silently.
3. The maskable icon is the unpadded 512
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
Both entries point at the same file. Android crops a maskable icon to a circle or squircle and only guarantees the inner ~80% safe zone, so an icon drawn to the full canvas loses its edges — and usually its wordmark — on the home screen. A maskable icon needs its own export with the artwork inset into the safe zone.
Suggested fix
- Add
id: '/' and scope: '/' to the manifest block.
- Export a separate padded
icon-maskable-512.png and point the purpose: 'maskable' entry at it.
Verifying
Chrome DevTools → Application → Manifest shows id and scope once set, and previews the maskable icon inside the safe-zone circle.
The PWA manifest in
frontend/vite.config.js(theVitePWA({ manifest: ... })block) is missing two fields, and the maskable icon entry reuses an unpadded asset.1. No
idWithout an explicit
id, the browser derives the app's identity fromstart_url. Ifstart_urlever changes, the installed app is treated as a different app: users get a second icon on their home screen instead of an update to the one they already installed.2. No
scopescopecurrently falls back to thestart_urldirectory. That happens to be correct today, but it is implicit — anything that changes the deploy path changes navigation containment silently.3. The maskable icon is the unpadded 512
Both entries point at the same file. Android crops a
maskableicon to a circle or squircle and only guarantees the inner ~80% safe zone, so an icon drawn to the full canvas loses its edges — and usually its wordmark — on the home screen. A maskable icon needs its own export with the artwork inset into the safe zone.Suggested fix
id: '/'andscope: '/'to the manifest block.icon-maskable-512.pngand point thepurpose: 'maskable'entry at it.Verifying
Chrome DevTools → Application → Manifest shows
idandscopeonce set, and previews the maskable icon inside the safe-zone circle.