Commit Graph

3 Commits

Author SHA1 Message Date
julien 29f79d677e 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
2026-05-11 01:03:05 +02:00
julien 3371fbd613 feat(portal-shell): app-shell layout with collapsable sidebar (#83)
CI / commits (push) Has been skipped
CI / scan (push) Successful in 1m37s
CI / check (push) Successful in 2m25s
CI / a11y (push) Successful in 57s
CI / perf (push) Successful in 3m5s
## Summary

- Replace the flat header+main+footer layout with a real app-shell: fixed header on top, collapsable sidebar + scrollable main below. Sidebar state (collapsed / expanded) persists across reloads via `localStorage`.
- Introduce the APF brand palette as Tailwind v4 `@theme` tokens (primary teal `#12546c`, accent orange `#f7a919`) so every utility (`bg-brand-primary-500`, `text-brand-accent-400`, `ring-brand-primary-200`, …) is available from now on.
- Add an `<app-icon>` façade backed by `lucide-angular` for v1. Logical kebab-case names already match the icomoon-sprite convention, so the future migration is a single-file change in `icon.ts` and consumers stay untouched.
- Migrate the accessibility-statement links from the (now-deleted) footer to the bottom of the sidebar.

## Decisions worth flagging

- **Static menu, permission-shaped data.** Items point to `#` placeholders in v1, except *Dashboard* which is `routerLink="/"` so the active-state styling is visible on the home page. The `MenuItem` shape already carries an optional `requiredPermissions: string[]` so the permission-aware filter (PR 2, alongside ADR-0009 auth) plugs in without restructuring.
- **`<app-icon>` over direct lucide imports.** Consumers write `<app-icon name="bell">` rather than importing the lucide pascal-case symbol. When the icomoon sprite lands, only the registry in `icon.ts` changes — templates do not.
- **Sidebar persistence via `localStorage`, not backend.** Zero round-trip, survives reloads, falls back gracefully when storage is blocked (private mode). Eventually mirrored server-side if the user-preferences feature lands.
- **Footer removed entirely.** With the sidebar carrying the FR + EN accessibility-statement links and the role badge, the bottom rail no longer earned its vertical real estate. The version badge moved out for now; it will return as part of a debug/help menu when there's a real release to surface.

## Accessibility (ADR-0016)

- Skip-link preserved (WCAG 2.4.1 *Bypass Blocks*) and restyled in the brand palette.
- Sidebar exposes named landmarks (`<nav aria-label="Sections">`, `<nav aria-label="Accessibility">`) and the collapse button uses `aria-expanded` + a descriptive `aria-label`.
- Active links carry `ariaCurrentWhenActive="page"`.
- All interactive controls (header action buttons, sidebar links/toggle) meet the 44×44 px minimum hit-target.
- Sidebar width transition is skipped under `prefers-reduced-motion: reduce`.
- Lucide SVGs are marked `aria-hidden` (decorative); accessible names live on the parent control.

## Perf (ADR-0017)

- Production build: **100 kB gzip** initial transfer (budget: 300 kB). Lucide imports are tree-shaken — only the ~18 icons actually used ship.

## What this PR explicitly does NOT do

- Wire icon-set migration to icomoon (kept as a deferred swap behind `<app-icon>`).
- Filter the menu by permission (deferred to PR 2 once the auth flow lands).
- Replace the avatar placeholder with a real user menu (waits on ADR-0009).
- Implement the search input behavior (placeholder only; needs a search backend).

## Test plan

- [x] `pnpm exec nx run-many -t lint test build --projects=portal-shell` — green (19 / 19 specs).
- [x] Production build under bundle budgets (100 kB gzip initial).
- [ ] Manual: load `/`, confirm Dashboard appears active in the sidebar, collapse → reload → still collapsed, focus the address bar then Tab → skip-link visible, keyboard-traverse the sidebar.
- [ ] Manual: `prefers-reduced-motion: reduce` → no width animation when toggling.
- [ ] Manual: zoom to 200 % → no horizontal scroll, header search hides at narrow widths (`md:` breakpoint).

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #83
2026-05-11 00:36:41 +02:00
Julien Gautier bea5e1954f chore: generate portal-shell and portal-bff apps per ADR-0004 / ADR-0005
Add the @nx/angular, @nx/nest, @nx/vite, @nx/eslint plugins, then
generate the two apps. Adjust the empty-template tsconfig.base.json
to be Angular-compatible (drop project references and customConditions
that the empty-template defaults to but Angular doesn't support; keep
the strict-TS extensions from ADR-0004).

apps/portal-shell (Angular 21):
- standalone APIs, routing, SCSS, esbuild
- vitest-angular as unitTestRunner, playwright for e2e
- strict mode
- tags scope:portal-shell, type:app
- app.config.ts wired with provideZonelessChangeDetection() per
  ADR-0004 (Angular 21 + Nx 22 generates without zone.js by default)

apps/portal-bff (NestJS 11):
- Express adapter (default per ADR-0005)
- Jest as unitTestRunner
- tags scope:portal-bff, type:app
- main.ts wired with a global ValidationPipe configured
  whitelist + forbidNonWhitelisted + transform per ADR-0005
- Phase-2 security additions (helmet, CORS, sessions, CSRF, rate
  limit, auth guards, error filter) deferred to their respective
  ADRs - placeholder comment in main.ts

Workspace dependencies: class-validator + class-transformer added
(required by NestJS ValidationPipe at runtime). Nx-generated
.gitignore additions (.angular, __screenshots__) merged into ours.
.vscode/extensions.json and launch.json added by Nx are kept (do not
override our existing settings.json).
2026-04-30 16:12:42 +02:00