feat(portal-shell): full favicon set + PWA manifest (#84)
CI / check (push) Successful in 2m15s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 2m2s
CI / a11y (push) Successful in 1m45s
CI / perf (push) Successful in 3m44s

## Summary

- Replace the single root `favicon.ico` with a complete asset bundle under [`apps/portal-shell/public/favicons/`](apps/portal-shell/public/favicons/): SVG primary favicon, PNG fallback (96×96), legacy ICO, Apple touch icon (180×180), and a Web App Manifest with 192 / 512 maskable PNGs.
- Wire the assets in [`index.html`](apps/portal-shell/src/index.html) via the standard `<link rel="icon|apple-touch-icon|manifest">` block and add a matching `<meta name="theme-color" content="#ffffff">`.
- Set the manifest name to `"APF Portal"` (short_name `"Portal"`) so the PWA install banner and home-screen icon read consistently with the in-app header.

## Why it matters

- **Modern favicons.** SVG is now the primary icon — vector-clean at every density, automatic dark-mode adaptation when the SVG carries `prefers-color-scheme` rules. PNG + ICO entries cover legacy and pinned-tab contexts.
- **Installability.** With a valid manifest + 192/512 icons + `display: standalone`, the portal is installable on Android home-screens and as a desktop PWA in Chromium-based browsers, without shipping a service worker.
- **Mobile chrome.** `<meta name="theme-color">` aligned with the manifest's `theme_color` tints the browser address bar and the PWA chrome to white — matching the app header.

## Decisions worth flagging

- **Icons declared `purpose: "maskable"` only.** The PNGs were generated with the Android safe-zone padding. On Android they render correctly (cropped to the device's adaptive shape); on contexts that ask for `"any"` the browser falls back to the SVG / PNG / ICO entries from the `<link rel="icon">` block, so nothing breaks. If we later want a no-padding edge-to-edge variant for desktop, we can add a second icon entry with `purpose: "any"` and a different source.
- **`theme_color: #ffffff` rather than brand teal.** The app header is white in the v1 design; tinting the mobile chrome to teal would create a visible seam at the top of the viewport. We can revisit if a darker header lands.
- **Cache-buster query strings kept (`?v=20260511`).** Static `public/` assets are not hashed by Angular's build (only bundled JS/CSS are), so the explicit version stamp guards against stale caches on icon updates. The date matches the generation day.

## What this PR explicitly does NOT do

- Ship a service worker / offline support (PWA installability does not require it; offline strategy is a separate decision and likely a future ADR).
- Replace the SVG icon contents with a brand-tuned design — uses the existing generator output.
- Wire a localized manifest (single `name` / `short_name`, no `lang` variant per locale).

## Test plan

- [x] `pnpm exec nx build portal-shell --configuration=production` — green, 100 kB gzip initial (unchanged).
- [x] All 7 assets ship to `dist/apps/portal-shell/browser/favicons/`.
- [x] `index.html` in the production build contains every `<link>` + the `theme-color` meta.
- [ ] Manual: tab favicon visible in Chrome / Firefox / Safari.
- [ ] Manual: Chrome DevTools → Application → Manifest reports no errors and shows both 192/512 icons.
- [ ] Manual: Chrome desktop install prompt offers "Install APF Portal".
- [ ] Manual: Add-to-Home-Screen on Android shows the maskable icon clipped to the device's adaptive shape.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #84
This commit was merged in pull request #84.
This commit is contained in:
2026-05-11 01:03:05 +02:00
parent 3371fbd613
commit 29f79d677e
9 changed files with 36 additions and 2 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 92 KiB

@@ -0,0 +1,21 @@
{
"name": "APF Portal",
"short_name": "Portal",
"icons": [
{
"src": "/favicons/web-app-manifest-192x192.png?v=20260511",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/favicons/web-app-manifest-512x512.png?v=20260511",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

+14 -2
View File
@@ -2,10 +2,22 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>portal-shell</title>
<title>APF Portal</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<!-- Favicons + PWA manifest. SVG is the modern default; the PNG /
ICO entries cover older browsers and pinned-tab contexts. The
"180x180" Apple touch icon serves iOS home-screen installs.
The manifest unlocks Add-to-Home-Screen / Install on Android +
desktop Chromium. theme-color tints the browser chrome on
mobile and matches the manifest's value. -->
<link rel="icon" type="image/svg+xml" href="favicons/favicon.svg" />
<link rel="icon" type="image/png" sizes="96x96" href="favicons/favicon-96x96.png" />
<link rel="shortcut icon" href="favicons/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="favicons/apple-touch-icon.png" />
<link rel="manifest" href="favicons/site.webmanifest" />
<meta name="theme-color" content="#ffffff" />
</head>
<body>
<app-root></app-root>