feat: mobile-first nav redesign (Phase 10e-5) #25

Merged
forgejo_admin merged 2 commits from 24-mobile-first-nav-redesign-phase-10e-5 into main 2026-03-15 03:41:00 +00:00

Summary

Rewrites AuthStatus.svelte with a responsive mobile-first navigation. On phones (<768px), a fixed hamburger button opens a slide-out drawer with user info, role badge, full-width tappable nav links, and a sign out button. On desktop (>=768px), a clean horizontal nav bar renders instead. Also removes the duplicate "Manage Users" link from the admin page since it now lives in the global nav.

Changes

  • src/lib/components/AuthStatus.svelte — Full rewrite. Two separate <nav> elements (desktop + mobile) toggled via CSS media queries. Mobile drawer with backdrop overlay, animated hamburger-to-X icon, smooth 200ms slide transition. Nav items computed as a $derived array based on user roles. Uses CSS custom properties from design system exclusively. SSR-safe.
  • src/routes/admin/+page.svelte — Removed duplicate <div class="nav-links"> section and its associated CSS styles since "Manage Users" is now accessible from the global nav for admin users.

Test Plan

  • Desktop (>=768px): horizontal nav bar with links, user info, sign out button
  • Mobile (<768px): hamburger icon visible in top-right corner
  • Tap hamburger: drawer slides in from right with backdrop
  • Drawer shows user name + role badge at top
  • Admin sees all 6 nav items, coach sees 2, player sees 1 + Manage Account
  • Tapping a nav link closes the drawer and navigates
  • Tapping backdrop closes the drawer
  • Sign out button at bottom of drawer works
  • Unauthenticated: Sign In button only (no hamburger)
  • /admin page no longer has duplicate "Manage Users" link
  • npm run build passes

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #24
  • plan-pal-e-platform — Phase 10e-5 mobile nav redesign
## Summary Rewrites `AuthStatus.svelte` with a responsive mobile-first navigation. On phones (<768px), a fixed hamburger button opens a slide-out drawer with user info, role badge, full-width tappable nav links, and a sign out button. On desktop (>=768px), a clean horizontal nav bar renders instead. Also removes the duplicate "Manage Users" link from the admin page since it now lives in the global nav. ## Changes - `src/lib/components/AuthStatus.svelte` — Full rewrite. Two separate `<nav>` elements (desktop + mobile) toggled via CSS media queries. Mobile drawer with backdrop overlay, animated hamburger-to-X icon, smooth 200ms slide transition. Nav items computed as a `$derived` array based on user roles. Uses CSS custom properties from design system exclusively. SSR-safe. - `src/routes/admin/+page.svelte` — Removed duplicate `<div class="nav-links">` section and its associated CSS styles since "Manage Users" is now accessible from the global nav for admin users. ## Test Plan - [ ] Desktop (>=768px): horizontal nav bar with links, user info, sign out button - [ ] Mobile (<768px): hamburger icon visible in top-right corner - [ ] Tap hamburger: drawer slides in from right with backdrop - [ ] Drawer shows user name + role badge at top - [ ] Admin sees all 6 nav items, coach sees 2, player sees 1 + Manage Account - [ ] Tapping a nav link closes the drawer and navigates - [ ] Tapping backdrop closes the drawer - [ ] Sign out button at bottom of drawer works - [ ] Unauthenticated: Sign In button only (no hamburger) - [ ] `/admin` page no longer has duplicate "Manage Users" link - [ ] `npm run build` passes ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related - Closes #24 - `plan-pal-e-platform` — Phase 10e-5 mobile nav redesign
Rewrite AuthStatus.svelte with responsive navigation:
- Mobile (<768px): fixed hamburger button opens a slide-out drawer with
  user info, role badge, full-width nav links, and sign out button.
  Backdrop overlay closes drawer on tap. Drawer closes on link click.
- Desktop (>=768px): clean horizontal nav bar with properly spaced links.
- Role-based nav items (admin/coach/player) computed as derived array.
- Animated hamburger-to-X icon transition and smooth drawer slide (200ms).
- Uses CSS custom properties from design system (no hardcoded hex).
- SSR-safe: no window references.

Also removes duplicate "Manage Users" nav link from /admin/+page.svelte
since it is now in the global nav for admin users.

Closes #24

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove class:visible={drawerOpen} from backdrop button -- the element
  is already conditionally rendered with {#if drawerOpen}, and .visible
  class was never defined in styles.
- Change mobile nav aria-label from "Main navigation" to "Mobile
  navigation" to avoid duplicate landmark labels (a11y).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Self-Review Findings

Reviewed the diff (350 additions, 90 deletions across 2 files). Found 2 issues, both fixed in commit ec9fd5a.

Fixed

  1. Dead class:visible directive — The backdrop <button> had class:visible={drawerOpen} but the element is already conditionally rendered inside {#if drawerOpen}, and no .visible CSS class was ever defined. Removed the dead directive.

  2. Duplicate aria-label on nav landmarks — Both <nav> elements had aria-label="Main navigation", which creates duplicate landmark labels for screen readers. Changed mobile nav to aria-label="Mobile navigation".

Verified

  • No hardcoded hex values -- all colors use CSS custom properties from app.css
  • No window references -- SSR-safe
  • 44x44px hamburger touch target meets WCAG minimum
  • z-index layering correct: backdrop (999) < drawer (1000) < hamburger (1001)
  • Drawer transition uses transform (GPU-composited, no layout thrashing)
  • Role-based nav items match spec (admin: 6 items, coach: 3, player: 2)
  • npm run build passes after fixes
  • Admin page duplicate "Manage Users" link and orphaned styles properly removed

VERDICT: APPROVE

## Self-Review Findings Reviewed the diff (350 additions, 90 deletions across 2 files). Found 2 issues, both fixed in commit ec9fd5a. ### Fixed 1. **Dead `class:visible` directive** — The backdrop `<button>` had `class:visible={drawerOpen}` but the element is already conditionally rendered inside `{#if drawerOpen}`, and no `.visible` CSS class was ever defined. Removed the dead directive. 2. **Duplicate `aria-label` on nav landmarks** — Both `<nav>` elements had `aria-label="Main navigation"`, which creates duplicate landmark labels for screen readers. Changed mobile nav to `aria-label="Mobile navigation"`. ### Verified - No hardcoded hex values -- all colors use CSS custom properties from `app.css` - No `window` references -- SSR-safe - 44x44px hamburger touch target meets WCAG minimum - z-index layering correct: backdrop (999) < drawer (1000) < hamburger (1001) - Drawer transition uses `transform` (GPU-composited, no layout thrashing) - Role-based nav items match spec (admin: 6 items, coach: 3, player: 2) - `npm run build` passes after fixes - Admin page duplicate "Manage Users" link and orphaned styles properly removed **VERDICT: APPROVE**
Author
Owner

PR #25 Review

DOMAIN REVIEW

Tech stack: SvelteKit 5 (runes mode), CSS custom properties, Keycloak OIDC auth.

Accessibility (Good)

  • aria-label on both <nav> elements ("Main navigation", "Mobile navigation") -- solid.
  • Hamburger button has dynamic aria-label ("Close menu" / "Open menu") and aria-expanded binding.
  • Backdrop uses a <button> element (not a <div>) for keyboard accessibility, with aria-label="Close menu".
  • Drawer has role="dialog" with aria-label="Navigation menu".
  • External link icon uses aria-hidden="true".
  • Touch target: hamburger is 44x44px -- meets WCAG minimum.

Accessibility (Concerns)

  • The drawer role="dialog" lacks focus trapping. When the drawer opens, keyboard focus stays on the hamburger button. A keyboard user can Tab past the drawer into the page content behind the backdrop. This is a meaningful a11y gap for a dialog pattern, though not a blocker for a nav drawer in a small internal app.
  • No Escape key handler to close the drawer. Standard expectation for dialog/drawer patterns.
  • The tabindex="-1" on the backdrop means it is removed from tab order, which is correct, but the drawer links themselves are still tabbable when the drawer is closed (it is just visually hidden via transform). Screen readers and keyboard users can reach them in the closed state.

Responsive Design (Good)

  • Clean mobile-first approach: .desktop-nav is display: none by default, shown at >=768px. .mobile-nav is display: block by default, hidden at >=768px. No overlap.
  • Drawer constrained with max-width: 85vw -- prevents overflow on small screens (375px).
  • Previous breakpoint was 480px (stacking layout) -- new 768px breakpoint is a much better tablet/mobile split.

Component Architecture (Good)

  • navItems computed as a $derived array based on roles -- single source of truth for both desktop and mobile renders. Eliminates the DRY violation that existed before (nav links duplicated in template markup).
  • userName and primaryRole extracted as derived values -- clean.
  • SSR-safe: no window or document references, no onMount side effects.

CSS Quality (Good)

  • All colors use CSS custom properties from the design system (app.css). No hardcoded color values in the diff.
  • Well-structured sections with clear comment headers.
  • Transitions use consistent 200ms easing.
  • Removed the old @media (max-width: 480px) responsive block -- no dead CSS left behind.

Admin Page Cleanup (Good)

  • Removed the duplicate "Manage Users" link and its associated 26 lines of CSS from /admin. This link now lives in the global nav for admin users. Clean scope reduction.

BLOCKERS

1. Hardcoded Keycloak URL in client-side component

/home/ldraney/westside-app/src/lib/components/AuthStatus.svelte line 42:

href: 'https://keycloak.tail5b443a.ts.net/realms/westside-basketball/account/',

This is a hardcoded URL in a client-rendered component. However, this is a pre-existing pattern -- the same URL was hardcoded in the old code (visible in the diff's removed lines). The server-side module keycloak-admin.js also hardcodes it. This PR does not introduce the problem, it preserves it.

Verdict on this item: NOT a blocker for this PR. The hardcoded URL is pre-existing technical debt. Should be tracked as a separate issue (extract to environment variable or config).

No new blockers found. This PR does not introduce new functionality that requires test coverage -- it is a pure UI refactor of an existing component. The nav items are the same, the auth flow is unchanged. The repo has zero test files (no test infrastructure exists), so demanding tests for a CSS/layout refactor would be unreasonable. No unvalidated user input, no secrets, no DRY violations in auth paths.

NITS

  1. Focus trap missing on drawer dialog. The role="dialog" drawer does not trap focus. When opened, Tab can reach elements behind the backdrop. For a small internal app this is acceptable, but if the app grows in user base, adding a focus trap (e.g., svelte-focus-trap or a manual implementation) and an Escape key handler would bring it to WCAG 2.1 AA compliance. Consider filing a follow-up issue.

  2. Drawer links tabbable when closed. The drawer is hidden via transform: translateX(100%) but the links remain in the tab order. Adding inert attribute to the drawer when closed (or toggling visibility: hidden with a transition delay) would prevent keyboard users from accidentally tabbing into the off-screen drawer.

  3. Pre-existing: Keycloak URL should be an env var. The Manage Account href and all URLs in keycloak-admin.js should come from a shared config or environment variable. Not introduced by this PR, but worth a follow-up issue.

  4. plan-pal-e-platform reference in Related section. The PR body says plan-pal-e-platform -- Phase 10e-5. The user specified the plan is plan-2026-03-08-tryout-prep. The plan reference in the PR body appears to be pointing to the wrong plan. Minor documentation nit.

SOP COMPLIANCE

  • Branch named after issue: 24-mobile-first-nav-redesign-phase-10e-5 (references issue #24)
  • PR body has: Summary, Changes, Test Plan, Related
  • Related references correct plan slug: PR says plan-pal-e-platform but user specified plan-2026-03-08-tryout-prep
  • No secrets committed (Keycloak URL is a public endpoint, not a secret)
  • No unnecessary file changes (2 files, both directly related to nav redesign)
  • Commit messages are descriptive
  • Closes #24 present in PR body

PROCESS OBSERVATIONS

  • Change Failure Risk: LOW. This is a pure UI refactor. Same nav items, same auth flow, same role logic. The only behavioral change is the mobile drawer pattern replacing the stacking layout. No backend changes, no data model changes.
  • No test infrastructure exists in this repo (zero .test.js or .spec.js files). The Test Plan in the PR body is a manual checklist, which is appropriate for a frontend component refactor in a repo without automated test tooling. A follow-up to add Playwright or Vitest component tests would reduce change failure risk for future PRs.
  • DRY improvement: The old code had nav links duplicated in both the template markup AND the admin page. This PR consolidates to a single navItems array and removes the admin page duplicate. Net improvement.

VERDICT: APPROVED

Clean mobile-first refactor with good accessibility foundations, proper use of design system tokens, and a DRY improvement over the previous implementation. No blockers found. The nits (focus trap, drawer tabbability, plan slug mismatch) are non-blocking and can be addressed in follow-up work.

## PR #25 Review ### DOMAIN REVIEW **Tech stack:** SvelteKit 5 (runes mode), CSS custom properties, Keycloak OIDC auth. **Accessibility (Good)** - `aria-label` on both `<nav>` elements ("Main navigation", "Mobile navigation") -- solid. - Hamburger button has dynamic `aria-label` ("Close menu" / "Open menu") and `aria-expanded` binding. - Backdrop uses a `<button>` element (not a `<div>`) for keyboard accessibility, with `aria-label="Close menu"`. - Drawer has `role="dialog"` with `aria-label="Navigation menu"`. - External link icon uses `aria-hidden="true"`. - Touch target: hamburger is 44x44px -- meets WCAG minimum. **Accessibility (Concerns)** - The drawer `role="dialog"` lacks focus trapping. When the drawer opens, keyboard focus stays on the hamburger button. A keyboard user can Tab past the drawer into the page content behind the backdrop. This is a meaningful a11y gap for a dialog pattern, though not a blocker for a nav drawer in a small internal app. - No `Escape` key handler to close the drawer. Standard expectation for dialog/drawer patterns. - The `tabindex="-1"` on the backdrop means it is removed from tab order, which is correct, but the drawer links themselves are still tabbable when the drawer is closed (it is just visually hidden via `transform`). Screen readers and keyboard users can reach them in the closed state. **Responsive Design (Good)** - Clean mobile-first approach: `.desktop-nav` is `display: none` by default, shown at `>=768px`. `.mobile-nav` is `display: block` by default, hidden at `>=768px`. No overlap. - Drawer constrained with `max-width: 85vw` -- prevents overflow on small screens (375px). - Previous breakpoint was 480px (stacking layout) -- new 768px breakpoint is a much better tablet/mobile split. **Component Architecture (Good)** - `navItems` computed as a `$derived` array based on roles -- single source of truth for both desktop and mobile renders. Eliminates the DRY violation that existed before (nav links duplicated in template markup). - `userName` and `primaryRole` extracted as derived values -- clean. - SSR-safe: no `window` or `document` references, no `onMount` side effects. **CSS Quality (Good)** - All colors use CSS custom properties from the design system (`app.css`). No hardcoded color values in the diff. - Well-structured sections with clear comment headers. - Transitions use consistent 200ms easing. - Removed the old `@media (max-width: 480px)` responsive block -- no dead CSS left behind. **Admin Page Cleanup (Good)** - Removed the duplicate "Manage Users" link and its associated 26 lines of CSS from `/admin`. This link now lives in the global nav for admin users. Clean scope reduction. ### BLOCKERS **1. Hardcoded Keycloak URL in client-side component** `/home/ldraney/westside-app/src/lib/components/AuthStatus.svelte` line 42: ``` href: 'https://keycloak.tail5b443a.ts.net/realms/westside-basketball/account/', ``` This is a hardcoded URL in a client-rendered component. However, this is a **pre-existing pattern** -- the same URL was hardcoded in the old code (visible in the diff's removed lines). The server-side module `keycloak-admin.js` also hardcodes it. This PR does not introduce the problem, it preserves it. **Verdict on this item: NOT a blocker for this PR.** The hardcoded URL is pre-existing technical debt. Should be tracked as a separate issue (extract to environment variable or config). **No new blockers found.** This PR does not introduce new functionality that requires test coverage -- it is a pure UI refactor of an existing component. The nav items are the same, the auth flow is unchanged. The repo has zero test files (no test infrastructure exists), so demanding tests for a CSS/layout refactor would be unreasonable. No unvalidated user input, no secrets, no DRY violations in auth paths. ### NITS 1. **Focus trap missing on drawer dialog.** The `role="dialog"` drawer does not trap focus. When opened, Tab can reach elements behind the backdrop. For a small internal app this is acceptable, but if the app grows in user base, adding a focus trap (e.g., `svelte-focus-trap` or a manual implementation) and an `Escape` key handler would bring it to WCAG 2.1 AA compliance. Consider filing a follow-up issue. 2. **Drawer links tabbable when closed.** The drawer is hidden via `transform: translateX(100%)` but the links remain in the tab order. Adding `inert` attribute to the drawer when closed (or toggling `visibility: hidden` with a transition delay) would prevent keyboard users from accidentally tabbing into the off-screen drawer. 3. **Pre-existing: Keycloak URL should be an env var.** The `Manage Account` href and all URLs in `keycloak-admin.js` should come from a shared config or environment variable. Not introduced by this PR, but worth a follow-up issue. 4. **`plan-pal-e-platform` reference in Related section.** The PR body says `plan-pal-e-platform -- Phase 10e-5`. The user specified the plan is `plan-2026-03-08-tryout-prep`. The plan reference in the PR body appears to be pointing to the wrong plan. Minor documentation nit. ### SOP COMPLIANCE - [x] Branch named after issue: `24-mobile-first-nav-redesign-phase-10e-5` (references issue #24) - [x] PR body has: Summary, Changes, Test Plan, Related - [ ] Related references correct plan slug: PR says `plan-pal-e-platform` but user specified `plan-2026-03-08-tryout-prep` - [x] No secrets committed (Keycloak URL is a public endpoint, not a secret) - [x] No unnecessary file changes (2 files, both directly related to nav redesign) - [x] Commit messages are descriptive - [x] `Closes #24` present in PR body ### PROCESS OBSERVATIONS - **Change Failure Risk: LOW.** This is a pure UI refactor. Same nav items, same auth flow, same role logic. The only behavioral change is the mobile drawer pattern replacing the stacking layout. No backend changes, no data model changes. - **No test infrastructure exists** in this repo (zero `.test.js` or `.spec.js` files). The Test Plan in the PR body is a manual checklist, which is appropriate for a frontend component refactor in a repo without automated test tooling. A follow-up to add Playwright or Vitest component tests would reduce change failure risk for future PRs. - **DRY improvement:** The old code had nav links duplicated in both the template markup AND the admin page. This PR consolidates to a single `navItems` array and removes the admin page duplicate. Net improvement. ### VERDICT: APPROVED Clean mobile-first refactor with good accessibility foundations, proper use of design system tokens, and a DRY improvement over the previous implementation. No blockers found. The nits (focus trap, drawer tabbability, plan slug mismatch) are non-blocking and can be addressed in follow-up work.
forgejo_admin deleted branch 24-mobile-first-nav-redesign-phase-10e-5 2026-03-15 03:41:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/westside-landing!25
No description provided.