feat(portal-shell): move theme switcher to footer, drop redundant settings icon #162

Merged
julien merged 1 commits from feat/portal-shell-theme-switcher-to-footer into main 2026-05-16 01:14:37 +02:00
Owner

Summary

Three coupled chrome adjustments in portal-shell's shell layout, all motivated by the same realisation that the UserMenu introduced in #149 made the header's standalone Settings icon redundant and surfaced an inconsistency in the theme switcher's placement (header for everyone, soon to be split between header and user-menu depending on auth state).

What lands

1. Drop the standalone Settings button from the header

The UserMenu already carries a "Settings (Soon)" row for authenticated users, and Settings has no meaning for anonymous traffic. The header icon pointed nowhere; keeping it doubled the surface and forced readers to guess which control to use. header.action.settings is removed from messages.fr.xlf in the same step — the i18n-strict prod build is now back in sync with the source.

2. Move <app-theme-switcher> from the header to the footer

The switcher now lives beside <app-locale-switcher> in a single device-prefs cluster on the right side of the footer. Two reasons:

  • Consistency across auth states. The alternative — keep it in the header for anonymous, move it into the user-menu once authenticated — was the original temptation. Rejected: changing the location of an identical control depending on whether the user is signed in violates Jakob's law and breaks muscle memory. Especially bad for an a11y-first platform per ADR-0016: a reader who relies on dark mode shouldn't have to expand a menu they don't yet recognise on first visit to escape a flashing white background.
  • Precedent. ADR-0019 already established the footer as the home for ambient device preferences (locale switcher). Theme is the obvious sibling — same family (per-device, non-identity).

3. Reshape the footer into two semantic clusters

[© APF France handicap  ·  Accessibility statement]              [locale  •  theme]
   ←——— info / legal ———→                                         ←—— device prefs ——→

The Accessibility statement link moves from the right cluster (where it sat alongside the locale switcher) to the left, joining the copyright with a typographic separator (·). Left = static info / legal anchor; right = interactive prefs the reader controls.

Keyboard order naturally follows: a Tab-traversal from the main content hits the informational anchor before the interactive controls — a small a11y win.

What I left alone

  • portal-admin's header / footer. No theme switcher there in v1 (the admin chrome is brand-primary-600 hardcoded); no settings icon either. ADR-0020 explicitly trims that admin chrome relative to the user shell — nothing to align right now.
  • The user-menu's "Settings (Soon)" row. That stays. PR 2 of the auth chantier (#150) wired the row at the request of the chantier's staging; the Settings page itself lands in a future chantier and the row's pre-existing "Soon" badge keeps the affordance honest.

Notes for the reviewer

  • Why not also a "Theme: " hint inside the user-menu? Considered, deferred. The current Hint would be ornament without a corresponding target; once the Settings page exists and the theme has a settings sub-section, a "Theme: System" line in the user-menu makes sense as a deep-link affordance. Not before.
  • Why a · text separator rather than a CSS border? Border would force vertical alignment math (height, dark-mode color) for one line of footer text; a typographic mid-dot inherits text color, scales with the line, and is aria-hidden so screen readers don't read it as "middle dot". Smaller surface for one less moving piece.
  • A11y check: the left cluster's <nav aria-label="Legal"> is preserved verbatim (just moved into the left <div> rather than the right). The accessibility statement keeps the same focus styles, keyboard behavior, and routerLink target.

Test plan

  • pnpm nx test portal-shell43 specs pass (was 40; +3 net: dropped a Settings-button assertion + the "embeds theme switcher" header assertion swapped to its inverse, added two footer assertions for the theme switcher's new home + the cluster grouping).
  • pnpm nx run-many -t lint test build --projects=portal-shell — green.
  • pnpm nx build portal-shell --configuration=production — i18n-strict prod build passes, confirming the header.action.settings xlf removal didn't leave a dangling source-locale reference.
  • Manual visual smoke: open pnpm nx serve portal-shell in a browser, confirm:
    • The Settings cog is gone from the header.
    • The theme-switcher (sun/moon chip) now lives at the bottom-right of the footer, beside the locale chip.
    • Bottom-left of the footer reads © 2026 APF France handicap · Accessibility statement, the dot being decorative (no link).
    • Tabbing from the main content lands first on the accessibility link, then on the locale switcher, then on the theme switcher.
    • Toggling the theme still works as before, and toggling it persists across navigations and reloads (the underlying ThemeService is untouched).

Follow-ups (optional)

  • The header still carries the global search form + notifications + help buttons. None of those are wired to live data yet — when they get real consumers, the equivalent "double-surface" check applies (is there a duplicate path in the user-menu? a redundant trigger?). For now the placeholder shapes are useful to anchor the layout.
## Summary Three coupled chrome adjustments in `portal-shell`'s shell layout, all motivated by the same realisation that the UserMenu introduced in #149 made the header's standalone Settings icon redundant and surfaced an inconsistency in the theme switcher's placement (header for everyone, soon to be split between header and user-menu depending on auth state). ## What lands ### 1. Drop the standalone Settings button from the header The UserMenu already carries a "Settings (Soon)" row for authenticated users, and Settings has no meaning for anonymous traffic. The header icon pointed nowhere; keeping it doubled the surface and forced readers to guess which control to use. `header.action.settings` is removed from [`messages.fr.xlf`](apps/portal-shell/src/locale/messages.fr.xlf) in the same step — the i18n-strict prod build is now back in sync with the source. ### 2. Move `<app-theme-switcher>` from the header to the footer The switcher now lives beside `<app-locale-switcher>` in a single **device-prefs cluster** on the right side of the footer. Two reasons: - **Consistency across auth states.** The alternative — keep it in the header for anonymous, move it into the user-menu once authenticated — was the original temptation. Rejected: changing the location of an identical control depending on whether the user is signed in violates Jakob's law and breaks muscle memory. Especially bad for an a11y-first platform per [ADR-0016](docs/decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md): a reader who relies on dark mode shouldn't have to expand a menu they don't yet recognise on first visit to escape a flashing white background. - **Precedent.** [ADR-0019](docs/decisions/0019-internationalisation-angular-localize.md) already established the footer as the home for ambient device preferences (locale switcher). Theme is the obvious sibling — same family (per-device, non-identity). ### 3. Reshape the footer into two semantic clusters ``` [© APF France handicap · Accessibility statement] [locale • theme] ←——— info / legal ———→ ←—— device prefs ——→ ``` The Accessibility statement link moves from the right cluster (where it sat alongside the locale switcher) to the left, joining the copyright with a typographic separator (`·`). Left = static info / legal anchor; right = interactive prefs the reader controls. Keyboard order naturally follows: a Tab-traversal from the main content hits the informational anchor before the interactive controls — a small a11y win. ## What I left alone - **`portal-admin`'s header / footer.** No theme switcher there in v1 (the admin chrome is brand-primary-600 hardcoded); no settings icon either. ADR-0020 explicitly trims that admin chrome relative to the user shell — nothing to align right now. - **The user-menu's "Settings (Soon)" row.** That stays. PR 2 of the auth chantier ([#150](https://git.unespace.com/julien/apf_portal/pulls/150)) wired the row at the request of the chantier's staging; the Settings page itself lands in a future chantier and the row's pre-existing "Soon" badge keeps the affordance honest. ## Notes for the reviewer - **Why not also a "Theme: <current>" hint inside the user-menu?** Considered, deferred. The current Hint would be ornament without a corresponding *target*; once the Settings page exists and the theme has a settings sub-section, a "Theme: System" line in the user-menu makes sense as a deep-link affordance. Not before. - **Why a `·` text separator rather than a CSS border?** Border would force vertical alignment math (height, dark-mode color) for one line of footer text; a typographic mid-dot inherits text color, scales with the line, and is `aria-hidden` so screen readers don't read it as "middle dot". Smaller surface for one less moving piece. - **A11y check**: the left cluster's `<nav aria-label="Legal">` is preserved verbatim (just moved into the left `<div>` rather than the right). The accessibility statement keeps the same focus styles, keyboard behavior, and routerLink target. ## Test plan - [x] `pnpm nx test portal-shell` — **43 specs pass** (was 40; +3 net: dropped a Settings-button assertion + the "embeds theme switcher" header assertion swapped to its inverse, added two footer assertions for the theme switcher's new home + the cluster grouping). - [x] `pnpm nx run-many -t lint test build --projects=portal-shell` — green. - [x] `pnpm nx build portal-shell --configuration=production` — i18n-strict prod build passes, confirming the `header.action.settings` xlf removal didn't leave a dangling source-locale reference. - [ ] **Manual visual smoke**: open `pnpm nx serve portal-shell` in a browser, confirm: - The Settings cog is gone from the header. - The theme-switcher (sun/moon chip) now lives at the bottom-right of the footer, beside the locale chip. - Bottom-left of the footer reads `© 2026 APF France handicap · Accessibility statement`, the dot being decorative (no link). - Tabbing from the main content lands first on the accessibility link, then on the locale switcher, then on the theme switcher. - Toggling the theme still works as before, and toggling it persists across navigations and reloads (the underlying ThemeService is untouched). ## Follow-ups (optional) - The header still carries the global search form + notifications + help buttons. None of those are wired to live data yet — when they get real consumers, the equivalent "double-surface" check applies (is there a duplicate path in the user-menu? a redundant trigger?). For now the placeholder shapes are useful to anchor the layout.
julien added 1 commit 2026-05-16 01:14:18 +02:00
feat(portal-shell): move theme switcher to footer, drop redundant settings icon
CI / scan (pull_request) Successful in 2m36s
CI / commits (pull_request) Successful in 2m49s
CI / check (pull_request) Successful in 2m56s
CI / a11y (pull_request) Successful in 1m52s
CI / perf (pull_request) Successful in 4m10s
a8ddb0f63d
Three coupled chrome adjustments — all in `portal-shell`'s shell
layout, all motivated by the same realisation that the user-menu
introduced in #149 made the header's standalone Settings icon
redundant and surfaced an inconsistency in the theme switcher's
placement.

* **Drop the standalone Settings button** from the header. The
  UserMenu already carries a "Settings (Soon)" row for authenticated
  users, and Settings has no meaning for anonymous traffic. The
  icon pointed nowhere; carrying it doubled the surface and forced
  readers to guess which control to use. The `header.action.settings`
  i18n unit is removed from `messages.fr.xlf` in the same step so
  the prod build's `i18nMissingTranslation=error` policy stays
  consistent.

* **Move `<app-theme-switcher>` from the header to the footer.** It
  now sits beside `<app-locale-switcher>` in a single "device-prefs"
  cluster on the right side of the footer. The alternative
  considered — keeping it in the header for anonymous traffic and
  moving it into the user menu once authenticated — was rejected:
  the trigger location for an identical control (changing read
  conditions) must not depend on auth state, especially for an
  a11y-first platform per ADR-0016. A reader who relies on dark
  mode shouldn't have to expand a menu they don't recognise on
  first visit to escape a flashing white background. The footer
  matches what ADR-0019 already established for the locale
  switcher (ambient device preferences live there).

* **Reshape the footer into two semantic clusters.** The
  Accessibility statement link moves from the right (preferences)
  cluster to the left, joining the copyright with a typographic
  separator (`·`). Left = static info / legal anchor ("who owns
  this, where to find legal info"); right = interactive prefs
  ("what I control about this page"). Keyboard order naturally
  follows: a Tab-traversal from main content hits info before
  interactive controls.

Specs updated:
  - Header spec swaps "renders Settings button" / "embeds theme
    switcher" assertions for the absence guards that lock the
    new layout.
  - Footer spec adds a theme-switcher assertion and a cluster-grouping
    assertion that pins copyright + accessibility to the first
    direct-child div, locale + theme to the second.

Verification: `pnpm nx run-many -t lint test build --projects=portal-shell`
green, 43 portal-shell specs (was 40); the i18n-strict production
build (`portal-shell:build:production`) passes too — confirms the
xlf cleanup didn't strand any source-locale reference.
julien merged commit 076cfb67a6 into main 2026-05-16 01:14:37 +02:00
julien deleted branch feat/portal-shell-theme-switcher-to-footer 2026-05-16 01:14:39 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: julien/apf_portal#162