# Provider Dependency Order Contract

## Overview

The BanGUI frontend relies on multiple context providers that wrap the React component tree. The **order** in which these providers are nested is critical and order-dependent initialization may fail silently if violated.

This document makes that order explicit, documents the rationale for each provider's position, and defines the contract that future refactors must respect.

---

## Provider Hierarchy (Outermost to Innermost)

```
1. ThemeProvider                       (must be outermost — provides theme to AppContents)
   └─ AppContents
      2. FluentProvider                (must wrap all Fluent UI consumers)
      3. NotificationProvider          (must wrap error boundaries)
      4. ErrorBoundary                 (top-level — catches catastrophic errors)
         5. NotificationContainer      (renders notifications)
         6. BrowserRouter              (enables routing)
            7. NavigationCancellationProvider (manages route-aware request cancellation)
               8. AuthProvider         (provides auth context)
                  9. Routes with SetupGuard & RequireAuth
                     10. TimezoneProvider (wraps protected routes only)
```

---

## Detailed Provider Contract

### 1. **ThemeProvider** (Outermost)

**Location in code:** `App()` → `AppContents()`

**Why it must be outermost:**
- Provides the theme context to `AppContents` via `useThemeMode()` hook
- The theme is determined from `useThemeMode()` to select between `darkTheme` and `lightTheme`
- `FluentProvider` requires the theme object as a prop
- Cannot call hooks (like `useThemeMode`) outside the provider that creates them

**Dependencies:** None (must be first)

**Initialization:** Synchronous (reads from localStorage + system preference)

---

### 2. **FluentProvider** (Inside AppContents, immediately after ThemeProvider)

**Location in code:** `AppContents()` wrapper for all Fluent UI usage

**Why it must be here:**
- Must wrap all Fluent UI component consumers (Spinner, etc.)
- Receives the theme selected by `useThemeMode()` inside `AppContents`
- Must be inside `AppContents` so it can access the theme from context

**Dependencies:** ThemeProvider (must be outside)

**Initialization:** Synchronous (just sets theme context)

---

### 3. **NotificationProvider** (Inside FluentProvider)

**Location in code:** `AppContents()` after FluentProvider

**Why it must be here:**
- Must wrap error boundaries so error boundaries can call `useNotification()`
- Error boundaries may trigger notifications on error
- Provides the notification service to all descendants

**Dependencies:** FluentProvider (for UI consistency, optional but recommended)

**Initialization:** Synchronous (creates notification queue)

---

### 4. **ErrorBoundary** (Top-level error boundary)

**Location in code:** `AppContents()` after NotificationProvider

**Why it must be here:**
- Catches catastrophic errors that would crash the entire app
- Placed after NotificationProvider so it can display error notifications
- Should wrap routing and all protected content

**Dependencies:** NotificationProvider (can use notifications on error)

**Initialization:** N/A (error boundary, not a provider)

---

### 5. **NotificationContainer** (Inside ErrorBoundary)

**Location in code:** `AppContents()` after ErrorBoundary

**Why it must be here:**
- Renders the visual notification UI
- Depends on `NotificationProvider` to exist in context
- Must be inside ErrorBoundary so it survives content errors

**Dependencies:** NotificationProvider

**Initialization:** Synchronous (renders empty until notifications are added)

---

### 6. **BrowserRouter** (Inside ErrorBoundary)

**Location in code:** `AppContents()` after NotificationContainer

**Why it must be here:**
- Enables routing for the entire app
- Must wrap `NavigationCancellationProvider` and route definitions
- Allows `useNavigate()` to work in `AuthProvider` (for logout redirects)

**Dependencies:** ErrorBoundary (error recovery), FluentProvider (potential Fluent components in routes)

**Initialization:** Synchronous (sets up routing context)

---

### 7. **NavigationCancellationProvider** (Inside BrowserRouter, before AuthProvider)

**Location in code:** Inside BrowserRouter but wrapping AuthProvider

**Why it must be here:**
- Uses `useLocation()` from React Router to detect route changes
- Must be inside `BrowserRouter` to access routing context
- Must be outside `AuthProvider` so it can wrap all routes including setup and login
- Must be before `AuthProvider` so authentication requests can use the cancellation signals if needed

**Dependencies:** BrowserRouter (uses `useLocation()` hook)

**Initialization:** Synchronous (creates initial AbortController on mount)

**Critical Contract:**
- Every signal obtained from `getNavigationSignal()` is associated with the pathname that was active when it was called.
- On navigation, **all** controllers whose associated pathname no longer matches the current route are aborted — not just the most recent one.
- This ensures that rapidly navigating A → B → C cancels B's in-flight requests even if B's controller was replaced before B's requests had a chance to check it.
- Background tasks (polling, long-lived syncs) can opt out by passing `ignoreCancellation: true` to `getNavigationSignal()` or `useNavigationAbortSignal({ ignoreCancellation: true })`. Opted-out signals are completely independent and are never aborted by navigation.
- Signals are pruned from internal tracking when they are aborted, preventing memory leaks.

**Usage by Consumers:**
- Call `useNavigationAbortSignal()` to get a signal that lives for the duration of the current route
- Pass this signal to API functions that accept `signal?: AbortSignal`
- The signal will be automatically aborted on route change

---

### 8. **AuthProvider** (Inside NavigationCancellationProvider)

**Location in code:** Inside BrowserRouter routes

**Why it must be here:**
- Provides `useAuth()` context
- Calls `useNavigate()` internally (requires BrowserRouter parent)
- Validates session on mount (calls API before rendering children)
- Protected routes (`RequireAuth`, `SetupGuard`) depend on auth context

**Initialization:** **Async** — validates session with backend on mount, shows loading spinner

**Critical Dependency:** Must be inside `BrowserRouter` because:
- Uses `useNavigate()` for logout redirects
- Uses `useLocation()` indirectly through `useNavigate()`

**Timing Issue:**
- On app start, `AuthProvider` validates session with the backend
- While validation is in progress, a loading spinner is shown
- Content rendering is blocked until validation completes or fails
- This ensures authenticated routes don't flicker before auth state is known

---

### 9. **TimezoneProvider** (Inside RequireAuth)

**Location in code:** Wrapped around `MainLayout` within protected routes

**Why it must be here (late in the chain):**
- Fetches timezone from the backend (`GET /api/config`)
- Only available after user is authenticated
- Only needed for protected routes that display dates/times
- Placed late to defer unnecessary API calls until user is authenticated

**Initialization:** **Async** — fetches timezone from backend on mount, defaults to UTC while loading

**Critical Dependency:** Must be inside `AuthProvider` (indirectly) because:
- Cannot be accessed by unauthenticated routes
- Depends on having a valid session to call the API

---

## Order-Sensitive Pitfalls

### ❌ What would break:

1. **Moving ThemeProvider inside AppContents:**
   - `useThemeMode()` call in `AppContents` would fail (hook outside provider)

2. **Moving FluentProvider outside AppContents:**
   - Cannot access `useThemeMode()` to determine theme

3. **Moving NavigationCancellationProvider before BrowserRouter:**
   - `useLocation()` would fail (hook outside routing context)
   - Route changes wouldn't be detected

4. **Moving AuthProvider before BrowserRouter or NavigationCancellationProvider:**
   - `useNavigate()` would fail (hook outside routing context)

5. **Moving TimezoneProvider before AuthProvider:**
   - Unauthenticated users could see timezone fetch attempts
   - API calls would fail without a valid session

6. **Moving ErrorBoundary after AuthProvider:**
   - Auth errors wouldn't be caught by the top-level boundary

7. **Moving NotificationProvider after ErrorBoundary:**
   - Error boundary couldn't display error notifications

---

## Adding New Providers

When adding a new provider in the future:

1. **Identify what it provides** (context, state, side effects)
2. **Identify what it depends on** (which hooks it calls, which APIs it accesses)
3. **Identify what depends on it** (which child components need it)
4. **Place it accordingly:**
   - If it provides data used by many children → place it high up
   - If it depends on auth → place it after AuthProvider
   - If it's order-independent → group it with similar providers
   - If it performs side effects → document when those effects occur

5. **Add it to this document** with its dependency rationale
6. **Add a composition test** validating the new ordering

---

## Testing Provider Order

Comprehensive tests exist in `src/providers/__tests__/providerComposition.test.tsx`:

- ✅ All providers mount without crashing
- ✅ Providers mount in the correct order
- ✅ Auth validation completes before child rendering
- ✅ Timezone fetch doesn't occur for unauthenticated routes
- ✅ Theme changes persist across provider remounts
- ✅ Notifications display correctly across all provider scenarios

These tests act as a regression suite: any refactor that violates the provider contract will fail these tests.

---

## Quick Reference

| Provider | Parent Must Be | Async | Key Dependency |
|----------|---|---|---|
| ThemeProvider | (root) | No | localStorage + system |
| FluentProvider | AppContents | No | theme from ThemeProvider |
| NotificationProvider | FluentProvider | No | none |
| ErrorBoundary | NotificationProvider | No | N/A |
| BrowserRouter | ErrorBoundary | No | none |
| NavigationCancellationProvider | BrowserRouter | No | `useLocation()` for route detection |
| AuthProvider | NavigationCancellationProvider | **Yes** | backend session validation |
| TimezoneProvider | RequireAuth | **Yes** | backend API (requires auth) |
