feat(header): replace white header with brand-navy header per design spec #165

Merged
marcel merged 12 commits from feat/issue-163-brand-navy-header into main 2026-03-31 10:41:41 +02:00
Owner

Closes #163

Summary

  • Header background replaced with bg-brand-navy (#012851) — static token, stays navy in both light and dark mode
  • 4px bg-accent strip added above the nav bar
  • Active nav state changed from background pill to border-b-2 border-accent bottom underline
  • Inactive nav links: text-white/55, hover text-white/85
  • Logo now visible on all viewport sizes including mobile
  • Avatar changed to bg-brand-mint text-brand-navy (8.5:1 contrast)
  • --c-nav-active CSS token deleted; mobile drawer active state uses bg-accent-bg
  • New AuthHeader.svelte component added for auth pages (logo + language switcher on navy bar)
  • Login and forgot-password pages use AuthHeader instead of the floating language switcher
  • Focus rings (focus-visible:ring-2 ring-accent) on all interactive header elements
  • LanguageSwitcher gains an inverted boolean prop for white text in dark-header contexts

Test plan

  • Header background is #012851 in light mode
  • Header background stays #012851 after switching to dark mode (toggle theme toggle)
  • Active nav link shows white text with mint bottom underline — no background pill
  • Inactive nav links are dimmed white; brighten on hover
  • Logo "Familienarchiv" is visible at 375px viewport width
  • Mobile hamburger icon is white/visible; tapping opens the white-background drawer
  • Mobile drawer active state has a subtle mint tint background
  • Avatar circle is mint with navy initials
  • Language switcher on desktop header shows white text
  • Language switcher in mobile drawer still shows navy text (not inverted)
  • Login page has a navy header bar at the top with logo + language switcher
  • Forgot-password page also has the navy header
  • Playwright e2e/header.spec.ts passes (4 tests)

🤖 Generated with Claude Code

Closes #163 ## Summary - Header background replaced with `bg-brand-navy` (`#012851`) — static token, stays navy in both light and dark mode - 4px `bg-accent` strip added above the nav bar - Active nav state changed from background pill to `border-b-2 border-accent` bottom underline - Inactive nav links: `text-white/55`, hover `text-white/85` - Logo now visible on all viewport sizes including mobile - Avatar changed to `bg-brand-mint text-brand-navy` (8.5:1 contrast) - `--c-nav-active` CSS token deleted; mobile drawer active state uses `bg-accent-bg` - New `AuthHeader.svelte` component added for auth pages (logo + language switcher on navy bar) - Login and forgot-password pages use `AuthHeader` instead of the floating language switcher - Focus rings (`focus-visible:ring-2 ring-accent`) on all interactive header elements - `LanguageSwitcher` gains an `inverted` boolean prop for white text in dark-header contexts ## Test plan - [ ] Header background is `#012851` in light mode - [ ] Header background stays `#012851` after switching to dark mode (toggle theme toggle) - [ ] Active nav link shows white text with mint bottom underline — no background pill - [ ] Inactive nav links are dimmed white; brighten on hover - [ ] Logo "Familienarchiv" is visible at 375px viewport width - [ ] Mobile hamburger icon is white/visible; tapping opens the white-background drawer - [ ] Mobile drawer active state has a subtle mint tint background - [ ] Avatar circle is mint with navy initials - [ ] Language switcher on desktop header shows white text - [ ] Language switcher in mobile drawer still shows navy text (not inverted) - [ ] Login page has a navy header bar at the top with logo + language switcher - [ ] Forgot-password page also has the navy header - [ ] Playwright `e2e/header.spec.ts` passes (4 tests) 🤖 Generated with [Claude Code](https://claude.ai/claude-code)
marcel added 9 commits 2026-03-30 22:21:59 +02:00
The nav active state moves from a background pill to a bottom-border
underline, so the rgba purple tint variable is no longer needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When inverted=true, buttons render white text instead of ink tokens,
suitable for placement on brand-navy background.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace bg-surface border-b with bg-brand-navy (always #012851)
- Add 4px bg-accent strip above the nav bar
- Remove border-r separator from language switcher wrapper
- Pass inverted prop to LanguageSwitcher for white text on dark bg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Logo: always visible (remove hidden md:flex), text-white
- Outer wrapper: items-stretch so active border reaches header bottom
- Desktop nav: items-stretch, active = border-b-2 border-accent text-white
- Inactive links: text-white/55, hover text-white/85
- Hamburger: text-white/70, hover text-white
- Mobile drawer active: bg-accent-bg replacing removed bg-nav-active
- Focus rings: focus-visible:ring-2 ring-accent on all interactive elements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Avatar: bg-brand-mint text-brand-navy (mint circle, navy initials)
- Guest icon button: text-white/60, hover text-white
- Both buttons: focus-visible:ring-2 ring-accent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Provides logo + language switcher on brand-navy background with
4px accent strip. Used on login and forgot-password pages in place
of the floating language switcher.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the absolutely-positioned language switcher div and replaces it
with the shared AuthHeader component (logo + lang switcher on navy bar).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test(header): add Playwright tests for brand-navy header
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
c905f136d2
- Asserts header background is rgb(1,40,81) in light mode
- Asserts header stays navy after switching to dark mode
- Asserts logo text visible at 375px viewport
- Asserts login page has AuthHeader with navy background and lang switcher

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel added 1 commit 2026-03-30 22:54:37 +02:00
fix(header): consistent icon styling, focus rings, and responsive breakpoints
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
281934529e
- Normalize all header icon buttons to white/65 + white/10 hover bg
- Fix guest person icon (img tag needs brightness-0 invert, not text color)
- Add missing focus-visible rings to ThemeToggle and LanguageSwitcher
- Use focus-visible:rounded on nav links so active underline stays sharp
- Bump burger/nav breakpoint from sm→lg to prevent overflow on tablets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Verdict: ⚠️ Approved with concerns

Blockers

AuthHeader.svelte duplicates LanguageSwitcher internals — violates DRY

AuthHeader.svelte contains its own locale list, localeMap, $derived(getLocale()), and button rendering logic — the exact same logic already encapsulated in LanguageSwitcher.svelte. The whole point of adding the inverted prop to LanguageSwitcher was to make it reusable on dark backgrounds. AuthHeader should use it:

<!-- AuthHeader.svelte — replace the inlined locale loop with: -->
<LanguageSwitcher inverted />

This would also mean AuthHeader.svelte's <script> block disappears entirely, making it a clean presentational component.

The duplication meets the "3+ places" extraction threshold: the locale buttons now exist in LanguageSwitcher.svelte, AuthHeader.svelte, and previously in login/+page.svelte (now removed). But we're still left with two.

Suggestions

{#each} key in AuthHeader.svelte is good(locale) is correct for a primitive list. ✓

my-2 + border-b-2 active state may float above the header edge. The nav container uses items-stretch, but each link has my-2 (8px vertical margin), so the border-b-2 active underline sits 8px above the header's bottom edge. The visual spec may intend the underline to reach the very bottom of the bar. Worth verifying against the design.

Test hygiene — [data-hydrated] ties tests to implementation detail. The tests use await page.waitForSelector('[data-hydrated]') as a hydration guard. If the attribute is ever renamed, two tests break silently on a non-UI change. Consider a role-based selector or a visible nav element as the readiness signal instead:

await page.waitForSelector('[role="navigation"]');

TDD evidence: Tests are committed on the same branch alongside implementation — I'd prefer to see them in a prior commit establishing the failing state, but the coverage is there and meaningful. ✓

## 👨‍💻 Felix Brandt — Senior Fullstack Developer **Verdict: ⚠️ Approved with concerns** ### Blockers **`AuthHeader.svelte` duplicates `LanguageSwitcher` internals — violates DRY** `AuthHeader.svelte` contains its own locale list, `localeMap`, `$derived(getLocale())`, and button rendering logic — the exact same logic already encapsulated in `LanguageSwitcher.svelte`. The whole point of adding the `inverted` prop to `LanguageSwitcher` was to make it reusable on dark backgrounds. `AuthHeader` should use it: ```svelte <!-- AuthHeader.svelte — replace the inlined locale loop with: --> <LanguageSwitcher inverted /> ``` This would also mean `AuthHeader.svelte`'s `<script>` block disappears entirely, making it a clean presentational component. The duplication meets the "3+ places" extraction threshold: the locale buttons now exist in `LanguageSwitcher.svelte`, `AuthHeader.svelte`, and previously in `login/+page.svelte` (now removed). But we're still left with two. ### Suggestions **`{#each}` key in `AuthHeader.svelte` is good** — `(locale)` is correct for a primitive list. ✓ **`my-2` + `border-b-2` active state may float above the header edge.** The nav container uses `items-stretch`, but each link has `my-2` (8px vertical margin), so the `border-b-2` active underline sits 8px above the header's bottom edge. The visual spec may intend the underline to reach the very bottom of the bar. Worth verifying against the design. **Test hygiene — `[data-hydrated]` ties tests to implementation detail.** The tests use `await page.waitForSelector('[data-hydrated]')` as a hydration guard. If the attribute is ever renamed, two tests break silently on a non-UI change. Consider a role-based selector or a visible nav element as the readiness signal instead: ```typescript await page.waitForSelector('[role="navigation"]'); ``` **TDD evidence:** Tests are committed on the same branch alongside implementation — I'd prefer to see them in a prior commit establishing the failing state, but the coverage is there and meaningful. ✓
Author
Owner

🏛️ Markus Keller — Application Architect

Verdict: Approved

This is a contained, frontend-only change. No new layers, no new dependencies, no backend or database involvement. Architecture is clean.

Observations

Removal of --color-nav-active / --c-nav-active is clean. The token is deleted from all three theme definitions (:root, [data-theme="dark"], and the second dark block). No zombie CSS variables left dangling. ✓

AuthHeader.svelte placement in routes/ vs lib/components/: The component is placed next to the routes that use it, which is reasonable for a layout component used only by auth pages. However, since +layout.svelte also lives in routes/ and imports from it, there's no layering violation. If the component ever needs to be used outside auth pages, move it to lib/components/.

Breakpoint shift from smlg for desktop nav: The hamburger menu now appears up to 1023px instead of up to 639px. This means tablets (768px–1023px) always get the mobile drawer instead of the inline nav. This is a design choice and the implementation is consistent throughout (AppNav, the mobile overlay fixed inset-0 top-[68px] z-40 lg:hidden). No inconsistent breakpoints found. ✓

The LanguageSwitcher duplication in AuthHeader (flagged by Felix) is a modularity concern. The component boundary exists precisely to avoid this. I'd want to see <LanguageSwitcher inverted /> used there before merge.

No structural or layering violations. Frontend module boundaries respected.

## 🏛️ Markus Keller — Application Architect **Verdict: ✅ Approved** This is a contained, frontend-only change. No new layers, no new dependencies, no backend or database involvement. Architecture is clean. ### Observations **Removal of `--color-nav-active` / `--c-nav-active` is clean.** The token is deleted from all three theme definitions (`:root`, `[data-theme="dark"]`, and the second dark block). No zombie CSS variables left dangling. ✓ **`AuthHeader.svelte` placement in `routes/` vs `lib/components/`:** The component is placed next to the routes that use it, which is reasonable for a layout component used only by auth pages. However, since `+layout.svelte` also lives in `routes/` and imports from it, there's no layering violation. If the component ever needs to be used outside auth pages, move it to `lib/components/`. **Breakpoint shift from `sm` → `lg` for desktop nav:** The hamburger menu now appears up to 1023px instead of up to 639px. This means tablets (768px–1023px) always get the mobile drawer instead of the inline nav. This is a design choice and the implementation is consistent throughout (AppNav, the mobile overlay `fixed inset-0 top-[68px] z-40 lg:hidden`). No inconsistent breakpoints found. ✓ **The LanguageSwitcher duplication in AuthHeader** (flagged by Felix) is a modularity concern. The component boundary exists precisely to avoid this. I'd want to see `<LanguageSwitcher inverted />` used there before merge. No structural or layering violations. Frontend module boundaries respected.
Author
Owner

🧪 Sara Holt — QA Engineer

Verdict: ⚠️ Approved with concerns

Good that tests exist — four E2E scenarios covering the most critical regression risks. Some gaps to address.

Blockers

No accessibility checks in E2E tests. My standard is checkA11y on every critical page. The header is a global navigation landmark — contrast failures, missing ARIA labels, and focus trap issues all surface here. This is especially important given the contrast concerns Leonie will raise about text-white/55.

import { checkA11y } from 'axe-playwright';

test('header background is brand-navy in light mode', async ({ page }) => {
    await page.goto('/');
    await page.waitForSelector('[data-hydrated]');
    await checkA11y(page);  // add this
    // ... rest of test
});

Suggestions

[data-hydrated] is an implementation coupling. If this attribute is renamed or removed, the two hydration-dependent tests will time out silently rather than failing descriptively. A safer readiness signal:

await expect(page.getByRole('navigation')).toBeVisible();

The BRAND_NAVY constant value is correctrgb(1, 40, 81) maps to #012851 matching --c-primary in layout.css. ✓

Missing: visual regression tests. This PR changes the entire header appearance (background, nav state style, avatar color). There are no visual regression snapshots at the required breakpoints (320px, 768px, 1440px) in light and dark mode. These should exist before merge so future changes have a baseline.

Missing: unit test for LanguageSwitcher inverted prop. The new boolean prop adds a conditional rendering branch in LanguageSwitcher.svelte. A Vitest component test would verify:

  • Active locale renders text-white font-bold when inverted=true
  • Active locale renders text-ink font-bold when inverted=false
  • Inactive locale renders text-white/55 when inverted=true

Missing: hamburger menu test at tablet breakpoints. The breakpoint change (sm → lg) means 768px–1023px now gets the mobile drawer. A test at { width: 768, height: 1024 } confirming the hamburger is visible (not the inline nav) would catch regressions.

Login test correctly clears auth state with storageState: { cookies: [], origins: [] }. ✓

## 🧪 Sara Holt — QA Engineer **Verdict: ⚠️ Approved with concerns** Good that tests exist — four E2E scenarios covering the most critical regression risks. Some gaps to address. ### Blockers **No accessibility checks in E2E tests.** My standard is `checkA11y` on every critical page. The header is a global navigation landmark — contrast failures, missing ARIA labels, and focus trap issues all surface here. This is especially important given the contrast concerns Leonie will raise about `text-white/55`. ```typescript import { checkA11y } from 'axe-playwright'; test('header background is brand-navy in light mode', async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-hydrated]'); await checkA11y(page); // add this // ... rest of test }); ``` ### Suggestions **`[data-hydrated]` is an implementation coupling.** If this attribute is renamed or removed, the two hydration-dependent tests will time out silently rather than failing descriptively. A safer readiness signal: ```typescript await expect(page.getByRole('navigation')).toBeVisible(); ``` **The `BRAND_NAVY` constant value is correct** — `rgb(1, 40, 81)` maps to `#012851` matching `--c-primary` in `layout.css`. ✓ **Missing: visual regression tests.** This PR changes the entire header appearance (background, nav state style, avatar color). There are no visual regression snapshots at the required breakpoints (320px, 768px, 1440px) in light and dark mode. These should exist before merge so future changes have a baseline. **Missing: unit test for `LanguageSwitcher inverted` prop.** The new boolean prop adds a conditional rendering branch in `LanguageSwitcher.svelte`. A Vitest component test would verify: - Active locale renders `text-white font-bold` when `inverted=true` - Active locale renders `text-ink font-bold` when `inverted=false` - Inactive locale renders `text-white/55` when `inverted=true` **Missing: hamburger menu test at tablet breakpoints.** The breakpoint change (sm → lg) means 768px–1023px now gets the mobile drawer. A test at `{ width: 768, height: 1024 }` confirming the hamburger is visible (not the inline nav) would catch regressions. **Login test correctly clears auth state** with `storageState: { cookies: [], origins: [] }`. ✓
Author
Owner

🔐 Nora "NullX" Steiner — Security Engineer

Verdict: Approved

This PR is a pure visual/CSS change — header background color, nav state styling, avatar colors, and a new auth page layout component. No authentication logic, no new endpoints, no data handling, no server-side changes.

What I Checked

  • No new server routes or actionsforgot-password/+page.svelte and login/+page.svelte only adopt a new header component, no changes to form actions or data flows.
  • AuthHeader.svelte links to href="/" only — no open redirect vectors.
  • LanguageSwitcher inverted prop — purely a CSS class toggle. setLocale() call is unchanged. No user input reaches any sink.
  • brightness-0 invert CSS filter on the account icon — client-side SVG rendering, no security implications.
  • No new dependencies added — supply chain surface unchanged.
  • No secrets or credentials touched.

Informational

The <button type="button"> on all locale switcher buttons is correct — without type="button", buttons inside a <form> default to type="submit" and could trigger unintended form submissions. On the auth pages this matters since there are login/forgot-password forms on the same page. ✓

Nothing to block or flag here. Clean change.

## 🔐 Nora "NullX" Steiner — Security Engineer **Verdict: ✅ Approved** This PR is a pure visual/CSS change — header background color, nav state styling, avatar colors, and a new auth page layout component. No authentication logic, no new endpoints, no data handling, no server-side changes. ### What I Checked - **No new server routes or actions** — `forgot-password/+page.svelte` and `login/+page.svelte` only adopt a new header component, no changes to form actions or data flows. - **`AuthHeader.svelte` links to `href="/"` only** — no open redirect vectors. - **`LanguageSwitcher` `inverted` prop** — purely a CSS class toggle. `setLocale()` call is unchanged. No user input reaches any sink. - **`brightness-0 invert` CSS filter on the account icon** — client-side SVG rendering, no security implications. - **No new dependencies added** — supply chain surface unchanged. - **No secrets or credentials touched.** ### Informational The `<button type="button">` on all locale switcher buttons is correct — without `type="button"`, buttons inside a `<form>` default to `type="submit"` and could trigger unintended form submissions. On the auth pages this matters since there are login/forgot-password forms on the same page. ✓ Nothing to block or flag here. Clean change.
Author
Owner

🎨 Leonie Voss — UI/UX Design Lead

Verdict: 🚫 Changes requested

The navy header direction is right and I'm glad to see it. The mint avatar at 8.5:1 is excellent. Focus rings on all interactive elements — exactly what I asked for. But there's a WCAG AA failure I cannot approve.

Blockers

text-white/55 fails WCAG AA for nav link text (CWE: WCAG 1.4.3 Contrast Minimum)

Inactive nav items and inactive locale buttons use text-white/55 on bg-brand-navy (#012851). White at 55% opacity blended against #012851 yields approximately rgb(141, 148, 168), giving a contrast ratio of ~3.0:1 against the navy background.

  • Nav items are text-xs (12px) font-bold uppercase — 12px bold does not qualify as "large text" under WCAG 2.2 (large text = ≥18px regular or ≥14px bold). So the 4.5:1 AA threshold applies.
  • 3.0:1 fails AA. This affects every inactive nav link on desktop, every inactive locale button on desktop and in AuthHeader.

Fix — raise opacity to meet 4.5:1:

text-white/55  → text-white/70   (~4.5:1 on #012851)
hover:text-white/85 → hover:text-white  (full white = 17:1, excellent)

The same fix must be applied consistently in:

  • AppNav.svelte — desktop nav links (inactive state)
  • AppNav.svelte — mobile hamburger icon (text-white/70)
  • LanguageSwitcher.svelte — inactive locale buttons (inverted branch)
  • AuthHeader.svelte — inactive locale buttons
  • NotificationBell.sveltetext-white/65 is borderline; bell icon gets 3:1 UI component exception (WCAG 1.4.11) ✓
  • ThemeToggle.sveltetext-white/65 — same UI component exception applies ✓
  • UserMenu.svelte — account icon opacity-65 — UI component exception ✓

text-white/70 for text-level elements, text-white/65 acceptable for icon-only UI components.


Suggestions

Active border-b-2 underline positioning. Desktop nav links use my-2 inline-flex items-center px-3. The my-2 (8px margin) means the border-b-2 border-accent sits 8px above the header's bottom edge — it won't visually connect to the accent strip below the header. The effect is a floating underline in the middle of the bar rather than a tab-style indicator grounded at the bottom. Consider removing my-2 and using pb-0.5 or self-end pb-1 to push the underline to the bottom of the h-16 container:

<!-- instead of my-2 inline-flex items-center -->
class="self-end inline-flex items-end px-3 pb-1 font-sans ... {active ? 'border-b-2 border-accent text-white' : ...}"

Breakpoint shift sm → lg acknowledged. Tablets (768px–1023px) now always show the hamburger. This is an intentional design choice per the PR description. The implementation is consistent. I'm noting it as a UX trade-off: tablet users get a more mobile-like nav experience. Fine if that was deliberate.

AuthHeader height is h-12 vs main header h-16. The auth pages get a slimmer header — this reads well as a reduced-chrome context. ✓

Logo always visible on mobile — good, this was missing before. ✓

Avatar bg-brand-mint text-brand-navy — mint (#A6DAD8) on navy (#012851) text: this combination is used for the initials avatar, which has bg-brand-mint background and text-brand-navy text. #A6DAD8 bg with #012851 text: contrast ≈ 6.7:1. Excellent. ✓

## 🎨 Leonie Voss — UI/UX Design Lead **Verdict: 🚫 Changes requested** The navy header direction is right and I'm glad to see it. The mint avatar at 8.5:1 is excellent. Focus rings on all interactive elements — exactly what I asked for. But there's a WCAG AA failure I cannot approve. ### Blockers **`text-white/55` fails WCAG AA for nav link text (CWE: WCAG 1.4.3 Contrast Minimum)** Inactive nav items and inactive locale buttons use `text-white/55` on `bg-brand-navy` (`#012851`). White at 55% opacity blended against `#012851` yields approximately `rgb(141, 148, 168)`, giving a contrast ratio of ~3.0:1 against the navy background. - Nav items are `text-xs` (12px) `font-bold` uppercase — 12px bold does **not** qualify as "large text" under WCAG 2.2 (large text = ≥18px regular or ≥14px bold). So the 4.5:1 AA threshold applies. - **3.0:1 fails AA.** This affects every inactive nav link on desktop, every inactive locale button on desktop and in `AuthHeader`. **Fix — raise opacity to meet 4.5:1:** ``` text-white/55 → text-white/70 (~4.5:1 on #012851) hover:text-white/85 → hover:text-white (full white = 17:1, excellent) ``` The same fix must be applied consistently in: - `AppNav.svelte` — desktop nav links (inactive state) - `AppNav.svelte` — mobile hamburger icon (`text-white/70`) - `LanguageSwitcher.svelte` — inactive locale buttons (`inverted` branch) - `AuthHeader.svelte` — inactive locale buttons - `NotificationBell.svelte` — `text-white/65` is borderline; bell icon gets 3:1 UI component exception (WCAG 1.4.11) ✓ - `ThemeToggle.svelte` — `text-white/65` — same UI component exception applies ✓ - `UserMenu.svelte` — account icon `opacity-65` — UI component exception ✓ **`text-white/70` for text-level elements, `text-white/65` acceptable for icon-only UI components.** --- ### Suggestions **Active `border-b-2` underline positioning.** Desktop nav links use `my-2 inline-flex items-center px-3`. The `my-2` (8px margin) means the `border-b-2 border-accent` sits 8px above the header's bottom edge — it won't visually connect to the accent strip below the header. The effect is a floating underline in the middle of the bar rather than a tab-style indicator grounded at the bottom. Consider removing `my-2` and using `pb-0.5` or `self-end pb-1` to push the underline to the bottom of the h-16 container: ```svelte <!-- instead of my-2 inline-flex items-center --> class="self-end inline-flex items-end px-3 pb-1 font-sans ... {active ? 'border-b-2 border-accent text-white' : ...}" ``` **Breakpoint shift sm → lg acknowledged.** Tablets (768px–1023px) now always show the hamburger. This is an intentional design choice per the PR description. The implementation is consistent. I'm noting it as a UX trade-off: tablet users get a more mobile-like nav experience. Fine if that was deliberate. **`AuthHeader` height is `h-12` vs main header `h-16`.** The auth pages get a slimmer header — this reads well as a reduced-chrome context. ✓ **Logo always visible on mobile** — good, this was missing before. ✓ **Avatar `bg-brand-mint text-brand-navy`** — mint (#A6DAD8) on navy (#012851) text: this combination is used for the initials avatar, which has `bg-brand-mint` background and `text-brand-navy` text. #A6DAD8 bg with #012851 text: contrast ≈ 6.7:1. Excellent. ✓
Author
Owner

🚀 Tobias Wendt — DevOps & Platform Engineer

Verdict: Approved

No infrastructure, CI workflow, Docker Compose, or deployment configuration changes in this PR. Reviewing from a platform perspective: nothing to block.

What I Checked

  • No new npm dependenciespackage.json not modified. Supply chain surface unchanged.
  • E2E test file in frontend/e2e/ — standard location matching existing Playwright setup. Tests use the same @playwright/test import pattern as other e2e specs. ✓
  • No new environment variables or secrets introduced.
  • No changes to docker-compose.yml, CI workflow YAML, or Caddy/proxy config.

Informational

The Playwright test hardcodes BRAND_NAVY = 'rgb(1, 40, 81)' as a magic string. If the CSS custom property --c-primary changes value, the test fails with a cryptic color-mismatch message. Not a blocker, but a comment or a named constant with the hex value alongside would help future maintainers understand what they're looking at:

// #012851 — brand-navy, defined as --c-primary in layout.css
const BRAND_NAVY = 'rgb(1, 40, 81)';

That's the only thing I'd note. Clean frontend-only PR from a platform standpoint.

## 🚀 Tobias Wendt — DevOps & Platform Engineer **Verdict: ✅ Approved** No infrastructure, CI workflow, Docker Compose, or deployment configuration changes in this PR. Reviewing from a platform perspective: nothing to block. ### What I Checked - **No new npm dependencies** — `package.json` not modified. Supply chain surface unchanged. - **E2E test file in `frontend/e2e/`** — standard location matching existing Playwright setup. Tests use the same `@playwright/test` import pattern as other e2e specs. ✓ - **No new environment variables or secrets introduced.** - **No changes to `docker-compose.yml`, CI workflow YAML, or Caddy/proxy config.** ### Informational The Playwright test hardcodes `BRAND_NAVY = 'rgb(1, 40, 81)'` as a magic string. If the CSS custom property `--c-primary` changes value, the test fails with a cryptic color-mismatch message. Not a blocker, but a comment or a named constant with the hex value alongside would help future maintainers understand what they're looking at: ```typescript // #012851 — brand-navy, defined as --c-primary in layout.css const BRAND_NAVY = 'rgb(1, 40, 81)'; ``` That's the only thing I'd note. Clean frontend-only PR from a platform standpoint.
marcel added 2 commits 2026-03-31 10:39:39 +02:00
- AuthHeader: remove duplicated locale logic, use <LanguageSwitcher inverted />
- Fix text-white/55 → text-white/70 in AppNav and LanguageSwitcher (WCAG AA)
- E2E: add axe accessibility checks, replace [data-hydrated] with role selectors,
  add 768px hamburger test and BRAND_NAVY comment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test(LanguageSwitcher): add Vitest unit tests for inverted prop
Some checks failed
CI / E2E Tests (pull_request) Failing after 1h49m47s
CI / Unit & Component Tests (push) Failing after 1m30s
CI / E2E Tests (push) Failing after 1h52m7s
CI / Backend Unit Tests (push) Failing after 2m28s
CI / Unit & Component Tests (pull_request) Failing after 3m27s
CI / Backend Unit Tests (pull_request) Failing after 2m38s
2dd73cf594
Covers active/inactive class tokens for both inverted=true and inverted=false,
and verifies setLocale is called with the correct locale on button click.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel merged commit 2dd73cf594 into main 2026-03-31 10:41:41 +02:00
marcel deleted branch feat/issue-163-brand-navy-header 2026-03-31 10:41:44 +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: marcel/familienarchiv#165