# Frontend UI: App Shell, Routing, Auth/RBAC, Overview Launcher, Explorer, Search, Date Range Filter, Recent Search History, Navigation Polish, Profile Password Change, User Management, Role Refinement, Must-Change-Password Gate, and User Management UX Hardening (RHL-019 / RHL-020 / RHL-021 / RHL-022 / RHL-023 / RHL-024 / RHL-025 / RHL-037 / RHL-032 / RHL-009 / RHL-012 / RHL-041 / RHL-044 / RHL-042 / RHL-043 / RHL-046)

This document describes the **frontend routing scaffold**, the **application shell layout**, and the core UI modules for the RHL Lieferscheine app.

Timeline:

- **RHL-019**: Public vs protected route scaffold + AppShell + placeholder pages.
- **RHL-020**: Real login flow + session handling and redirects.
- **RHL-021**: UI-side RBAC guard + consistent Forbidden / NotFound UX.
- **RHL-022**: Explorer v2 (Year → Month → Day → Files) + shadcn Breadcrumbs with dropdowns.
- **RHL-023**: Explorer file action “Open PDF” using the binary PDF endpoint (opens in a new tab).
- **RHL-024**: Search UI v1 (URL-driven `q`, scopes, cursor pagination, open PDF + jump to day).
- **RHL-025**: Search date range filter (from/to) with a calendar popover, presets, local validation, and URL sync.
- **RHL-042**: Search history dropdown (recent queries, per-user localStorage, LRU behavior, and first-page-only restore links).
- **RHL-037**: Search scope UX improvements (TopNav deep-link branch switching + Single combobox + Multi selection UX + deterministic URL state).
- **RHL-032**: Navigation UX polish (TopNav branding, theme toggle, user menu, tooltips, session indicator without content flicker, favicon, active states, and debounced loading UI).
- **RHL-009**: Profile password change UI (Change Password form + toasts).
- **RHL-041**: Role refinement (`superadmin`) + capability separation (branch access vs user management).
- **RHL-012**: User management UI (list/create/update/delete users) + guarded admin endpoints.
- **RHL-044**: Must-change-password enforcement (central protected-route gate, `/profile` redirect, and post-change resume flow).
- **RHL-043**: User management UX hardening + layout cleanup:
  - safer delete flow (typed username confirmation),
  - branch input hardening (`NL` + numeric draft) + branch existence validation with fail-open note,
  - loaded-count + users sorting toolbar,
  - temporary-password controls (reset/show/copy + tooltips) in table and edit dialog,
  - sticky icon-only action column polish,
  - AppShell cleanup (remove sidebar placeholder, desktop width to `75%`).
- **RHL-046**: Overview home cards (`/`) + role-based launcher card + shared profile support card + image loading performance update (`/overview-cards/v2` WebP, eager loading for above-the-fold cards, long-lived immutable cache headers).

> **Language policy**
>
> - Developer coordination can be German.
> - **Code, comments, tests, and documentation are English.**
> - **All user-facing UI strings are German** (labels, button text, alerts, hints).

---

## 1. Scope

### 1.1 Implemented (as of RHL-012 + RHL-041 + RHL-037 + RHL-025 + RHL-042 + RHL-032 + RHL-009 + RHL-044 + RHL-043 + RHL-046)

- **Public** `/login` route with a functional login form (shadcn/ui primitives).

- **Protected** application shell for all authenticated routes:
  - Top navigation (branding, quick navigation, actions)
  - Centered main content container
  - Main content area

  Width policy:

  - mobile/tablet: full width
  - desktop (`lg+`): centered `w-3/4` content for a wider but bounded workspace

- **Protected overview launcher page** (`/`, RHL-046):
  - Replaces the old placeholder entry page.
  - Keeps the page header `Übersicht`.
  - Renders launcher cards:
    - always: `Explorer`, `Suche`, `Profil`
    - only for `canManageUsers(role)`: `Benutzerverwaltung`
  - Card targets:
    - Explorer -> `/:branch`
    - Suche -> `/:branch/search`
    - Profil -> `/profile`
    - Benutzerverwaltung -> `/admin/users`
  - Branch target resolution uses the dedicated overview logic:
    - branch role: own `user.branchId`
    - admin-like: route context / last branch (`rhl_last_branch`) / fallback chain
  - Fail-safe behavior:
    - if no valid branch target can be determined, branch-dependent cards are shown disabled with a German hint.

- **Session guard** for protected routes:
  - Session identity is checked via `GET /api/auth/me`.
  - When unauthenticated: redirect to `/login?reason=expired&next=<original-url>`.

- **In-shell auth gating (UX improvement)**:
  - Auth loading/error/redirect states render **inside** the AppShell main content.
  - AppShell (TopNav + main content frame) remains stable; no “blank spinner screens”.

- **Logout**:
  - Logout action calls `GET /api/auth/logout` and redirects to `/login?reason=logged-out`.

- **User dropdown menu (RHL-032)**:
  - Profil / (optional) Benutzerverwaltung / Support / Abmelden

- **Profile password change (RHL-009)**:
  - Route: `/profile` (protected)
  - Profile shows read-only account/session metadata (role, branch, email).
  - Password change form calls `apiClient.changePassword({ currentPassword, newPassword })`.
  - Uses inline validation for actionable form errors.
  - Uses toast notifications (Sonner) for success/error feedback.
  - Profile includes an additional support card with a direct `mailto:` support action (RHL-046 follow-up).

- **Must-change-password enforcement (RHL-044)**:
  - Session identity (`GET /api/auth/me`) includes `mustChangePassword`.
  - `AuthGate` centrally enforces:
    - if `mustChangePassword=true`, redirect any protected non-profile route to `/profile`.
    - keep users on `/profile` until password is changed.
  - Redirect state is preserved with:
    - `/profile?mustChangePasswordGate=1&next=<safe-internal-url>`
  - After successful password change:
    - backend clears `mustChangePassword` and refreshes the session cookie.
    - frontend revalidates auth state and automatically resumes `next` when available.
  - `/profile` shows:
    - required-action alert (red) while `mustChangePassword=true`
    - success alert (green) after successful password change once the gate is lifted.

- **Global toast notifications (Sonner)**:
  - Sonner Toaster is mounted once in the root layout.
  - UI code uses a small wrapper (`lib/frontend/ui/toast.js`) to keep copy and behavior consistent.

- **Role model (RHL-041)**:
  - Backend roles: `branch | admin | superadmin | dev`.
  - UI treats **branch access** and **user management** as separate capabilities.
  - UI introduces a pure role helper:
    - `lib/frontend/auth/roles.js`
      - `isAdminLike(role)` → `admin | superadmin | dev`
      - `canManageUsers(role)` → `superadmin | dev`

- **User management UI (RHL-012 + RHL-043 hardening)**:
  - Route: `/admin/users` (protected)
  - Permission gating:
    - allowed: `superadmin`, `dev`
    - forbidden: `admin`, `branch`

  - Features:
    - list users with filters (username/email search, role, branchId)
    - loaded-count indicator (`X Benutzer geladen`) in the users toolbar
    - sort toolbar (`Standard`, `Rolle (Rechte)`, `Niederlassung (NL)`)
    - create user dialog (initial password + must-change-password flag on create)
    - create/edit branch input hardening (`NL` prefix + numeric input draft)
    - create/edit branch existence checks against `/api/branches` with fail-open note
    - update user (role/branchId consistency enforced)
    - delete user with typed-username confirmation gate
    - temporary password controls (reset, reveal/hide, copy) in table + edit dialog
    - sticky right action column with icon-only actions (edit/delete)
    - cursor-based pagination (“Mehr laden”), consistent with selected sort mode

- **UI RBAC (branch-level)**:
  - `BranchGuard` prevents branch users from accessing other branches’ URLs.
  - Admin-like users (`admin/superadmin/dev`) can access multiple branches.
  - Admin-like branch existence validation uses `GET /api/branches`.
  - Fail-open policy on validation failures (do not lock the UI on temporary API errors).

- **Route param validation** (syntactic):
  - `branch`: `NL` + digits (syntactic validity; existence is validated by BranchGuard for admin-like)
  - `year`: `YYYY`
  - `month`: `01–12`
  - `day`: `01–31`
  - Invalid params trigger `notFound()` early in layouts.

- **Explorer v2** (Branch → Year → Month → Day → Files):
  - `/:branch` → years
  - `/:branch/:year` → months
  - `/:branch/:year/:month` → days
  - `/:branch/:year/:month/:day` → files

- **Breadcrumb navigation**:
  - shadcn/ui `Breadcrumb` + dropdowns for year/month/day when options are available.
  - Dropdown options are derived from real API results (only show segments that exist).

- **Consistent states** across Explorer levels:
  - Loading states (Skeleton)
  - Empty states
  - Error states with retry
  - `FS_NOT_FOUND` mapped to an Explorer “path no longer exists” card

- **Explorer leaf action: Open PDF (RHL-023)**
  - The file list on `/:branch/:year/:month/:day` provides an **“Öffnen”** action.
  - Clicking “Öffnen” opens the selected PDF **in a new browser tab**.
  - URL construction is centralized in `lib/frontend/explorer/pdfUrl.js`.

- **Search UI (RHL-024) + Scope UX improvements (RHL-037)**
  - Route: `/:branch/search` (protected).
  - URL-driven state for shareability: `q`, `scope`, `branches`, `limit`, `from`, `to`.
  - Cursor-based pagination (`nextCursor`) is **not stored in the URL**.

- **Search date range filter (RHL-025)**
  - A calendar popover allows selecting `from` and `to`.
  - Presets (“Heute”, “Letzte 7 Tage”, …) set common ranges quickly.
  - The active filter is shown as a compact chip and can be cleared.
  - The filter is validated locally (fast feedback) and is also validated by the backend.

- **Search history (recent queries, RHL-042)**
  - A recent-search dropdown is available from the query box clock button.
  - The clock trigger is icon-only, keyboard accessible, and has a tooltip.
  - Entries are stored in browser `localStorage` per authenticated user:
    - key: `rhl.searchHistory.v1.<userId>`
    - max items: `10` (LRU: reused query moves to top)
  - Stored entry fields:
    - `routeBranch`, `q`, `scope`, `branches`, `limit`, `from`, `to`, `createdAt`
    - `cursor` is intentionally never stored.
  - Write trigger policy:
    - entries are stored only when first-page search loading succeeds.
  - Selection behavior:
    - selecting a recent item navigates to a canonical first-page search URL
      (including route branch switching when needed, e.g. `/NL32/search` -> `/NL17/search`).
  - Clear behavior:
    - users can clear all recent-search entries from the dropdown (`Verlauf löschen`).

- **TopNav / navigation polish (RHL-032)**
  - Solid header (no blur/transparency) for crisp borders.
  - Branding logo (light + dark assets).
  - Icon-only theme toggle.
  - User dropdown menu (Profil / Benutzerverwaltung\* / Support / Abmelden).
  - Consistent tooltips across navigation.
  - Session check indicator in TopNav (debounced) to avoid content flicker.
  - Clear active states for Explorer and Search.
  - Safe handling of invalid branch routes (admin-like): warning + one-click recovery.

  Implementation note:
  - For small static brand assets (logos), Next Image optimization is disabled (`unoptimized`) to avoid browser-specific “pending” indicators caused by aborted `/_next/image` optimization requests.

  - The “Benutzerverwaltung” entry is only shown when the user has the user-management capability.

- **Overview image performance update (RHL-046 follow-up)**:
  - Overview card assets are served from versioned static files:
    - `/overview-cards/v2/explorer.webp`
    - `/overview-cards/v2/search.webp`
    - `/overview-cards/v2/profile.webp`
    - `/overview-cards/v2/users.webp`
  - Above-the-fold overview cards use eager image loading.
  - Static asset caching for `/overview-cards/v2/*` is explicitly configured as:
    - `Cache-Control: public, max-age=31536000, immutable`

---

### 1.2 Still out of scope / planned

- Email-based password reset/recovery:
  - Planned as a separate follow-up ticket/phase.
  - Rationale: higher security surface (token handling and non-leakage), external SMTP/IT dependencies, and separate rate limiting ticket (RHL-031) to avoid scope creep.

- Optional Search UX improvements:
  - grouping results by date and/or branch
  - debounced “typeahead” search (current v1 is explicit submit)
  - optional **date-only search mode** (allow searches with `from/to` even when `q` is empty) if desired later

- Optional Explorer improvements:
  - add a dedicated “Herunterladen” UI action (download variant)
  - optional in-app PDF viewer experience (instead of a new tab)

- Optional user management enhancements (not implemented in RHL-043):
  - additional branch list filtering in branch pickers (create/edit)
  - one-click “copy username” helper

---

## 2. Route Groups & URL Structure

The app uses Next.js App Router **Route Groups** to separate public and protected UI.

### 2.1 Route groups

- **Public**: `app/(public)`
  - Routes that do **not** show the authenticated AppShell.
  - Current route: `/login`

- **Protected**: `app/(protected)`
  - Routes that render inside the AppShell.
  - Protected routes are guarded by:
    1. Session check (AuthProvider)
    2. Must-change-password gate (`AuthGate`, RHL-044)
    3. UI RBAC check for branch routes (BranchGuard)

### 2.2 URL map

| URL                          | Purpose                     | Notes                                                      |
| ---------------------------- | --------------------------- | ---------------------------------------------------------- |
| `/login`                     | Login page                  | Supports `reason` and `next` query params                  |
| `/`                          | Overview launcher           | Protected landing page with role-based cards (RHL-046)     |
| `/profile`                   | Profile                     | Password change + support card (RHL-009 / RHL-046)         |
| `/admin/users`               | Benutzerverwaltung          | Only `superadmin`/`dev` (RHL-012)                          |
| `/:branch`                   | Explorer: years             | Example: `/NL01`                                           |
| `/:branch/:year`             | Explorer: months            | Example: `/NL01/2025`                                      |
| `/:branch/:year/:month`      | Explorer: days              | Example: `/NL01/2025/12`                                   |
| `/:branch/:year/:month/:day` | Explorer: files             | Example: `/NL01/2025/12/31`                                |
| `/:branch/search`            | Search UI (v1)              | Explicit segment so `search` is not interpreted as `:year` |
| `/forbidden`                 | Forbidden page wrapper      | Optional wrapper; Forbidden is typically rendered inline   |

Important:

- There is **no** standalone `/search` route. Visiting `/search` matches `/:branch` with `branch = "search"`.

### 2.3 Protected entry page (`/`) overview implementation (RHL-046)

Primary files:

- `app/(protected)/page.jsx`
- `components/overview/OverviewHomePage.jsx`
- `components/overview/OverviewCard.jsx`
- `components/overview/layoutClasses.js`
- `lib/frontend/overview/useOverviewBranchTarget.js`
- `lib/frontend/overview/homeBranchTarget.js`
- `lib/frontend/overview/cardsConfig.js`

Behavior summary:

- `OverviewHomePage` builds card data and renders card UI with role-based visibility.
- Branch target resolution for Explorer/Search is encapsulated in the overview branch hook/helper flow.
- Card content (title/description/image/target) is built through `buildOverviewCards(...)`.

---

## 3. Layouts

### 3.1 Root layout

File: `app/layout.jsx`

Responsibilities:

- Global CSS imports (`app/globals.css`).
- Theme provider setup (shadcn/ui + next-themes wrapper).
- Base HTML/body structure.
- Mount global UI providers:
  - theme provider
  - Sonner Toaster (global notifications)

### 3.2 Public layout

File: `app/(public)/layout.jsx`

Responsibilities:

- Minimal centered layout for public routes.
- Intended for `/login` (and potential future public routes).

### 3.3 Protected layout

File: `app/(protected)/layout.jsx`

Responsibilities:

- Wrap all protected pages with:
  - `AuthProvider` (session check + redirect)
  - `AppShell` (stable frame)
  - `AuthGate` (renders auth loading/error/redirect UI inside the shell)

UX rationale:

- Keep the AppShell frame visible while auth/session checks run.
- Avoid full-screen “blank spinners” on slow connections.

---

## 4. App Shell & Top Navigation

### 4.1 AppShell framing

File: `components/app-shell/AppShell.jsx`

AppShell is the stable frame for all protected pages:

- TopNav is always visible.
- Route pages render inside the main content area (no sidebar rail).

#### 4.1.1 Width policy

Current strategy (implemented in `AppShell.jsx` + `TopNav.jsx`):

- keep content full-width on smaller screens,
- switch to a centered `lg:w-3/4` container on desktop for both:
  - TopNav inner row,
  - protected page content area.

This keeps header and content visually aligned and avoids horizontal “jump” between navigation and page body.

### 4.2 TopNav structure (RHL-032)

File: `components/app-shell/TopNav.jsx`

TopNav is a sticky header and is the primary navigation surface.

Layout groups (left → right):

- **Brand**: logo link to `/` (light + dark variants).
- **Primary navigation**: QuickNav (branch selector + Explorer/Search buttons).
- **Actions**: theme toggle + session indicator.
- **User menu**: dropdown trigger (Profil / Benutzerverwaltung / Support / Abmelden).

Design note:

- The header is intentionally rendered as a **solid** background (no backdrop blur) to keep borders/buttons crisp.

### 4.3 Branding (logo assets)

Asset convention:

- Store brand assets under `public/brand/`.
- Use two assets when needed (light/dark) and toggle them via Tailwind `dark:` classes.

Implementation note:

- For small static logo assets, Next Image optimization is disabled (`unoptimized`) to avoid repeated aborted `/_next/image` requests on some browsers.

### 4.4 Theme toggle

File: `components/app-shell/ThemeToggleButton.jsx`

- Icon-only toggle (sun/moon) using `next-themes`.
- Preference respects system theme by default.

### 4.5 Session indicator (no content flicker)

File: `components/app-shell/SessionIndicator.jsx`

Goal:

- Avoid “session checking” cards flashing in the main content area.
- Surface transient checks as a small indicator in the TopNav.

Policy:

- The indicator is **debounced** to avoid flicker on fast connections.
- If the session check completes before the delay, nothing is shown.

### 4.6 Tooltips policy (RHL-032)

- Use shadcn/Radix tooltips for TopNav actions.
- Prefer tooltips over native `title` attributes to avoid double tooltips.
- A single `TooltipProvider` is mounted at TopNav scope so all triggers share the same delay configuration.

### 4.7 User menu

File: `components/app-shell/UserStatus.jsx`

Shared support mailto builder:

- `lib/frontend/support/supportMailto.js`

Menu items (German):

- **Profil** → `/profile` (account info + password change)
- **Benutzerverwaltung** → `/admin/users` (only `superadmin` / `dev`)
- **Support** → opens a `mailto:` link to support.
- **Abmelden** → calls logout and redirects to login.

Role label mapping:

- `branch` → “Niederlassung”
- `admin` → “Admin”
- `superadmin` → “Superadmin”
- `dev` → “Entwicklung”

---

## 5. Quick navigation (quality-of-life)

File: `components/app-shell/QuickNav.jsx`

Purpose:

- Provide direct links to:
  - Explorer (`/:branch`)
  - Search (`/:branch/search`)

- For admin-like users: enable quick branch switching while preserving the current “context”.

Behavior:

- Branch users:
  - QuickNav is effectively fixed to their `branchId`.

- Admin-like users (`admin/superadmin/dev`):
  - Loads branches via `GET /api/branches`.
  - Stores the last selected branch in `localStorage` (`rhl_last_branch`) for convenience.
  - Keeps `selectedBranch` stable and avoids update loops (guarded initialization).

Branch switching rule (RHL-037):

- Selecting a branch navigates to the same section while replacing the first path segment.
  - Explorer deep paths preserved:
    - `/NL32/2025/12/31` → `/NL20/2025/12/31`

  - Search route preserved and shareable params preserved:
    - `/NL32/search?q=x&scope=multi&branches=NL06,NL20&limit=200&from=...&to=...`
      → `/NL20/search?q=x&scope=multi&branches=NL06,NL20&limit=200&from=...&to=...`

Implementation notes:

- Deep-path branch switching logic is centralized in `lib/frontend/quickNav/branchSwitch.js`.
- Avoid using `useSearchParams()` inside QuickNav for “current query string” access.
  - Use `window.location.search` at click-time instead (client-only).

Invalid branch routes (admin-like) (RHL-032):

- If the user manually navigates to a syntactically valid but non-existent branch (e.g. `/NL200`):
  - QuickNav shows a warning indicator.
  - The dropdown shows a warning explanation.
  - A one-click recovery item is available: “Zur letzten gültigen Niederlassung”.

---

## 6. Authentication UX (RHL-020)

### 6.1 Session check for protected routes

File: `components/auth/AuthProvider.jsx`

Behavior:

1. On mount, call `apiClient.getMe()`.

2. If `{ user: { ... } }`:
   - set auth state to `authenticated`
   - render protected UI

3. If `{ user: null }`:
   - redirect to `/login?reason=expired&next=<current-url>`

Notes:

- `GET /api/auth/me` returns minimal identity data for the UI:
  - `userId`, `role`, `branchId`, `email`, `mustChangePassword`.

- Role is one of: `branch | admin | superadmin | dev`.

### 6.2 AuthGate (in-shell gating)

File: `components/auth/AuthGate.jsx`

Behavior:

- While session is loading: render a minimal in-shell state.
- On auth errors: show an in-shell error card + retry.
- On unauthenticated: show a short “redirecting” message while redirect happens.
- On authenticated + `mustChangePassword=true`:
  - redirect to `/profile` unless already on `/profile`.
  - preserve a safe resume target via `next`.
- On authenticated + `mustChangePassword=false` and gate marker present:
  - resume to `next` automatically (if present and safe).

RHL-032 note:

- Prefer surfacing transient “session checking” feedback in the TopNav (SessionIndicator) to avoid content flicker.

### 6.3 Login page (reason / next)

Files:

- `app/(public)/login/page.jsx` (Server Component)
- `components/auth/LoginForm.jsx` (Client Component)

Flow:

1. Login page parses query params using `parseLoginParams(...)`.

2. If `reason` is present:
   - `expired` → show “session expired” banner (German)
   - `logged-out` → show “logged out” banner (German)

3. On submit, the form calls `apiClient.login({ username, password })`.

4. On success:
   - redirect to `next` if present
   - otherwise redirect to `/`

5. On failure:
   - show a safe, user-friendly error message (German)

Username policy:

- Backend stores usernames in lowercase and performs normalization during login.
- UI enforces the same policy:
  - username input is normalized to lowercase
  - `autoCapitalize="none"` to prevent mobile auto-caps

---

## 7. UI RBAC, Forbidden, and NotFound (RHL-021 / RHL-041)

### 7.1 Goals

UI-side RBAC exists for UX (backend RBAC remains authoritative):

- Branch users must not access other branches’ URLs.
- Admin-like users may access any existing branch.
- Invalid route parameters (year/month/day) should surface as NotFound.

### 7.2 BranchGuard (UI-side RBAC)

Files:

- `components/auth/BranchGuard.jsx`

Pure logic:

- `lib/frontend/rbac/branchAccess.js`
- `lib/frontend/rbac/branchUiDecision.js`

Responsibilities:

- Read `user` and `status` from AuthContext.
- Enforce branch rules:
  - role `branch` → allowed only when `:branch === user.branchId`
  - admin-like (`admin/superadmin/dev`) → allowed for any branch that exists

Admin-like branch existence validation:

- `BranchGuard` fetches `GET /api/branches` and verifies the route branch exists.
- Fail-open policy:
  - If fetching the list fails, do not block rendering.
  - Backend RBAC and subsequent API calls remain authoritative.

### 7.3 Param validation (year/month/day)

Files:

- `lib/frontend/params.js`

Layout enforcement (server-side `notFound()`):

- `app/(protected)/[branch]/layout.jsx` (branch syntax)
- `app/(protected)/[branch]/[year]/layout.jsx`
- `app/(protected)/[branch]/[year]/[month]/layout.jsx`
- `app/(protected)/[branch]/[year]/[month]/[day]/layout.jsx`

---

## 8. Explorer v2 (RHL-022) + PDF Open (RHL-023)

### 8.1 UI goal

Provide a simple “file explorer” drill-down:

- Year → Month → Day → Files

### 8.2 Explorer pages

Routes and components:

- `/:branch` → `components/explorer/levels/YearsExplorer.jsx`
- `/:branch/:year` → `components/explorer/levels/MonthsExplorer.jsx`
- `/:branch/:year/:month` → `components/explorer/levels/DaysExplorer.jsx`
- `/:branch/:year/:month/:day` → `components/explorer/levels/FilesExplorer.jsx`

### 8.3 Data fetching strategy

- All Explorer pages are **Client Components**.
- All JSON API calls go through `lib/frontend/apiClient.js`.
- A small hook provides consistent query state:
  - `lib/frontend/hooks/useExplorerQuery.js`

### 8.4 Breadcrumbs (with dropdowns)

Files:

- UI component:
  - `components/explorer/breadcrumbs/ExplorerBreadcrumbs.jsx`
  - `components/explorer/breadcrumbs/SegmentDropdown.jsx`

- Pure helpers:
  - `lib/frontend/explorer/breadcrumbDropdowns.js`
  - `lib/frontend/explorer/formatters.js` (German month labels)
  - `lib/frontend/explorer/sorters.js`

### 8.5 Files list (leaf route) and “Open PDF”

Leaf route:

- `/:branch/:year/:month/:day`

Files list behavior:

- Uses shadcn/ui `Table`.
- Shows:
  - file name
  - relative path (desktop column + mobile secondary line)

Primary file action:

- “Öffnen” opens the PDF in a **new browser tab** via the binary PDF endpoint:
  - `GET /api/files/:branch/:year/:month/:day/:filename`

Implementation notes:

- URL construction is centralized in:
  - `lib/frontend/explorer/pdfUrl.js`

---

## 9. Search UI (RHL-024 / RHL-037) + Date Range Filter (RHL-025) + Recent Search History (RHL-042)

### 9.1 Route and state model

Route:

- `/:branch/search`

Single Source of Truth rule (RHL-037):

- The **path segment** `/:branch/search` is the source of truth for the **current branch context**.
- Single-scope search uses the **route branch**.
- Shareable URLs for search do not need to carry `branch=` for Single.

URL-driven state policy:

- Search state is **URL-driven** to support shareable links.
- Cursor-based pagination state is **not shareable** and is kept in client state.

Admin-like scope availability:

- Admin-like users (`admin/superadmin/dev`) can switch between:
  - Single (route branch)
  - Multi (selected branches)
  - All (global)

Branch users:

- Are forced to Single on their own branch.

Shareable params (first page identity):

- `q` (string)

- Scope params:
  - **Single**: no `scope` param required; route branch defines the branch.
  - **Multi**: `scope=multi&branches=NL06,NL20` (deterministic)
  - **All**: `scope=all`

- `limit`:
  - allowed values: `50 | 100 | 200`
  - default: `100`

- `from/to` (RHL-025):
  - ISO date filter in `YYYY-MM-DD`
  - preserved in URLs and across branch switching

### 9.2 Recent search history (RHL-042)

Search history is a frontend-only convenience feature built on top of the URL-driven first-page state model.

Storage model:

- Per-user browser key:
  - `rhl.searchHistory.v1.<userId>`
- Max entries:
  - `10`
- Deduplication:
  - stable identity over `routeBranch`, `q`, `scope`, `branches`, `limit`, `from`, `to`
  - reusing an existing search moves it to the top (LRU)

Write trigger policy:

- Entries are persisted only when the first-page request has succeeded.
- Keystrokes/draft updates are never persisted.

Restore behavior:

- Restoring a recent item navigates to a canonical internal href under `/:branch/search`.
- The route branch is restored from the stored entry (`routeBranch`) when needed.
- The URL contains shareable first-page params only (`cursor` remains excluded).

UI behavior:

- Recent history is opened via the clock icon button in the query row.
- The trigger has a tooltip (“Letzte Suchen anzeigen”).
- The dropdown supports keyboard navigation (Command list).
- A global clear action is available: “Verlauf löschen”.

### 9.3 Debounced loading UI (RHL-032)

Goal:

- Avoid visible “skeleton flashes” for fast requests.

Approach:

- Loading UI is shown only after a small delay.
- This is implemented with `useDebouncedVisibility(...)` and centralized timing constants.

---

## 10. Toast notifications (Sonner)

The project uses Sonner (shadcn/ui integration) for toast notifications.

- The Toaster is mounted once in `app/layout.jsx` (root layout) and respects the current theme.
- UI code should use the wrapper in `lib/frontend/ui/toast.js`.

---

## 11. User management UI (RHL-012 + RHL-043)

### 11.1 Route

- `/admin/users` (protected)

### 11.2 Authorization

- UI gating uses `canManageUsers(role)` from `lib/frontend/auth/roles.js`.
- Backend is authoritative:
  - all user management APIs require `requireUserManagement(session)`.

### 11.3 UI components

Files:

- `components/admin/users/AdminUsersPage.jsx` (gating)
- `components/admin/users/AdminUsersClient.jsx` (list + filters + actions)
- `components/admin/users/AdminUsersTableToolbar.jsx` (loaded count + sort dropdown)
- `components/admin/users/UsersTable.jsx` (table)
- `components/admin/users/UserTemporaryPasswordField.jsx` (temporary password controls)
- `components/admin/users/DeleteUserDialog.jsx` (delete safety dialog)
- `components/admin/users/BranchNumberInput.jsx` (fixed `NL` prefix + numeric input)
- `components/admin/users/CreateUserDialog.jsx` + `components/admin/users/create-user/*` (create flow)
- `components/admin/users/EditUserDialog.jsx` + `components/admin/users/edit-user/*` (edit flow)

The UI uses the API client wrappers from `lib/frontend/apiClient.js`:

- `adminListUsers`
- `adminCreateUser`
- `adminUpdateUser`
- `adminDeleteUser`
- `adminResetUserPassword`

### 11.4 UX hardening rules (RHL-043)

- Delete flow requires explicit typed confirmation:
  - dialog shows the target user details,
  - user must type the username,
  - delete button stays disabled until the normalized value matches.

- Branch-role input hardening (`role=branch`) in create and edit:
  - fixed prefix `NL`,
  - numeric input draft for the branch number,
  - normalization output examples:
    - `1` -> `NL01`
    - `32` -> `NL32`
    - `200` -> `NL200`
  - typing stability: drafts like `01` remain visible while typing.

- Branch existence validation:
  - if `/api/branches` is available and selected branch does not exist:
    - inline error is shown,
    - submit is blocked.
  - if `/api/branches` cannot be loaded:
    - submit is not hard-blocked (fail-open),
    - a small informational note is shown.

### 11.5 Table UX details

- Users toolbar (above table):
  - loaded count badge (`X Benutzer geladen`)
  - sorting dropdown:
    - `Standard`
    - `Rolle (Rechte)`
    - `Niederlassung (NL)`

- Table layout:
  - fixed/sticky right `Aktion` column for edit/delete actions,
  - icon-only edit/delete actions to reduce horizontal space.

- Temporary password column:
  - masked display by default (`••••••`),
  - controls:
    - reset temporary password,
    - reveal/hide,
    - copy to clipboard,
  - copy feedback:
    - success checkmark state after copy,
    - fallback copy path for browsers/environments where `navigator.clipboard` is not usable.

- Tooltips:
  - temporary-password control buttons use tooltips for discoverability and consistent icon-only UX.

### 11.6 Sorting and pagination behavior

- Sorting is applied server-side (`/api/admin/users?sort=...`) and remains stable across `Mehr laden`.
- Cursor payload carries sort context to prevent cross-mode cursor reuse.
- Frontend query state includes `sort` so changing sort resets the list cleanly.

---

## 12. Profile: Password change (RHL-009)

### 12.1 Route

- `/profile` (protected)

### 12.2 Components

- `components/profile/ProfilePage.jsx`
  - Shows read-only account/session info (role, branch, email).
  - Shows required-action alert when `mustChangePassword=true`.
  - Shows success alert after a successful forced password change.
  - Includes a support card with `Support kontaktieren` action (RHL-046 follow-up).

- `components/profile/ChangePasswordCard.jsx`
  - Calls `apiClient.changePassword({ currentPassword, newPassword })`.
  - Maps password policy errors into user-friendly German hints.
  - Triggers auth revalidation after success (`retry()` from AuthContext).

- `lib/frontend/support/supportMailto.js`
  - Centralized builder for support `mailto:` links.
  - Shared by TopNav user menu and profile support card to keep subject/body/context consistent.

---

## 13. UI primitives (shadcn/ui)

The Explorer + auth + search UI uses shadcn/ui primitives from `components/ui/*`.

---

## 14. File Naming Convention (.js vs .jsx)

To keep the project consistent:

- Use **`.jsx`** for files that contain JSX.
- Use **`.js`** for non-JSX files.

---

## 15. Tests

### 15.1 Unit tests

Role helper tests (RHL-041):

- `lib/frontend/auth/roles.test.js`

Overview + support utility tests (RHL-046):

- `lib/frontend/overview/homeBranchTarget.test.js`
- `lib/frontend/overview/cardsConfig.test.js`
- `lib/frontend/support/supportMailto.test.js`

Existing tests remain as documented in the project.

### 15.2 Running tests

From the repo root:

```bash
npx vitest run
```

Optional build check:

```bash
npm run build
```

---

## 16. Manual verification checklist

### 16.1 Local (Docker)

- Login and verify TopNav role label per account.
- Verify overview launcher (`/`):
  - branch/admin users see 3 cards (`Explorer`, `Suche`, `Profil`)
  - `dev/superadmin` see the additional `Benutzerverwaltung` card
  - Explorer/Search routes resolve to the expected branch target
- Branch switching for admin-like users.
- Explorer drill-down.
- Search scopes (admin-like) + Search restrictions (branch).
- Search history:
  - execute several searches with different scopes/date filters/limits
  - open recent-search dropdown via clock button
  - restore recent entries across same and different branches
  - clear history via `Verlauf löschen`
- Open PDFs.
- Verify profile support card:
  - support card is visible below password section
  - `Support kontaktieren` opens a prefilled support `mailto:` link
- Verify overview image performance:
  - assets are loaded from `/overview-cards/v2/*.webp`
  - response headers include `Cache-Control: public, max-age=31536000, immutable`
- User management UI (superadmin/dev):
  - list/create/update/delete
  - delete typed confirmation
  - branch input (`NL` + number) and branch existence warning/blocking
  - temporary password reset/reveal/copy controls
  - sorting (`Standard`, `Rolle (Rechte)`, `Niederlassung (NL)`)
  - sticky action column behavior on horizontal overflow

### 16.2 Server

- Repeat the local checks on the real server.
- Specifically verify overview card image caching and second-load cache hits in browser DevTools.
- Validate RBAC behavior:
  - branch user → forbidden on foreign branches
  - admin-like → notfound on non-existent branches
  - admin/branch → forbidden on `/admin/users`
- Validate must-change-password behavior:
  - new user with `mustChangePassword=true` is forced to `/profile`
  - gate lifts immediately after password change (no logout/login required)
  - user resumes the previously blocked protected route when `next` is available

---

## 17. Planned follow-ups

- Optional user-management polish:
  - additional branch-list filtering in the create/edit branch picker UX
  - optional one-click username copy helper
- Optional overview perceived-performance polish:
  - add a lightweight image-area skeleton only if real-world perception still feels delayed after asset/caching optimization.
