# Frontend UI: App Shell, Routing, Auth/RBAC, Explorer, Search, Date Range Filter, Navigation Polish, and Profile Password Change (RHL-019 / RHL-020 / RHL-021 / RHL-022 / RHL-023 / RHL-024 / RHL-025 / RHL-037 / RHL-032 / RHL-009) 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-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). > **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-037 + RHL-025 + RHL-032 + RHL-009) - **Public** `/login` route with a functional login form (shadcn/ui primitives). - **Protected** application shell for all authenticated routes: - Top navigation (branding, quick navigation, actions) - Sidebar placeholder area - Main content area - **Session guard** for protected routes: - Session identity is checked via `GET /api/auth/me`. - When unauthenticated: redirect to `/login?reason=expired&next=`. - **In-shell auth gating (UX improvement)**: - Auth loading/error/redirect states render **inside** the AppShell main content. - AppShell (TopNav + sidebar) 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)**: - Profile / Support / Logout - **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. - **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. - **UI RBAC (branch-level)**: - `BranchGuard` prevents branch users from accessing other branches’ URLs. - Admin/dev can access multiple branches. - Admin/dev 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/dev) - `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. - **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 (Profile / Support / Logout). - 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/dev): warning + one-click recovery. --- ### 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) --- ## 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. UI RBAC check for branch routes (BranchGuard) ### 2.2 URL map | URL | Purpose | Notes | | ---------------------------- | --------------------------- | ---------------------------------------------------------- | | `/login` | Login page | Supports `reason` and `next` query params | | `/` | Protected entry placeholder | Rendered only when authenticated | | `/profile` | Profile | Password change is implemented here (RHL-009) | | `/: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"`. --- ## 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. - The sidebar is currently a placeholder (reserved for future navigation/filter UI). - Route pages render inside the main content area. #### 4.1.1 Width policy and sidebar docking (2xl+) On very wide screens the UI should remain readable. Current strategy (implemented in `AppShell.jsx` + `TopNav.jsx`): - Use a centered content column (~45% of the viewport at `2xl+`). - Keep the Explorer/Search content inside that centered column. - Render the sidebar in the left gutter and align it to the right edge so it “docks” to the centered content **without consuming the centered width**. Below `2xl`: - Use full width for usability. - Hide the sidebar placeholder to avoid shrinking the main content area. ### 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 (Profile / Support / Logout). 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. ### 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` Menu items (German): - **Profil** → `/profile` (account info + password change) - **Support** → opens a `mailto:` link to `info@attus.de`. - **Abmelden** → calls logout and redirects to login. Support mailto guidelines: - Build the `mailto:` query string with `encodeURIComponent` (not `URLSearchParams`) to avoid “+” rendering issues in some mail clients. - Include basic context in the mail body: - current URL - route path - timestamp - user role/branch - user-agent --- ## 5. Quick navigation (quality-of-life) File: `components/app-shell/QuickNav.jsx` Purpose: - Provide direct links to: - Explorer (`/:branch`) - Search (`/:branch/search`) - For admin/dev: enable quick branch switching while preserving the current “context”. Behavior: - Branch users: - QuickNav is effectively fixed to their `branchId`. - Admin/dev users: - 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). - This avoids build-time issues for static/prerender contexts. Invalid branch routes (admin/dev) (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=` Notes: - `GET /api/auth/me` returns minimal identity data for the UI: - `userId`, `role`, `branchId`, and (optionally) `email`. The `next` parameter: - includes the original `pathname` and query string - is sanitized to avoid open redirects (only internal paths are allowed) ### 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. 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 this policy as well: - username input is normalized to lowercase - `autoCapitalize="none"` to prevent mobile auto-caps --- ## 7. UI RBAC, Forbidden, and NotFound (RHL-021) ### 7.1 Goals RHL-021 adds a friendly UI layer on top of backend RBAC: - Branch users must not access other branches’ URLs. - Admin/dev users may access any existing branch. - Invalid route parameters (year/month/day) should surface as NotFound. Backend RBAC remains the source of truth. UI RBAC exists to: - prevent “obviously forbidden” navigation in the frontend - provide clear and consistent UX for end users ### 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` - role `admin` / `dev` → allowed for any branch that exists Admin/dev 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) ### 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. 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 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. Files: - Timing constants: `lib/frontend/ui/uxTimings.js` - `LOADING_UI_DELAY_MS` - `SESSION_INDICATOR_DELAY_MS` - `SESSION_INDICATOR_MIN_VISIBLE_MS` - `TOOLTIP_DELAY_MS` - Debounce hook: `lib/frontend/hooks/useDebouncedVisibility.js` Applied to: - Explorer level loading skeletons - Search results loading skeletons - TopNav session indicator --- ## 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`. Wrapper functions: - `notifySuccess(...)`, `notifyError(...)`, `notifyInfo(...)`, `notifyWarning(...)`, `notifyLoading(...)` - `notifyApiError(err, ...)` for consistent mapping of `ApiClientError` to safe German copy Rationale: - Consistent UX across features. - Avoid scattering direct `toast.*` calls. --- ## 11. Profile: Password change (RHL-009) ### 11.1 Route - `/profile` (protected) ### 11.2 Components - `components/profile/ProfilePage.jsx` - Renders: - Account/session info card (role, branch, read-only email) - Password change card Note: - The email address is displayed read-only. - Email changes are not supported in the UI; email is managed centrally (IT / developers). - `components/profile/ChangePasswordCard.jsx` - Contains a password change form: - `currentPassword` - `newPassword` - `confirmNewPassword` - Uses inline validation for: - required fields - confirmation mismatch - new password equals current password - Calls `apiClient.changePassword({ currentPassword, newPassword })`. - Uses Sonner toasts for success/error feedback. ### 11.3 Password policy UX The backend enforces an explicit password policy (see `docs/auth.md`). The frontend displays policy hints and maps `VALIDATION_WEAK_PASSWORD` details to user-friendly German hints. Helper module: - `lib/frontend/profile/passwordPolicyUi.js` --- ## 12. UI primitives (shadcn/ui) The Explorer + auth + search UI uses shadcn/ui primitives from `components/ui/*`. Core primitives: - `card` - `input` - `label` - `alert` - `button` - `breadcrumb` - `dropdown-menu` - `skeleton` - `table` Additional primitives used for Search scope UX: - `popover` - `command` - `badge` - `calendar` (react-day-picker wrapper) - `tooltip` (RHL-032) Radix integration note: - Radix triggers (`DropdownMenuTrigger`, `TooltipTrigger`, …) require the trigger element to support `ref` forwarding. - The project’s `components/ui/button.jsx` forwards refs to remain compatible with Radix `asChild` usage. --- ## 13. File Naming Convention (.js vs .jsx) To keep the project consistent: - Use **`.jsx`** for files that contain JSX: - `app/**/page.jsx`, `app/**/layout.jsx` - React components in `components/**` - Use **`.js`** for non-JSX files: - `lib/**` utilities and helpers - `app/api/**/route.js` - `models/**` - tests that do not contain JSX --- ## 14. Tests ### 14.1 Unit tests Core tests: - `lib/frontend/routes.test.js` - `lib/frontend/apiClient.test.js` - `lib/frontend/authRedirect.test.js` - `lib/frontend/authMessages.test.js` RBAC tests: - `lib/frontend/rbac/branchAccess.test.js` - `lib/frontend/rbac/branchUiDecision.test.js` Explorer helper tests: - `lib/frontend/explorer/breadcrumbDropdowns.test.js` - `lib/frontend/explorer/errorMapping.test.js` - `lib/frontend/explorer/formatters.test.js` - `lib/frontend/explorer/sorters.test.js` - `lib/frontend/explorer/pdfUrl.test.js` (RHL-023) Search helper tests: - `lib/frontend/search/urlState.test.js` - `lib/frontend/search/errorMapping.test.js` - `lib/frontend/search/normalizeState.test.js` - `lib/frontend/search/searchApiInput.test.js` - `lib/frontend/search/resultsSorting.test.js` - `lib/frontend/search/dateRange.test.js` (RHL-025) - `lib/frontend/search/datePresets.test.js` (RHL-025) - `lib/frontend/search/dateRangePickerUtils.test.js` (RHL-025) - `lib/frontend/search/searchDateValidation.test.js` (RHL-025) - `lib/frontend/search/dateFilterValidation.test.js` (RHL-025) QuickNav helper tests: - `lib/frontend/quickNav/branchSwitch.test.js` Component SSR smoke test: - `components/app-shell/AppShell.test.js` Password change / toast helpers: - `lib/auth/passwordPolicy.test.js` - `lib/frontend/profile/passwordPolicyUi.test.js` - `lib/frontend/ui/toast.test.js` ### 14.2 Running tests From the repo root: ```bash npx vitest run ``` Optional build check: ```bash npm run build ``` --- ## 15. Manual verification checklist ### 15.1 Local (Docker) Start: ```bash docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build ``` Verify flows in the browser: - Open a protected route while logged out (e.g. `/NL01/2025/12`) - Expect redirect to `/login?reason=expired&next=/NL01/2025/12` - Valid login - Expect redirect into the protected route - Logout - Expect redirect to `/login?reason=logged-out` Profile checks (RHL-009): - Open `/profile` - Confirm account info is visible (including read-only email) - Change password: - wrong current password → inline error + toast - weak new password → inline hints + toast - valid change → success toast, form clears Navigation/TopNav checks (RHL-032): - Tooltips show consistently (no double native tooltips). - Theme toggle switches theme. - Branch switching updates the URL and keeps context (Explorer/Search). - Invalid branch route (`/NL200`) shows warning + recovery item. Explorer checks: - `/:branch` shows years - `/:branch/:year` shows months - `/:branch/:year/:month` shows days - `/:branch/:year/:month/:day` shows files Search checks: - `/NL01/search` - empty state - submit triggers URL update and first-page fetch - admin/dev: - scope switching (single/multi/all) - multi selection via checkbox grid + “Alle abwählen” - limit switching (50/100/200) - date range filter updates `from/to` in the URL Debounced loading UI (RHL-032): - With fast connections: skeleton flashes are minimized. - With throttling: skeletons appear after the delay and remain stable. ### 15.2 Server Deploy and verify on the server URL. Verify: - Explorer navigation and PDF open - Search UI: - scopes - limit selection - date range filter + URL sync - open PDF / jump to day - TopNav branch switching keeps deep links - Profile / password change: - success and negative flows (wrong current password / weak password) --- ## 16. Planned follow-ups - Optional Search UX improvements: - grouping results by date / branch - optional date-only search mode (if desired) - Optional Explorer improvements: - add “Herunterladen” action - optional in-app PDF viewer - Password reset / recovery: - separate follow-up ticket/phase (not part of RHL-009).