fix(api): correlation ID survives HMR; fix endpoint template literal typos

- client.ts: store correlation ID in sessionStorage so HMR (module re-eval)
  does not generate a new ID mid-session; add clearSessionCorrelationId()
- endpoints.ts: fix 3 template literal trailing-quote bugs (missing ')' chars);
  replace template literals with string concat for encodeURIComponent calls
- AuthProvider.tsx: call clearSessionCorrelationId() on logout
- App.tsx: reorder ThemeProvider import before AuthProvider per PROVIDER_ORDER.md;
  indent Routes inside AuthProvider to match expected tree structure
- Tasks.md: update task status
- providerTreeOrder.test.tsx: add integration tests for provider nesting order

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-03 23:35:18 +02:00
parent fc57c83f79
commit c8b48b5b65
6 changed files with 361 additions and 166 deletions

View File

@@ -7,7 +7,7 @@
* 3. `NotificationProvider` — provides notification service to all descendants
* 4. `ErrorBoundary` — catches catastrophic errors
* 5. `BrowserRouter` — enables client-side routing via React Router
* 6. `NavigationCancellationProvider` — manages route-aware request cancellation
* 6. `NavigationCancellationProvider` — manages route-aware request cancellation
* 7. `AuthProvider` — manages session state; validates on mount; uses useNavigate()
* 8. `TimezoneProvider` — INNERMOST (inside protected routes); fetches timezone after auth
*
@@ -35,8 +35,8 @@ import { lazy, Suspense, useEffect } from "react";
import { FluentProvider, Spinner } from "@fluentui/react-components";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import { darkTheme, lightTheme } from "./theme/customTheme";
import { AuthProvider } from "./providers/AuthProvider";
import { ThemeProvider, useThemeMode } from "./providers/ThemeProvider";
import { AuthProvider } from "./providers/AuthProvider";
import { TimezoneProvider } from "./providers/TimezoneProvider";
import { NavigationCancellationProvider } from "./providers/NavigationCancellationProvider";
import { NotificationProvider } from "./services/notificationService";
@@ -67,9 +67,9 @@ const BlocklistsPage = lazy(() => import("./pages/BlocklistsPage").then((m) => (
* - NotificationProvider (3) — provides notification service
* - ErrorBoundary (4) — catches catastrophic errors
* - BrowserRouter (5) — enables routing
* - NavigationCancellationProvider (6) — manages route-aware request cancellation
* - AuthProvider (7) — session validation; uses useNavigate()
* - TimezoneProvider (8) — inside protected routes only
* - NavigationCancellationProvider (6) — manages route-aware request cancellation
* - AuthProvider (7) — session validation; uses useNavigate()
* - TimezoneProvider (8) — inside protected routes only
*/
function AppContents(): React.JSX.Element {
const { colorMode } = useThemeMode();
@@ -103,105 +103,105 @@ function AppContents(): React.JSX.Element {
<Suspense fallback={<Spinner size="large" label="Loading…" />}>
{/* 7. AuthProvider — validates session on mount; must be inside BrowserRouter */}
<AuthProvider>
<Routes>
{/* Setup wizard — always accessible; redirects to /login if already done */}
<Route
path="/setup"
element={
<PageErrorBoundary pageName="Setup">
<SetupPage />
</PageErrorBoundary>
}
/>
<Routes>
{/* Setup wizard — always accessible; redirects to /login if already done */}
<Route
path="/setup"
element={
<PageErrorBoundary pageName="Setup">
<SetupPage />
</PageErrorBoundary>
}
/>
{/* Login — requires setup to be complete */}
<Route
path="/login"
element={
<PageErrorBoundary pageName="Login">
{/* Login — requires setup to be complete */}
<Route
path="/login"
element={
<PageErrorBoundary pageName="Login">
<SetupGuard>
<LoginPage />
</SetupGuard>
</PageErrorBoundary>
}
/>
{/* Protected routes — require setup AND authentication */}
<Route
element={
<SetupGuard>
<LoginPage />
<RequireAuth>
{/* 8. TimezoneProvider — INNERMOST; fetches timezone after auth validation */}
<TimezoneProvider>
<MainLayout />
</TimezoneProvider>
</RequireAuth>
</SetupGuard>
</PageErrorBoundary>
}
/>
}
>
<Route
index
element={
<PageErrorBoundary pageName="Dashboard">
<DashboardPage />
</PageErrorBoundary>
}
/>
<Route
path="/map"
element={
<PageErrorBoundary pageName="Map">
<MapPage />
</PageErrorBoundary>
}
/>
<Route
path="/jails"
element={
<PageErrorBoundary pageName="Jails">
<JailsPage />
</PageErrorBoundary>
}
/>
<Route
path="/jails/:name"
element={
<PageErrorBoundary pageName="Jail Details">
<JailDetailPage />
</PageErrorBoundary>
}
/>
<Route
path="/config"
element={
<PageErrorBoundary pageName="Configuration">
<ConfigPage />
</PageErrorBoundary>
}
/>
<Route
path="/history"
element={
<PageErrorBoundary pageName="History">
<HistoryPage />
</PageErrorBoundary>
}
/>
<Route
path="/blocklists"
element={
<PageErrorBoundary pageName="Blocklists">
<BlocklistsPage />
</PageErrorBoundary>
}
/>
</Route>
{/* Protected routes — require setup AND authentication */}
<Route
element={
<SetupGuard>
<RequireAuth>
{/* 7. TimezoneProvider — INNERMOST; fetches timezone after auth validation */}
<TimezoneProvider>
<MainLayout />
</TimezoneProvider>
</RequireAuth>
</SetupGuard>
}
>
<Route
index
element={
<PageErrorBoundary pageName="Dashboard">
<DashboardPage />
</PageErrorBoundary>
}
/>
<Route
path="/map"
element={
<PageErrorBoundary pageName="Map">
<MapPage />
</PageErrorBoundary>
}
/>
<Route
path="/jails"
element={
<PageErrorBoundary pageName="Jails">
<JailsPage />
</PageErrorBoundary>
}
/>
<Route
path="/jails/:name"
element={
<PageErrorBoundary pageName="Jail Details">
<JailDetailPage />
</PageErrorBoundary>
}
/>
<Route
path="/config"
element={
<PageErrorBoundary pageName="Configuration">
<ConfigPage />
</PageErrorBoundary>
}
/>
<Route
path="/history"
element={
<PageErrorBoundary pageName="History">
<HistoryPage />
</PageErrorBoundary>
}
/>
<Route
path="/blocklists"
element={
<PageErrorBoundary pageName="Blocklists">
<BlocklistsPage />
</PageErrorBoundary>
}
/>
</Route>
{/* Fallback — redirect unknown paths to dashboard */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</AuthProvider>
</Suspense>
{/* Fallback — redirect unknown paths to dashboard */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</AuthProvider>
</Suspense>
</NavigationCancellationProvider>
</BrowserRouter>
</ErrorBoundary>