|
@@ -1,14 +1,4 @@
|
|
|
-<!-- --------------------------------------------------------------------------- -->
|
|
|
|
|
-
|
|
|
|
|
-<!-- Folder: docs -->
|
|
|
|
|
-
|
|
|
|
|
-<!-- File: frontend-ui.md -->
|
|
|
|
|
-
|
|
|
|
|
-<!-- Relative Path: docs/frontend-ui.md -->
|
|
|
|
|
-
|
|
|
|
|
-<!-- --------------------------------------------------------------------------- -->
|
|
|
|
|
-
|
|
|
|
|
-# Frontend UI: App Shell, Routing, Auth/RBAC, Explorer, Search, and Date Range Filter (RHL-019 / RHL-020 / RHL-021 / RHL-022 / RHL-023 / RHL-024 / RHL-025 / RHL-037)
|
|
|
|
|
|
|
+# Frontend UI: App Shell, Routing, Auth/RBAC, Explorer, Search, Date Range Filter, and Navigation Polish (RHL-019 / RHL-020 / RHL-021 / RHL-022 / RHL-023 / RHL-024 / RHL-025 / RHL-037 / RHL-032)
|
|
|
|
|
|
|
|
This document describes the **frontend routing scaffold**, the **application shell layout**, and the core UI modules for the RHL Lieferscheine app.
|
|
This document describes the **frontend routing scaffold**, the **application shell layout**, and the core UI modules for the RHL Lieferscheine app.
|
|
|
|
|
|
|
@@ -22,10 +12,11 @@ Timeline:
|
|
|
- **RHL-024**: Search UI v1 (URL-driven `q`, scopes, cursor pagination, open PDF + jump to day).
|
|
- **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-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-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).
|
|
|
|
|
|
|
|
> **Language policy**
|
|
> **Language policy**
|
|
|
>
|
|
>
|
|
|
-> - Conversation and developer coordination can be German.
|
|
|
|
|
|
|
+> - Developer coordination can be German.
|
|
|
> - **Code, comments, tests, and documentation are English.**
|
|
> - **Code, comments, tests, and documentation are English.**
|
|
|
> - **All user-facing UI strings are German** (labels, button text, alerts, hints).
|
|
> - **All user-facing UI strings are German** (labels, button text, alerts, hints).
|
|
|
|
|
|
|
@@ -33,12 +24,12 @@ Timeline:
|
|
|
|
|
|
|
|
## 1. Scope
|
|
## 1. Scope
|
|
|
|
|
|
|
|
-### 1.1 Implemented (as of RHL-037 + RHL-025)
|
|
|
|
|
|
|
+### 1.1 Implemented (as of RHL-037 + RHL-025 + RHL-032)
|
|
|
|
|
|
|
|
- **Public** `/login` route with a functional login form (shadcn/ui primitives).
|
|
- **Public** `/login` route with a functional login form (shadcn/ui primitives).
|
|
|
|
|
|
|
|
- **Protected** application shell for all authenticated routes:
|
|
- **Protected** application shell for all authenticated routes:
|
|
|
- - Top navigation (brand, status, logout)
|
|
|
|
|
|
|
+ - Top navigation (branding, quick navigation, actions)
|
|
|
- Sidebar placeholder area
|
|
- Sidebar placeholder area
|
|
|
- Main content area
|
|
- Main content area
|
|
|
|
|
|
|
@@ -51,7 +42,7 @@ Timeline:
|
|
|
- AppShell (TopNav + sidebar) remains stable; no “blank spinner screens”.
|
|
- AppShell (TopNav + sidebar) remains stable; no “blank spinner screens”.
|
|
|
|
|
|
|
|
- **Logout**:
|
|
- **Logout**:
|
|
|
- - Logout button calls `GET /api/auth/logout` and redirects to `/login?reason=logged-out`.
|
|
|
|
|
|
|
+ - Logout action calls `GET /api/auth/logout` and redirects to `/login?reason=logged-out`.
|
|
|
|
|
|
|
|
- **UI RBAC (branch-level)**:
|
|
- **UI RBAC (branch-level)**:
|
|
|
- `BranchGuard` prevents branch users from accessing other branches’ URLs.
|
|
- `BranchGuard` prevents branch users from accessing other branches’ URLs.
|
|
@@ -89,41 +80,24 @@ Timeline:
|
|
|
|
|
|
|
|
- **Search UI (RHL-024) + Scope UX improvements (RHL-037)**
|
|
- **Search UI (RHL-024) + Scope UX improvements (RHL-037)**
|
|
|
- Route: `/:branch/search` (protected).
|
|
- Route: `/:branch/search` (protected).
|
|
|
-
|
|
|
|
|
- - URL-driven state for shareability:
|
|
|
|
|
- - `q` (search query)
|
|
|
|
|
-
|
|
|
|
|
- - scope semantics:
|
|
|
|
|
- - **Single**: route branch is the scope (`/:branch/search`). No `branch=` query parameter is required.
|
|
|
|
|
- - **Multi**: `scope=multi&branches=NL06,NL20` (deterministic order, unique list)
|
|
|
|
|
- - **All**: `scope=all`
|
|
|
|
|
-
|
|
|
|
|
- - `limit` (optional): `50 | 100 | 200` (default 100)
|
|
|
|
|
-
|
|
|
|
|
- - `from` / `to` (optional): ISO date range filter (`YYYY-MM-DD`) synced to the URL (RHL-025)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ - URL-driven state for shareability: `q`, `scope`, `branches`, `limit`, `from`, `to`.
|
|
|
- Cursor-based pagination (`nextCursor`) is **not stored in the URL**.
|
|
- Cursor-based pagination (`nextCursor`) is **not stored in the URL**.
|
|
|
|
|
|
|
|
- - Admin/dev UX:
|
|
|
|
|
- - **TopNav branch switch navigates** (deep-link branch switching):
|
|
|
|
|
- - preserves the current deep path (Explorer and Search)
|
|
|
|
|
- - preserves shareable Search query params (`q`, `scope`, `branches`, `limit`, `from`, `to`)
|
|
|
|
|
- - keeps Single scope consistent with the route branch.
|
|
|
|
|
-
|
|
|
|
|
- - **Single scope branch selection** uses a shadcn combobox to switch the _route branch_.
|
|
|
|
|
-
|
|
|
|
|
- - **Multi scope branch selection** uses a checkbox grid (optimized for large branch counts) and provides an **“Alle abwählen”** action.
|
|
|
|
|
-
|
|
|
|
|
- - **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.
|
|
|
|
|
-
|
|
|
|
|
- Important UX policy (current UI v1):
|
|
|
|
|
- - The Search UI still triggers queries only when a non-empty `q` is present.
|
|
|
|
|
- - `from` / `to` are treated as **additive filters** for a text query.
|
|
|
|
|
- - The backend supports date-only searches, but the current UI intentionally avoids this to prevent accidental broad queries.
|
|
|
|
|
|
|
+- **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.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -138,10 +112,6 @@ Timeline:
|
|
|
- “Herunterladen” action (download variant) next to “Öffnen”
|
|
- “Herunterladen” action (download variant) next to “Öffnen”
|
|
|
- a dedicated in-app PDF viewer UI (instead of a new tab)
|
|
- a dedicated in-app PDF viewer UI (instead of a new tab)
|
|
|
|
|
|
|
|
-- Perceived performance polish:
|
|
|
|
|
- - client-side caching/prefetch
|
|
|
|
|
- - skeleton/layout shift reduction
|
|
|
|
|
-
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 2. Route Groups & URL Structure
|
|
## 2. Route Groups & URL Structure
|
|
@@ -190,6 +160,7 @@ Responsibilities:
|
|
|
- Global CSS imports (`app/globals.css`).
|
|
- Global CSS imports (`app/globals.css`).
|
|
|
- Theme provider setup (shadcn/ui + next-themes wrapper).
|
|
- Theme provider setup (shadcn/ui + next-themes wrapper).
|
|
|
- Base HTML/body structure.
|
|
- Base HTML/body structure.
|
|
|
|
|
+- **Icons / favicon** (RHL-032): metadata icons can be configured in the root layout.
|
|
|
|
|
|
|
|
### 3.2 Public layout
|
|
### 3.2 Public layout
|
|
|
|
|
|
|
@@ -213,8 +184,8 @@ Responsibilities:
|
|
|
|
|
|
|
|
UX rationale:
|
|
UX rationale:
|
|
|
|
|
|
|
|
-- We keep the AppShell frame visible while auth/session checks run.
|
|
|
|
|
-- This avoids full-screen “blank spinners” on slow connections.
|
|
|
|
|
|
|
+- Keep the AppShell frame visible while auth/session checks run.
|
|
|
|
|
+- Avoid full-screen “blank spinners” on slow connections.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -245,64 +216,136 @@ Below `2xl`:
|
|
|
- Use full width for usability.
|
|
- Use full width for usability.
|
|
|
- Hide the sidebar placeholder to avoid shrinking the main content area.
|
|
- Hide the sidebar placeholder to avoid shrinking the main content area.
|
|
|
|
|
|
|
|
-### 4.2 Quick navigation (quality-of-life)
|
|
|
|
|
|
|
+### 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` (placeholder page until profile editing is implemented).
|
|
|
|
|
+- **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`
|
|
File: `components/app-shell/QuickNav.jsx`
|
|
|
|
|
|
|
|
Purpose:
|
|
Purpose:
|
|
|
|
|
|
|
|
-- Reduce manual URL typing during manual testing and daily usage.
|
|
|
|
|
- Provide direct links to:
|
|
- Provide direct links to:
|
|
|
- Explorer (`/:branch`)
|
|
- Explorer (`/:branch`)
|
|
|
- Search (`/:branch/search`)
|
|
- Search (`/:branch/search`)
|
|
|
|
|
|
|
|
|
|
+- For admin/dev: enable quick branch switching while preserving the current “context”.
|
|
|
|
|
+
|
|
|
Behavior:
|
|
Behavior:
|
|
|
|
|
|
|
|
-- Branch users: QuickNav uses the user’s `branchId`.
|
|
|
|
|
|
|
+- Branch users:
|
|
|
|
|
+ - QuickNav is effectively fixed to their `branchId`.
|
|
|
|
|
|
|
|
- Admin/dev users:
|
|
- Admin/dev users:
|
|
|
- - QuickNav loads branches via `GET /api/branches`.
|
|
|
|
|
- - Provides a branch dropdown.
|
|
|
|
|
- - Stores the last selected branch in `localStorage` for convenience.
|
|
|
|
|
-
|
|
|
|
|
-Implementation notes:
|
|
|
|
|
|
|
+ - 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 list loading is intentionally **not** tied to the dropdown selection.
|
|
|
|
|
- - The list is fetched once for the authenticated admin/dev user (or when the user changes).
|
|
|
|
|
- - This avoids unnecessary refetches when switching branches in the UI.
|
|
|
|
|
|
|
+Branch switching rule (RHL-037):
|
|
|
|
|
|
|
|
-RHL-037 navigation rule:
|
|
|
|
|
-
|
|
|
|
|
-- When admin/dev selects a branch in QuickNav, the app **navigates** to the same section while replacing the path branch segment:
|
|
|
|
|
- - Explorer deep paths are preserved:
|
|
|
|
|
|
|
+- 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`
|
|
- `/NL32/2025/12/31` → `/NL20/2025/12/31`
|
|
|
|
|
|
|
|
- - Search route is preserved and shareable query params are preserved:
|
|
|
|
|
- - `/NL32/search?q=x&scope=multi&branches=NL06,NL20&limit=200&from=2026-01-01&to=2026-01-31`
|
|
|
|
|
- → `/NL20/search?q=x&scope=multi&branches=NL06,NL20&limit=200&from=2026-01-01&to=2026-01-31`
|
|
|
|
|
-
|
|
|
|
|
- - Single scope is kept consistent with the route branch (no divergence between UI selection and URL).
|
|
|
|
|
|
|
+ - 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 note:
|
|
|
|
|
|
|
+Implementation notes:
|
|
|
|
|
|
|
|
-- Helper logic for deep-path branch switching is centralized under `lib/frontend/quickNav/branchSwitch.js`.
|
|
|
|
|
|
|
+- 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.
|
|
|
|
|
|
|
|
-Responsive behavior:
|
|
|
|
|
|
|
+Invalid branch routes (admin/dev) (RHL-032):
|
|
|
|
|
|
|
|
-- QuickNav is hidden on small screens by default (`md` and up only) to keep the header compact.
|
|
|
|
|
|
|
+- 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”.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 5. Authentication UX (RHL-020)
|
|
|
|
|
|
|
+## 6. Authentication UX (RHL-020)
|
|
|
|
|
|
|
|
-### 5.1 Session check for protected routes
|
|
|
|
|
|
|
+### 6.1 Session check for protected routes
|
|
|
|
|
|
|
|
File: `components/auth/AuthProvider.jsx`
|
|
File: `components/auth/AuthProvider.jsx`
|
|
|
|
|
|
|
|
Behavior:
|
|
Behavior:
|
|
|
|
|
|
|
|
1. On mount, call `apiClient.getMe()`.
|
|
1. On mount, call `apiClient.getMe()`.
|
|
|
-
|
|
|
|
|
2. If `{ user: { ... } }`:
|
|
2. If `{ user: { ... } }`:
|
|
|
- set auth state to `authenticated`
|
|
- set auth state to `authenticated`
|
|
|
- render protected UI
|
|
- render protected UI
|
|
@@ -315,17 +358,21 @@ The `next` parameter:
|
|
|
- includes the original `pathname` and query string
|
|
- includes the original `pathname` and query string
|
|
|
- is sanitized to avoid open redirects (only internal paths are allowed)
|
|
- is sanitized to avoid open redirects (only internal paths are allowed)
|
|
|
|
|
|
|
|
-### 5.2 AuthGate (in-shell gating)
|
|
|
|
|
|
|
+### 6.2 AuthGate (in-shell gating)
|
|
|
|
|
|
|
|
File: `components/auth/AuthGate.jsx`
|
|
File: `components/auth/AuthGate.jsx`
|
|
|
|
|
|
|
|
Behavior:
|
|
Behavior:
|
|
|
|
|
|
|
|
-- While session is loading: show an in-shell loading card.
|
|
|
|
|
|
|
+- While session is loading: render a minimal in-shell state.
|
|
|
- On auth errors: show an in-shell error card + retry.
|
|
- On auth errors: show an in-shell error card + retry.
|
|
|
-- On unauthenticated: show an in-shell “redirecting” message while redirect happens.
|
|
|
|
|
|
|
+- On unauthenticated: show a short “redirecting” message while redirect happens.
|
|
|
|
|
+
|
|
|
|
|
+RHL-032 note:
|
|
|
|
|
|
|
|
-### 5.3 Login page (reason / next)
|
|
|
|
|
|
|
+- Prefer surfacing transient “session checking” feedback in the TopNav (SessionIndicator) to avoid content flicker.
|
|
|
|
|
+
|
|
|
|
|
+### 6.3 Login page (reason / next)
|
|
|
|
|
|
|
|
Files:
|
|
Files:
|
|
|
|
|
|
|
@@ -335,13 +382,11 @@ Files:
|
|
|
Flow:
|
|
Flow:
|
|
|
|
|
|
|
|
1. Login page parses query params using `parseLoginParams(...)`.
|
|
1. Login page parses query params using `parseLoginParams(...)`.
|
|
|
-
|
|
|
|
|
2. If `reason` is present:
|
|
2. If `reason` is present:
|
|
|
- `expired` → show “session expired” banner (German)
|
|
- `expired` → show “session expired” banner (German)
|
|
|
- `logged-out` → show “logged out” banner (German)
|
|
- `logged-out` → show “logged out” banner (German)
|
|
|
|
|
|
|
|
3. On submit, the form calls `apiClient.login({ username, password })`.
|
|
3. On submit, the form calls `apiClient.login({ username, password })`.
|
|
|
-
|
|
|
|
|
4. On success:
|
|
4. On success:
|
|
|
- redirect to `next` if present
|
|
- redirect to `next` if present
|
|
|
- otherwise redirect to `/`
|
|
- otherwise redirect to `/`
|
|
@@ -356,20 +401,11 @@ Username policy:
|
|
|
- username input is normalized to lowercase
|
|
- username input is normalized to lowercase
|
|
|
- `autoCapitalize="none"` to prevent mobile auto-caps
|
|
- `autoCapitalize="none"` to prevent mobile auto-caps
|
|
|
|
|
|
|
|
-### 5.4 Logout
|
|
|
|
|
-
|
|
|
|
|
-File: `components/auth/LogoutButton.jsx`
|
|
|
|
|
-
|
|
|
|
|
-Flow:
|
|
|
|
|
-
|
|
|
|
|
-1. Calls `apiClient.logout()`.
|
|
|
|
|
-2. Redirects to `/login?reason=logged-out`.
|
|
|
|
|
-
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 6. UI RBAC, Forbidden, and NotFound (RHL-021)
|
|
|
|
|
|
|
+## 7. UI RBAC, Forbidden, and NotFound (RHL-021)
|
|
|
|
|
|
|
|
-### 6.1 Goals
|
|
|
|
|
|
|
+### 7.1 Goals
|
|
|
|
|
|
|
|
RHL-021 adds a friendly UI layer on top of backend RBAC:
|
|
RHL-021 adds a friendly UI layer on top of backend RBAC:
|
|
|
|
|
|
|
@@ -382,7 +418,7 @@ Backend RBAC remains the source of truth. UI RBAC exists to:
|
|
|
- prevent “obviously forbidden” navigation in the frontend
|
|
- prevent “obviously forbidden” navigation in the frontend
|
|
|
- provide clear and consistent UX for end users
|
|
- provide clear and consistent UX for end users
|
|
|
|
|
|
|
|
-### 6.2 BranchGuard (UI-side RBAC)
|
|
|
|
|
|
|
+### 7.2 BranchGuard (UI-side RBAC)
|
|
|
|
|
|
|
|
Files:
|
|
Files:
|
|
|
|
|
|
|
@@ -405,7 +441,7 @@ Admin/dev branch existence validation:
|
|
|
- If fetching the list fails, do not block rendering.
|
|
- If fetching the list fails, do not block rendering.
|
|
|
- Backend RBAC and subsequent API calls remain authoritative.
|
|
- Backend RBAC and subsequent API calls remain authoritative.
|
|
|
|
|
|
|
|
-### 6.3 Param validation (year/month/day)
|
|
|
|
|
|
|
+### 7.3 Param validation (year/month/day)
|
|
|
|
|
|
|
|
Files:
|
|
Files:
|
|
|
|
|
|
|
@@ -420,15 +456,15 @@ Layout enforcement (server-side `notFound()`):
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 7. Explorer v2 (RHL-022) + PDF Open (RHL-023)
|
|
|
|
|
|
|
+## 8. Explorer v2 (RHL-022) + PDF Open (RHL-023)
|
|
|
|
|
|
|
|
-### 7.1 UI goal
|
|
|
|
|
|
|
+### 8.1 UI goal
|
|
|
|
|
|
|
|
Provide a simple “file explorer” drill-down:
|
|
Provide a simple “file explorer” drill-down:
|
|
|
|
|
|
|
|
- Year → Month → Day → Files
|
|
- Year → Month → Day → Files
|
|
|
|
|
|
|
|
-### 7.2 Explorer pages
|
|
|
|
|
|
|
+### 8.2 Explorer pages
|
|
|
|
|
|
|
|
Routes and components:
|
|
Routes and components:
|
|
|
|
|
|
|
@@ -437,14 +473,14 @@ Routes and components:
|
|
|
- `/:branch/:year/:month` → `components/explorer/levels/DaysExplorer.jsx`
|
|
- `/:branch/:year/:month` → `components/explorer/levels/DaysExplorer.jsx`
|
|
|
- `/:branch/:year/:month/:day` → `components/explorer/levels/FilesExplorer.jsx`
|
|
- `/:branch/:year/:month/:day` → `components/explorer/levels/FilesExplorer.jsx`
|
|
|
|
|
|
|
|
-### 7.3 Data fetching strategy
|
|
|
|
|
|
|
+### 8.3 Data fetching strategy
|
|
|
|
|
|
|
|
- All Explorer pages are **Client Components**.
|
|
- All Explorer pages are **Client Components**.
|
|
|
- All JSON API calls go through `lib/frontend/apiClient.js`.
|
|
- All JSON API calls go through `lib/frontend/apiClient.js`.
|
|
|
- A small hook provides consistent query state:
|
|
- A small hook provides consistent query state:
|
|
|
- `lib/frontend/hooks/useExplorerQuery.js`
|
|
- `lib/frontend/hooks/useExplorerQuery.js`
|
|
|
|
|
|
|
|
-### 7.4 Breadcrumbs (with dropdowns)
|
|
|
|
|
|
|
+### 8.4 Breadcrumbs (with dropdowns)
|
|
|
|
|
|
|
|
Files:
|
|
Files:
|
|
|
|
|
|
|
@@ -457,7 +493,7 @@ Files:
|
|
|
- `lib/frontend/explorer/formatters.js` (German month labels)
|
|
- `lib/frontend/explorer/formatters.js` (German month labels)
|
|
|
- `lib/frontend/explorer/sorters.js`
|
|
- `lib/frontend/explorer/sorters.js`
|
|
|
|
|
|
|
|
-### 7.5 Files list (leaf route) and “Open PDF”
|
|
|
|
|
|
|
+### 8.5 Files list (leaf route) and “Open PDF”
|
|
|
|
|
|
|
|
Leaf route:
|
|
Leaf route:
|
|
|
|
|
|
|
@@ -480,13 +516,11 @@ Implementation notes:
|
|
|
- URL construction is centralized in:
|
|
- URL construction is centralized in:
|
|
|
- `lib/frontend/explorer/pdfUrl.js`
|
|
- `lib/frontend/explorer/pdfUrl.js`
|
|
|
|
|
|
|
|
-- The PDF endpoint is binary (`application/pdf`). The UI uses navigation (`<a target="_blank">`) instead of `apiClient`.
|
|
|
|
|
-
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 8. Search UI (RHL-024 / RHL-037) + Date Range Filter (RHL-025)
|
|
|
|
|
|
|
+## 9. Search UI (RHL-024 / RHL-037) + Date Range Filter (RHL-025)
|
|
|
|
|
|
|
|
-### 8.1 Route and state model
|
|
|
|
|
|
|
+### 9.1 Route and state model
|
|
|
|
|
|
|
|
Route:
|
|
Route:
|
|
|
|
|
|
|
@@ -509,193 +543,51 @@ Shareable params (first page identity):
|
|
|
|
|
|
|
|
- Scope params:
|
|
- Scope params:
|
|
|
- **Single**: no `scope` param required; route branch defines the branch.
|
|
- **Single**: no `scope` param required; route branch defines the branch.
|
|
|
- - Example: `/NL01/search?q=reifen`
|
|
|
|
|
-
|
|
|
|
|
- - **Multi**: `scope=multi&branches=NL06,NL20`
|
|
|
|
|
- - branches list is deterministic:
|
|
|
|
|
- - unique
|
|
|
|
|
- - stable ordering
|
|
|
|
|
-
|
|
|
|
|
|
|
+ - **Multi**: `scope=multi&branches=NL06,NL20` (deterministic)
|
|
|
- **All**: `scope=all`
|
|
- **All**: `scope=all`
|
|
|
|
|
|
|
|
- `limit`:
|
|
- `limit`:
|
|
|
- allowed values: `50 | 100 | 200`
|
|
- allowed values: `50 | 100 | 200`
|
|
|
- default: `100`
|
|
- default: `100`
|
|
|
- - only written to URL when non-default to keep URLs readable
|
|
|
|
|
|
|
|
|
|
- `from/to` (RHL-025):
|
|
- `from/to` (RHL-025):
|
|
|
- ISO date filter in `YYYY-MM-DD`
|
|
- ISO date filter in `YYYY-MM-DD`
|
|
|
- - both keys are optional
|
|
|
|
|
- preserved in URLs and across branch switching
|
|
- preserved in URLs and across branch switching
|
|
|
|
|
|
|
|
-Pagination:
|
|
|
|
|
-
|
|
|
|
|
-- `nextCursor` is kept in client state.
|
|
|
|
|
-- “Mehr laden” triggers a request with `cursor=nextCursor` and appends results.
|
|
|
|
|
-
|
|
|
|
|
-### 8.2 Roles and scope UI
|
|
|
|
|
|
|
+### 9.2 Debounced loading UI (RHL-032)
|
|
|
|
|
|
|
|
-Branch users:
|
|
|
|
|
|
|
+Goal:
|
|
|
|
|
|
|
|
-- No scope selector.
|
|
|
|
|
-- The UI always searches within the current route branch.
|
|
|
|
|
|
|
+- Avoid visible “skeleton flashes” for fast requests.
|
|
|
|
|
|
|
|
-Admin/dev users:
|
|
|
|
|
|
|
+Approach:
|
|
|
|
|
|
|
|
-- Scope selector:
|
|
|
|
|
- - “Diese Niederlassung” (single: route branch)
|
|
|
|
|
- - “Mehrere Niederlassungen” (multi)
|
|
|
|
|
- - “Alle Niederlassungen” (all)
|
|
|
|
|
-
|
|
|
|
|
-- Single branch selection:
|
|
|
|
|
- - shadcn combobox
|
|
|
|
|
- - selecting a branch **navigates** to `/:branch/search` while preserving shareable query params (`q`, scope params, `limit`, `from/to`).
|
|
|
|
|
-
|
|
|
|
|
-- Multi branch selection:
|
|
|
|
|
- - checkbox grid optimized for large branch counts
|
|
|
|
|
- - “selectable card” label wrapper highlights checked items (border + background)
|
|
|
|
|
- - pointer cursor + hover affordances improve discoverability
|
|
|
|
|
- - “Alle abwählen” button to reset selection
|
|
|
|
|
-
|
|
|
|
|
-- Branch list:
|
|
|
|
|
- - loaded via `GET /api/branches`
|
|
|
|
|
- - fail-open UI behavior:
|
|
|
|
|
- - if branch list fails, Search UI remains usable
|
|
|
|
|
- - allow manual NLxx input as a fallback for selection
|
|
|
|
|
-
|
|
|
|
|
-### 8.3 Search form structure
|
|
|
|
|
|
|
+- Loading UI is shown only after a small delay.
|
|
|
|
|
+- This is implemented with `useDebouncedVisibility(...)` and centralized timing constants.
|
|
|
|
|
|
|
|
Files:
|
|
Files:
|
|
|
|
|
|
|
|
-- `components/search/SearchPage.jsx`
|
|
|
|
|
-- `components/search/SearchForm.jsx`
|
|
|
|
|
-
|
|
|
|
|
-Form building blocks:
|
|
|
|
|
-
|
|
|
|
|
-- `components/search/form/SearchQueryBox.jsx`
|
|
|
|
|
-- `components/search/form/SearchScopeSelect.jsx`
|
|
|
|
|
-- `components/search/form/SearchSingleBranchCombobox.jsx`
|
|
|
|
|
-- `components/search/form/SearchMultiBranchPicker.jsx`
|
|
|
|
|
-- `components/search/form/SearchLimitSelect.jsx`
|
|
|
|
|
-- `components/search/form/SearchDateRangePicker.jsx` (RHL-025)
|
|
|
|
|
-- `components/search/form/SearchDateFilterChip.jsx` (RHL-025)
|
|
|
|
|
-
|
|
|
|
|
-Key UX rules:
|
|
|
|
|
-
|
|
|
|
|
-- `q` is the primary trigger for searches (explicit submit, no debounce).
|
|
|
|
|
-- Date range changes update the URL and are applied when a query is active.
|
|
|
|
|
-- Validation errors are shown **near the form**, not duplicated in the results area.
|
|
|
|
|
-
|
|
|
|
|
-### 8.4 Result list and actions
|
|
|
|
|
-
|
|
|
|
|
-Search results show at least:
|
|
|
|
|
-
|
|
|
|
|
-- Branch (for multi/all)
|
|
|
|
|
-- Date (German formatted)
|
|
|
|
|
-- Filename
|
|
|
|
|
-- Relative path
|
|
|
|
|
-- Optional snippet (if returned by the provider)
|
|
|
|
|
-
|
|
|
|
|
-Actions:
|
|
|
|
|
-
|
|
|
|
|
-- “Öffnen”
|
|
|
|
|
- - uses `buildPdfUrl(...)`
|
|
|
|
|
- - opens the binary endpoint in a new tab (`<a target="_blank" rel="noopener noreferrer">`)
|
|
|
|
|
-
|
|
|
|
|
-- “Zum Ordner”
|
|
|
|
|
- - navigates to `/:branch/:year/:month/:day` using `dayPath(...)`
|
|
|
|
|
-
|
|
|
|
|
-Implementation note:
|
|
|
|
|
-
|
|
|
|
|
-- In the Search results table, the actions are intentionally kept in a fixed-width column.
|
|
|
|
|
-- Buttons render side-by-side on desktop; on very small widths they can wrap.
|
|
|
|
|
-
|
|
|
|
|
-Sorting:
|
|
|
|
|
-
|
|
|
|
|
-- “Relevanz” (backend order)
|
|
|
|
|
-- “Datum (neueste zuerst)”
|
|
|
|
|
-- “Niederlassung” (NLxx ascending; with stable tie-breakers by date and filename)
|
|
|
|
|
-
|
|
|
|
|
-Totals:
|
|
|
|
|
-
|
|
|
|
|
-- When the backend returns `total`, the UI shows “x von y Treffern geladen”.
|
|
|
|
|
-- If `total` is `null`, the UI falls back to showing only the loaded count.
|
|
|
|
|
-
|
|
|
|
|
-### 8.5 Date range filter (RHL-025)
|
|
|
|
|
|
|
+- Timing constants: `lib/frontend/ui/uxTimings.js`
|
|
|
|
|
+ - `LOADING_UI_DELAY_MS`
|
|
|
|
|
+ - `SESSION_INDICATOR_DELAY_MS`
|
|
|
|
|
+ - `SESSION_INDICATOR_MIN_VISIBLE_MS`
|
|
|
|
|
+ - `TOOLTIP_DELAY_MS`
|
|
|
|
|
|
|
|
-#### 8.5.1 Goals
|
|
|
|
|
|
|
+- Debounce hook: `lib/frontend/hooks/useDebouncedVisibility.js`
|
|
|
|
|
|
|
|
-- Allow narrowing down text searches by **inclusive** date filters.
|
|
|
|
|
-- Keep the date filter **shareable** via the URL (`from`, `to`).
|
|
|
|
|
-- Provide **fast feedback** (local validation) while also relying on backend validation.
|
|
|
|
|
|
|
+Applied to:
|
|
|
|
|
|
|
|
-#### 8.5.2 URL representation
|
|
|
|
|
-
|
|
|
|
|
-- `from` and `to` are ISO strings: `YYYY-MM-DD`.
|
|
|
|
|
-
|
|
|
|
|
-- Open ranges are supported:
|
|
|
|
|
- - only `from` → “ab <date>”
|
|
|
|
|
- - only `to` → “bis <date>”
|
|
|
|
|
-
|
|
|
|
|
-- A single-day search is represented as:
|
|
|
|
|
- - `from === to`
|
|
|
|
|
-
|
|
|
|
|
-#### 8.5.3 UI controls
|
|
|
|
|
-
|
|
|
|
|
-- Entry point: “Zeitraum” button in the Search form.
|
|
|
|
|
-
|
|
|
|
|
-- Popover contents:
|
|
|
|
|
- - Two read-only inputs (“Von”, “Bis”) with clear buttons
|
|
|
|
|
- - Two-month calendar view
|
|
|
|
|
- - Presets (German) for common ranges
|
|
|
|
|
- - Reset action
|
|
|
|
|
-
|
|
|
|
|
-- Active filter display:
|
|
|
|
|
- - A compact chip below the form controls
|
|
|
|
|
- - Provides a one-click clear action
|
|
|
|
|
-
|
|
|
|
|
-#### 8.5.4 Validation and error mapping
|
|
|
|
|
-
|
|
|
|
|
-Single Source of Truth (frontend):
|
|
|
|
|
-
|
|
|
|
|
-- ISO parsing + range checks:
|
|
|
|
|
- - `lib/frontend/search/dateRange.js`
|
|
|
|
|
-
|
|
|
|
|
-- Canonical date-range validator:
|
|
|
|
|
- - `lib/frontend/search/searchDateValidation.js`
|
|
|
|
|
-
|
|
|
|
|
-Local validation:
|
|
|
|
|
-
|
|
|
|
|
-- While editing, the UI produces a local `ApiClientError` using:
|
|
|
|
|
- - `lib/frontend/search/dateFilterValidation.js`
|
|
|
|
|
-
|
|
|
|
|
-- This error is mapped to German UI copy via:
|
|
|
|
|
- - `lib/frontend/search/errorMapping.js`
|
|
|
|
|
-
|
|
|
|
|
-Rules:
|
|
|
|
|
-
|
|
|
|
|
-- `from > to` is invalid and produces `VALIDATION_SEARCH_RANGE`.
|
|
|
|
|
-- Invalid ISO strings produce `VALIDATION_SEARCH_DATE`.
|
|
|
|
|
-- `from === to` is valid (single day).
|
|
|
|
|
-
|
|
|
|
|
-Backend alignment:
|
|
|
|
|
-
|
|
|
|
|
-- The backend also validates `from/to` and returns the same error codes.
|
|
|
|
|
-- This keeps frontend and backend behavior consistent.
|
|
|
|
|
-
|
|
|
|
|
-#### 8.5.5 Calendar integration details
|
|
|
|
|
-
|
|
|
|
|
-- The calendar is loaded client-side (SSR disabled) to avoid hydration/runtime issues.
|
|
|
|
|
-- The hook normalizes day-click handler signatures defensively to avoid version drift.
|
|
|
|
|
-- When the range is invalid (`from > to`), the calendar still shows the interval but highlights it as invalid.
|
|
|
|
|
|
|
+- Explorer level loading skeletons
|
|
|
|
|
+- Search results loading skeletons
|
|
|
|
|
+- TopNav session indicator
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 9. UI primitives (shadcn/ui)
|
|
|
|
|
|
|
+## 10. UI primitives (shadcn/ui)
|
|
|
|
|
|
|
|
The Explorer + auth + search UI uses shadcn/ui primitives from `components/ui/*`.
|
|
The Explorer + auth + search UI uses shadcn/ui primitives from `components/ui/*`.
|
|
|
|
|
|
|
|
-Required components for the current scope:
|
|
|
|
|
|
|
+Core primitives:
|
|
|
|
|
|
|
|
- `card`
|
|
- `card`
|
|
|
- `input`
|
|
- `input`
|
|
@@ -713,10 +605,16 @@ Additional primitives used for Search scope UX:
|
|
|
- `command`
|
|
- `command`
|
|
|
- `badge`
|
|
- `badge`
|
|
|
- `calendar` (react-day-picker wrapper)
|
|
- `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.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 10. File Naming Convention (.js vs .jsx)
|
|
|
|
|
|
|
+## 11. File Naming Convention (.js vs .jsx)
|
|
|
|
|
|
|
|
To keep the project consistent:
|
|
To keep the project consistent:
|
|
|
|
|
|
|
@@ -732,9 +630,9 @@ To keep the project consistent:
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 11. Tests
|
|
|
|
|
|
|
+## 12. Tests
|
|
|
|
|
|
|
|
-### 11.1 Unit tests
|
|
|
|
|
|
|
+### 12.1 Unit tests
|
|
|
|
|
|
|
|
Core tests:
|
|
Core tests:
|
|
|
|
|
|
|
@@ -777,7 +675,7 @@ Component SSR smoke test:
|
|
|
|
|
|
|
|
- `components/app-shell/AppShell.test.js`
|
|
- `components/app-shell/AppShell.test.js`
|
|
|
|
|
|
|
|
-### 11.2 Running tests
|
|
|
|
|
|
|
+### 12.2 Running tests
|
|
|
|
|
|
|
|
From the repo root:
|
|
From the repo root:
|
|
|
|
|
|
|
@@ -793,9 +691,9 @@ npm run build
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 12. Manual verification checklist
|
|
|
|
|
|
|
+## 13. Manual verification checklist
|
|
|
|
|
|
|
|
-### 12.1 Local (Docker)
|
|
|
|
|
|
|
+### 13.1 Local (Docker)
|
|
|
|
|
|
|
|
Start:
|
|
Start:
|
|
|
|
|
|
|
@@ -808,15 +706,19 @@ Verify flows in the browser:
|
|
|
- Open a protected route while logged out (e.g. `/NL01/2025/12`)
|
|
- Open a protected route while logged out (e.g. `/NL01/2025/12`)
|
|
|
- Expect redirect to `/login?reason=expired&next=/NL01/2025/12`
|
|
- Expect redirect to `/login?reason=expired&next=/NL01/2025/12`
|
|
|
|
|
|
|
|
-- Invalid login
|
|
|
|
|
- - Expect a German error message (e.g. “Benutzername oder Passwort ist falsch.”)
|
|
|
|
|
-
|
|
|
|
|
- Valid login
|
|
- Valid login
|
|
|
- Expect redirect into the protected route
|
|
- Expect redirect into the protected route
|
|
|
|
|
|
|
|
- Logout
|
|
- Logout
|
|
|
- Expect redirect to `/login?reason=logged-out`
|
|
- Expect redirect to `/login?reason=logged-out`
|
|
|
|
|
|
|
|
|
|
+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:
|
|
Explorer checks:
|
|
|
|
|
|
|
|
- `/:branch` shows years
|
|
- `/:branch` shows years
|
|
@@ -824,12 +726,6 @@ Explorer checks:
|
|
|
- `/:branch/:year/:month` shows days
|
|
- `/:branch/:year/:month` shows days
|
|
|
- `/:branch/:year/:month/:day` shows files
|
|
- `/:branch/:year/:month/:day` shows files
|
|
|
|
|
|
|
|
-PDF open:
|
|
|
|
|
-
|
|
|
|
|
-- On `/:branch/:year/:month/:day`, click **“Öffnen”**
|
|
|
|
|
- - Expected: opens the PDF in a new tab
|
|
|
|
|
- - Expected: works for filenames with spaces and special characters (URL-encoding)
|
|
|
|
|
-
|
|
|
|
|
Search checks:
|
|
Search checks:
|
|
|
|
|
|
|
|
- `/NL01/search`
|
|
- `/NL01/search`
|
|
@@ -838,27 +734,16 @@ Search checks:
|
|
|
|
|
|
|
|
- admin/dev:
|
|
- admin/dev:
|
|
|
- scope switching (single/multi/all)
|
|
- scope switching (single/multi/all)
|
|
|
- - single branch selection via combobox
|
|
|
|
|
- multi selection via checkbox grid + “Alle abwählen”
|
|
- multi selection via checkbox grid + “Alle abwählen”
|
|
|
- limit switching (50/100/200)
|
|
- limit switching (50/100/200)
|
|
|
|
|
+ - date range filter updates `from/to` in the URL
|
|
|
|
|
|
|
|
-- date range filter (RHL-025):
|
|
|
|
|
- - open “Zeitraum” popover and pick a range
|
|
|
|
|
- - verify `from/to` are written to the URL
|
|
|
|
|
- - verify chip shows the German label and can clear the filter
|
|
|
|
|
- - verify presets (“Heute”, “Letzte 7 Tage”, …) set the expected range
|
|
|
|
|
- - invalid range:
|
|
|
|
|
- - ensure UI displays a validation message
|
|
|
|
|
- - ensure the calendar highlights the invalid interval
|
|
|
|
|
-
|
|
|
|
|
-- pagination:
|
|
|
|
|
- - “Mehr laden” appends results when `nextCursor` exists
|
|
|
|
|
|
|
+Debounced loading UI (RHL-032):
|
|
|
|
|
|
|
|
-- TopNav QuickNav:
|
|
|
|
|
- - switching branch updates the URL and keeps the current deep path
|
|
|
|
|
- - switching branch on Search keeps shareable query params (including `from/to`)
|
|
|
|
|
|
|
+- With fast connections: skeleton flashes are minimized.
|
|
|
|
|
+- With throttling: skeletons appear after the delay and remain stable.
|
|
|
|
|
|
|
|
-### 12.2 Server
|
|
|
|
|
|
|
+### 13.2 Server
|
|
|
|
|
|
|
|
Deploy and verify on the server URL.
|
|
Deploy and verify on the server URL.
|
|
|
|
|
|
|
@@ -869,17 +754,15 @@ Verify:
|
|
|
- scopes
|
|
- scopes
|
|
|
- limit selection
|
|
- limit selection
|
|
|
- date range filter + URL sync
|
|
- date range filter + URL sync
|
|
|
- - total count (“x von y geladen”)
|
|
|
|
|
- open PDF / jump to day
|
|
- open PDF / jump to day
|
|
|
- TopNav branch switching keeps deep links
|
|
- TopNav branch switching keeps deep links
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 13. Planned follow-ups
|
|
|
|
|
|
|
+## 14. Planned follow-ups
|
|
|
|
|
|
|
|
- Optional Search UX improvements:
|
|
- Optional Search UX improvements:
|
|
|
- grouping results by date / branch
|
|
- grouping results by date / branch
|
|
|
- - query presets (beyond the date presets)
|
|
|
|
|
- optional date-only search mode (if desired)
|
|
- optional date-only search mode (if desired)
|
|
|
|
|
|
|
|
- Optional Explorer improvements:
|
|
- Optional Explorer improvements:
|