docs: make provider dependency chain explicit with documentation and tests
This addresses issue #19 by making the implicit provider dependency order explicit and order-sensitive. Changes: 1. Created PROVIDER_ORDER.md - comprehensive documentation explaining: - The provider hierarchy from outermost to innermost - Why each provider must be at its position - Order-sensitive pitfalls and what would break - Guidelines for adding new providers in the future 2. Added provider composition tests (providerComposition.test.tsx): - 13 comprehensive tests validating provider order and dependencies - Tests verify all providers mount correctly - Tests check that hooks only work inside correct providers - Tests validate async initialization (AuthProvider, TimezoneProvider) - Tests verify theme persistence and notification propagation 3. Updated App.tsx with inline documentation: - Added detailed provider order contract in JSDoc header - Inline comments explaining each provider's position - Reference to PROVIDER_ORDER.md for detailed rationale 4. Updated Web-Development.md: - Added new section 5.5 'Provider Order Contract' - Documents provider hierarchy and rationale - Links to comprehensive provider documentation - References regression test suite All tests pass. TypeScript compilation succeeds. Build succeeds. The provider order is now explicit and future refactors can validate compliance through the regression test suite. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
## 18) Provider dependency chain is implicit
|
||||
## 19) Provider dependency chain is implicit
|
||||
- Where found:
|
||||
- [frontend/src/App.tsx](frontend/src/App.tsx)
|
||||
- Why this is needed:
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 19) Loading UX lacks progressive/skeleton states
|
||||
## 20) Loading UX lacks progressive/skeleton states
|
||||
- Where found:
|
||||
- [frontend/src/pages](frontend/src/pages)
|
||||
- Why this is needed:
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 20) Silent auth error swallow in fetch error utility
|
||||
## 21) Silent auth error swallow in fetch error utility
|
||||
- Where found:
|
||||
- [frontend/src/utils/fetchError.ts](frontend/src/utils/fetchError.ts)
|
||||
- Why this is needed:
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 21) Magic strings are scattered in frontend storage keys
|
||||
## 22) Magic strings are scattered in frontend storage keys
|
||||
- Where found:
|
||||
- [frontend/src/providers/AuthProvider.tsx](frontend/src/providers/AuthProvider.tsx)
|
||||
- [frontend/src/layouts/MainLayout.tsx](frontend/src/layouts/MainLayout.tsx)
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 22) No global cancellation policy on route transitions
|
||||
## 23) No global cancellation policy on route transitions
|
||||
- Where found:
|
||||
- [frontend/src/hooks](frontend/src/hooks)
|
||||
- Why this is needed:
|
||||
@@ -91,7 +91,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 23) API response wrapper shape is inconsistent
|
||||
## 24) API response wrapper shape is inconsistent
|
||||
- Where found:
|
||||
- [backend/app/routers/dashboard.py](backend/app/routers/dashboard.py)
|
||||
- [backend/app/routers/jails.py](backend/app/routers/jails.py)
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 24) No canonical snake_case/camelCase serialization policy
|
||||
## 25) No canonical snake_case/camelCase serialization policy
|
||||
- Where found:
|
||||
- [backend/app/models/server.py](backend/app/models/server.py)
|
||||
- [frontend/src/types/server.ts](frontend/src/types/server.ts)
|
||||
@@ -132,7 +132,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 25) Pagination contract is not standardized across endpoints
|
||||
## 26) Pagination contract is not standardized across endpoints
|
||||
- Where found:
|
||||
- [backend/app/routers/dashboard.py](backend/app/routers/dashboard.py)
|
||||
- [backend/app/routers/history.py](backend/app/routers/history.py)
|
||||
@@ -152,7 +152,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 26) Error response body shape is inconsistent
|
||||
## 27) Error response body shape is inconsistent
|
||||
- Where found:
|
||||
- [backend/app/main.py](backend/app/main.py)
|
||||
- [backend/app/routers](backend/app/routers)
|
||||
@@ -172,7 +172,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 27) Login failure delay can enable app-layer DoS
|
||||
## 28) Login failure delay can enable app-layer DoS
|
||||
- Where found:
|
||||
- [backend/app/routers/auth.py](backend/app/routers/auth.py#L110)
|
||||
- Why this is needed:
|
||||
@@ -190,7 +190,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 28) Blocklist URL validation has DNS-rebinding window
|
||||
## 29) Blocklist URL validation has DNS-rebinding window
|
||||
- Where found:
|
||||
- [backend/app/utils/ip_utils.py](backend/app/utils/ip_utils.py#L145)
|
||||
- [backend/app/services/blocklist_service.py](backend/app/services/blocklist_service.py#L81)
|
||||
@@ -210,7 +210,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 29) Setup persistence is non-atomic across DB contexts
|
||||
## 30) Setup persistence is non-atomic across DB contexts
|
||||
- Where found:
|
||||
- [backend/app/services/setup_service.py](backend/app/services/setup_service.py)
|
||||
- [backend/app/repositories/settings_repo.py](backend/app/repositories/settings_repo.py)
|
||||
@@ -229,7 +229,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 30) Fire-and-forget reschedule may fail silently
|
||||
## 31) Fire-and-forget reschedule may fail silently
|
||||
- Where found:
|
||||
- [backend/app/tasks/blocklist_import.py](backend/app/tasks/blocklist_import.py#L108)
|
||||
- Why this is needed:
|
||||
@@ -247,7 +247,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 31) RateLimiter cleanup function is not scheduled/used
|
||||
## 32) RateLimiter cleanup function is not scheduled/used
|
||||
- Where found:
|
||||
- [backend/app/utils/rate_limiter.py](backend/app/utils/rate_limiter.py#L84)
|
||||
- [backend/app/startup.py](backend/app/startup.py)
|
||||
@@ -266,7 +266,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 32) Trusted proxy configuration is hardcoded in auth router
|
||||
## 33) Trusted proxy configuration is hardcoded in auth router
|
||||
- Where found:
|
||||
- [backend/app/routers/auth.py](backend/app/routers/auth.py#L46)
|
||||
- [backend/app/utils/client_ip.py](backend/app/utils/client_ip.py)
|
||||
@@ -286,7 +286,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 33) Setup redirect allowlist uses broad prefix matching
|
||||
## 34) Setup redirect allowlist uses broad prefix matching
|
||||
- Where found:
|
||||
- [backend/app/main.py](backend/app/main.py#L434)
|
||||
- Why this is needed:
|
||||
@@ -304,7 +304,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 34) API client sends JSON and CSRF header for every request method
|
||||
## 35) API client sends JSON and CSRF header for every request method
|
||||
- Where found:
|
||||
- [frontend/src/api/client.ts](frontend/src/api/client.ts)
|
||||
- Why this is needed:
|
||||
@@ -323,7 +323,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 35) Polling continues when tab is not visible
|
||||
## 36) Polling continues when tab is not visible
|
||||
- Where found:
|
||||
- [frontend/src/hooks/usePolledData.ts](frontend/src/hooks/usePolledData.ts#L90)
|
||||
- [frontend/src/hooks/useBlocklistStatus.ts](frontend/src/hooks/useBlocklistStatus.ts)
|
||||
@@ -342,7 +342,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 36) Multi-worker safety check depends on one environment variable
|
||||
## 37) Multi-worker safety check depends on one environment variable
|
||||
- Where found:
|
||||
- [backend/app/startup.py](backend/app/startup.py#L61)
|
||||
- Why this is needed:
|
||||
@@ -360,7 +360,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 37) History archive query paths may need explicit indexing plan
|
||||
## 38) History archive query paths may need explicit indexing plan
|
||||
- Where found:
|
||||
- [backend/app/db.py](backend/app/db.py)
|
||||
- [backend/app/repositories/history_archive_repo.py](backend/app/repositories/history_archive_repo.py)
|
||||
@@ -381,7 +381,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 38) No explicit DI container strategy for backend service graph
|
||||
## 39) No explicit DI container strategy for backend service graph
|
||||
- Where found:
|
||||
- [backend/app/dependencies.py](backend/app/dependencies.py)
|
||||
- [backend/app/services](backend/app/services)
|
||||
@@ -400,7 +400,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 39) Frontend and backend observability are not aligned
|
||||
## 40) Frontend and backend observability are not aligned
|
||||
- Where found:
|
||||
- [backend/app/main.py](backend/app/main.py)
|
||||
- [frontend/src](frontend/src)
|
||||
|
||||
@@ -591,6 +591,72 @@ function BanCard({ isHighlighted }: BanCardProps): JSX.Element {
|
||||
|
||||
---
|
||||
|
||||
## 5.5. Provider Order Contract
|
||||
|
||||
The frontend wraps the application in multiple context providers. **The order in which these providers are nested is order-sensitive and critical for correct operation.** Future refactors must respect this contract or fail silently.
|
||||
|
||||
### Provider Hierarchy
|
||||
|
||||
From outermost to innermost:
|
||||
|
||||
1. **ThemeProvider** — must be outermost; provides theme context to `AppContents` so it can determine the theme and pass it to `FluentProvider`
|
||||
2. **FluentProvider** — receives theme from `useThemeMode()` within `AppContents`; must wrap all Fluent UI consumers
|
||||
3. **NotificationProvider** — provides notification service to all descendants; placed before error boundaries
|
||||
4. **ErrorBoundary** (top-level) — catches catastrophic errors; placed before routing
|
||||
5. **BrowserRouter** — enables routing; must wrap `AuthProvider` (which uses `useNavigate()`)
|
||||
6. **AuthProvider** — validates session on mount; must be inside `BrowserRouter`; shows loading spinner while validating
|
||||
7. **TimezoneProvider** — must be inside authenticated context (inside `RequireAuth`); fetches timezone from backend on mount
|
||||
|
||||
### Why This Order Matters
|
||||
|
||||
**ThemeProvider must be outermost:**
|
||||
- `AppContents` calls `useThemeMode()` to get the current theme
|
||||
- Cannot call a hook outside its provider
|
||||
- `FluentProvider` receives the theme as a prop
|
||||
|
||||
**FluentProvider must be inside AppContents:**
|
||||
- Needs to read `useThemeMode()` hook
|
||||
- Must wrap all Fluent UI components
|
||||
|
||||
**NotificationProvider before ErrorBoundary:**
|
||||
- Error boundaries may emit notifications on error
|
||||
- Provides notification service to error recovery handlers
|
||||
|
||||
**AuthProvider inside BrowserRouter:**
|
||||
- Uses `useNavigate()` internally for logout redirects
|
||||
- `useNavigate()` requires `BrowserRouter` context
|
||||
|
||||
**TimezoneProvider last:**
|
||||
- Fetches timezone from backend (requires authentication)
|
||||
- Only needed for authenticated routes
|
||||
- Placed inside `RequireAuth` guard
|
||||
|
||||
### Adding New Providers
|
||||
|
||||
When adding a new provider in the future:
|
||||
|
||||
1. Identify what it depends on (which hooks or APIs it calls)
|
||||
2. Identify what depends on it (which child components use it)
|
||||
3. Place it accordingly in the hierarchy
|
||||
4. **Update this section** with its rationale
|
||||
5. **Add or update tests** in `src/providers/__tests__/providerComposition.test.tsx` validating the order
|
||||
|
||||
### Provider Order Regression Tests
|
||||
|
||||
Comprehensive tests in `src/providers/__tests__/providerComposition.test.tsx` validate:
|
||||
|
||||
- ✅ All providers mount without crashing
|
||||
- ✅ Providers are accessible from their descendant components
|
||||
- ✅ Order-dependent initialization works correctly (auth validation, timezone fetch)
|
||||
- ✅ Theme persistence works across re-renders
|
||||
- ✅ Notifications propagate correctly
|
||||
|
||||
**Do not refactor the provider hierarchy without running these tests first.**
|
||||
|
||||
For detailed context and rationale, see `src/providers/PROVIDER_ORDER.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Component Rules
|
||||
|
||||
- One component per file. The filename matches the component name: `BanTable.tsx` exports `BanTable`.
|
||||
|
||||
Reference in New Issue
Block a user