# Architecture Knowledge Base **Last Updated:** December 9, 2025 **Purpose:** Critical architectural patterns, common issues, and solutions reference --- ## 🔥 CRITICAL FIXES - December 9, 2025 ### PERMANENT FIX: User Swapping / Random Logout Issue **ROOT CAUSE**: Django's database-backed sessions with in-memory user caching caused cross-request contamination at the process level. **SOLUTION IMPLEMENTED**: 1. ✅ Redis-backed sessions (`SESSION_ENGINE = 'django.contrib.sessions.backends.cache'`) 2. ✅ Custom authentication backend without caching (`NoCacheModelBackend`) 3. ✅ Session integrity validation (stores and verifies account_id/user_id on every request) 4. ✅ Middleware never mutates `request.user` (uses Django's set value directly) **See**: `CRITICAL-BUG-FIXES-DEC-2025.md` for complete details. ### PERMANENT FIX: useNavigate / useLocation Errors During HMR **ROOT CAUSE**: Individual Suspense boundaries per route lost React Router context during Hot Module Replacement. **SOLUTION IMPLEMENTED**: 1. ✅ Single top-level Suspense boundary around entire `` component 2. ✅ Removed 100+ individual Suspense wrappers from route elements 3. ✅ Router context now persists through HMR automatically **See**: `CRITICAL-BUG-FIXES-DEC-2025.md` for complete details. --- ## Table of Contents 1. [Authentication & Session Management](#authentication--session-management) 2. [Site/Sector Architecture](#sitesector-architecture) 3. [State Management & Race Conditions](#state-management--race-conditions) 4. [Permission System](#permission-system) 5. [Frontend Component Dependencies](#frontend-component-dependencies) 6. [Common Pitfalls & Solutions](#common-pitfalls--solutions) --- ## Authentication & Session Management ### Token Persistence Architecture **Problem Pattern:** - Zustand persist middleware writes to localStorage asynchronously - API calls can happen before tokens are persisted - Results in 403 "Authentication credentials were not provided" errors **Solution Implemented:** ```typescript // In authStore.ts login/register functions // CRITICAL: Immediately persist tokens synchronously after setting state const authState = { state: { user, token, refreshToken, isAuthenticated: true }, version: 0 }; localStorage.setItem('auth-storage', JSON.stringify(authState)); ``` **Key Principle:** Always write tokens to localStorage synchronously in auth actions, don't rely solely on persist middleware. --- ### Logout & State Cleanup **WRONG APPROACH (causes race conditions):** ```typescript logout: () => { localStorage.clear(); // ❌ BREAKS EVERYTHING set({ user: null, token: null }); } ``` **CORRECT APPROACH:** ```typescript logout: () => { // ✅ Selective removal - only auth-related keys const authKeys = ['auth-storage', 'site-storage', 'sector-storage', 'billing-storage']; authKeys.forEach(key => localStorage.removeItem(key)); // ✅ Reset dependent stores explicitly useSiteStore.setState({ activeSite: null }); useSectorStore.setState({ activeSector: null, sectors: [] }); set({ user: null, token: null, isAuthenticated: false }); } ``` **Key Principle:** Never use `localStorage.clear()` - it breaks Zustand persist middleware initialization. Always selectively remove keys. --- ### 403 Error Handling **Problem Pattern:** - 403 errors thrown before checking if it's an auth error - Token validation code becomes unreachable - Invalid tokens persist in localStorage **WRONG ORDER:** ```typescript // In api.ts if (response.status === 403) { throw new Error(response.statusText); // ❌ Thrown immediately } // This code NEVER runs (unreachable): if (errorData?.detail?.includes('Authentication credentials')) { logout(); // Never called! } ``` **CORRECT ORDER:** ```typescript // Check for auth errors FIRST, then throw if (response.status === 403) { const errorData = JSON.parse(text); // ✅ Check authentication BEFORE throwing if (errorData?.detail?.includes('Authentication credentials')) { const authState = useAuthStore.getState(); if (authState?.isAuthenticated) { authState.logout(); window.location.href = '/signin'; } } // Now throw the error throw new Error(errorMessage); } ``` **Key Principle:** Handle authentication errors before throwing. Order matters in error handling logic. --- ## Site/Sector Architecture ### Data Hierarchy ``` Account (Tenant) └── Site (e.g., myblog.com) └── Sector (e.g., Technology, Health) └── Keywords └── Clusters └── Ideas └── Content ``` ### Where Sectors Are Used (Global Context) **USES SECTORS (requires site/sector selection):** - ✅ Planner Module (Keywords, Clusters, Ideas) - ✅ Writer Module (Tasks, Content, Drafts, Published) - ✅ Linker Module (Internal linking) - ✅ Optimizer Module (Content optimization) - ✅ Setup/Add Keywords page - ✅ Seed Keywords reference data **DOES NOT USE SECTORS (account-level only):** - ❌ Billing/Plans pages (`/account/*`) - ❌ Account Settings - ❌ Team Management - ❌ User Profile - ❌ Admin Dashboard - ❌ System Settings ### Sector Loading Pattern **Architecture Decision:** - Sectors loaded by **PageHeader component** (not AppLayout) - Only loads when `hideSiteSector={false}` prop is set - Account/billing pages pass `hideSiteSector={true}` to skip loading **Implementation:** ```typescript // PageHeader.tsx useEffect(() => { if (hideSiteSector) return; // Skip for account pages const currentSiteId = activeSite?.id ?? null; if (currentSiteId && activeSite?.is_active) { loadSectorsForSite(currentSiteId); } }, [activeSite?.id, hideSiteSector]); ``` **Key Principle:** Lazy-load sectors only when components need them. Don't load globally for all pages. --- ### Site/Sector Store Persistence **Storage Keys:** - `site-storage` - Active site selection - `sector-storage` - Active sector selection **Reset Pattern:** ```typescript // When site changes, reset sector if it belongs to different site if (currentSector && currentSector.site_id !== newSiteId) { set({ activeSector: null }); localStorage.setItem('sector-storage', JSON.stringify({ state: { activeSector: null }, version: 0 })); } ``` **Key Principle:** Sector selection is site-scoped. Always validate sector belongs to active site. --- ## State Management & Race Conditions ### Common Race Condition Patterns #### 1. User Switching **Problem:** Rapid logout → login leaves stale state in stores **Solution:** ```typescript logout: () => { // Reset ALL dependent stores explicitly import('./siteStore').then(({ useSiteStore }) => { useSiteStore.setState({ activeSite: null, loading: false, error: null }); }); import('./sectorStore').then(({ useSectorStore }) => { useSectorStore.setState({ activeSector: null, sectors: [], loading: false, error: null }); }); } ``` #### 2. API Calls Before Token Persistence **Problem:** API calls happen before Zustand persist writes token **Solution:** Synchronous localStorage write immediately after state update (see Authentication section) #### 3. Module Loading Failures **Problem:** 404 errors during page navigation cause module loading to fail **Solution:** Ensure API endpoints exist before pages try to load them. Use conditional rendering based on route. --- ### Zustand Persist Middleware Gotchas **Issue 1: Version Mismatch** ```typescript // Stored format { state: { user, token }, version: 0 } // If version changes, persist middleware clears state ``` **Issue 2: Async Hydration** - State rehydration from localStorage is async - Can cause brief flash of "no user" state **Solution:** Use loading states or check both store AND localStorage: ```typescript const getAuthToken = (): string | null => { // Try Zustand store first const authState = useAuthStore.getState(); if (authState?.token) return authState.token; // Fallback to localStorage const stored = localStorage.getItem('auth-storage'); return JSON.parse(stored)?.state?.token || null; }; ``` --- ## Permission System ### Superuser/Developer Bypass Pattern **Critical Locations for Bypass:** 1. Middleware - `auth/middleware.py` 2. Permission Classes - `api/permissions.py` 3. ViewSet Querysets - `api/base.py` 4. Validation Functions - `auth/utils.py` **Standard Bypass Check:** ```python def check_bypass(user): return ( user.is_superuser or user.role == 'developer' or is_system_account_user(user) ) ``` **Apply at ALL levels:** - Middleware request validation - DRF permission `has_permission()` - ViewSet `get_queryset()` filtering - Custom validation functions **Key Principle:** Bypass checks must be consistent across all permission layers. Missing one layer breaks superuser access. --- ### System Account Pattern **Reserved Accounts:** - `aws-admin` - System automation account - `default-account` - Default tenant fallback **Check Function:** ```python def is_system_account_user(user): if not user or not user.account: return False return user.account.slug in ['aws-admin', 'default-account'] ``` **Usage:** Always include in bypass checks alongside superuser/developer. --- ## Frontend Component Dependencies ### PageHeader Component **Dependencies:** - `useSiteStore` - Active site - `useSectorStore` - Active sector - `SiteAndSectorSelector` - Dropdown component **Props:** - `hideSiteSector: boolean` - Skip site/sector display and loading - `title: string` - Page title - `navigation: ReactNode` - Optional module tabs **Used By:** - All Planner pages - All Writer pages - All Optimizer pages - Setup pages - Seed Keywords page **NOT Used By:** - Account/billing pages (use plain headers instead) --- ### Module Navigation Pattern **Component:** `ModuleNavigationTabs.tsx` **CRITICAL:** Must be wrapped in `` context - Uses `useLocation()` and `useNavigate()` hooks - Cannot be used outside `` tree **Common Error:** ``` Error: useLocation() may be used only in the context of a component ``` **Cause:** Component rendered outside React Router context **Solution:** Ensure component is within `` element in App.tsx --- ## Common Pitfalls & Solutions ### Pitfall 1: Frontend 403 Errors After User Switch **Symptoms:** - "Authentication credentials were not provided" - User appears logged in but API calls fail - Manually clearing cache fixes it **Root Cause:** Invalid tokens persisting in localStorage after logout **Solution:** 1. Check 403 handler runs BEFORE throwing error 2. Ensure logout clears specific auth keys (not `localStorage.clear()`) 3. Add immediate token persistence after login **Prevention:** See "Authentication & Session Management" section --- ### Pitfall 2: Sector 404 Errors on Billing Pages **Symptoms:** - `GET /v1/auth/sites/{id}/sectors/` returns 404 - "Failed to fetch dynamically imported module" error - Billing pages don't load **Root Cause:** AppLayout loading sectors for ALL pages globally **Solution:** Move sector loading to PageHeader component (lazy loading) **Prevention:** Only load data when components that need it are mounted --- ### Pitfall 3: Module Loading Failures After Git Commits **Symptoms:** - React Router context errors - "useLocation() may be used only in context of " errors - Pages work after rebuild but fail after git push **Root Cause:** Docker build cache not invalidated properly **Solution:** ```bash # Force clean rebuild docker compose -f docker-compose.app.yml down docker compose -f docker-compose.app.yml build --no-cache igny8_frontend docker compose -f docker-compose.app.yml up -d ``` **Prevention:** Use `--no-cache` flag when rebuilding after major changes --- ### Pitfall 4: Plan Selection Issues in Pricing Page **Symptoms:** - Monthly/Annual toggle missing - Pre-selected plan not highlighted - Discount calculation wrong **Root Cause:** 1. PricingTable component missing `showToggle` prop 2. Backend missing `is_featured` and `annual_discount_percent` fields 3. Frontend not calculating annual price from discount **Solution:** 1. Add fields to Plan model with migration 2. Pass `annualDiscountPercent` to PricingTable 3. Calculate: `annualPrice = monthlyPrice * 12 * (1 - discount/100)` **Files Modified:** - `backend/igny8_core/auth/models.py` - `backend/igny8_core/auth/serializers.py` - `frontend/src/services/billing.api.ts` - `frontend/src/components/ui/pricing-table/PricingTable.tsx` --- ### Pitfall 5: Adjacent JSX Elements Error **Symptoms:** - "Adjacent JSX elements must be wrapped in an enclosing tag" - Build fails but line numbers don't help **Root Cause:** Mismatched opening/closing tags (usually missing ``) **Debugging Strategy:** 1. Use TypeScript compiler: `npx tsc --noEmit ` 2. Count opening vs closing tags: `grep -c ""` 3. Check conditionals have matching closing parens/braces **Common Pattern:** ```tsx {condition && (
{/* Content */}
{/* Missing closing parenthesis causes "adjacent elements" error */} } ``` **Solution:** Ensure every opening bracket has matching close bracket --- ## Best Practices Summary ### State Management ✅ **DO:** Immediately persist auth tokens synchronously ✅ **DO:** Selectively remove localStorage keys ✅ **DO:** Reset dependent stores on logout ❌ **DON'T:** Use `localStorage.clear()` ❌ **DON'T:** Rely solely on Zustand persist middleware timing ### Error Handling ✅ **DO:** Check authentication errors BEFORE throwing ✅ **DO:** Force logout on invalid tokens ✅ **DO:** Redirect to login after logout ❌ **DON'T:** Throw errors before checking auth status ❌ **DON'T:** Leave invalid tokens in storage ### Component Architecture ✅ **DO:** Lazy-load data at component level ✅ **DO:** Skip unnecessary data loading (hideSiteSector pattern) ✅ **DO:** Keep components in Router context ❌ **DON'T:** Load data globally in AppLayout ❌ **DON'T:** Use Router hooks outside Router context ### Permission System ✅ **DO:** Implement bypass at ALL permission layers ✅ **DO:** Include system accounts in bypass checks ✅ **DO:** Use consistent bypass logic everywhere ❌ **DON'T:** Forget middleware layer bypass ❌ **DON'T:** Mix permission approaches ### Docker Builds ✅ **DO:** Use `--no-cache` after major changes ✅ **DO:** Restart containers after rebuilds ✅ **DO:** Check logs for module loading errors ❌ **DON'T:** Trust build cache after git commits ❌ **DON'T:** Deploy without testing fresh build --- ## Quick Reference: File Locations ### Authentication - Token handling: `frontend/src/services/api.ts` - Auth store: `frontend/src/store/authStore.ts` - Middleware: `backend/igny8_core/auth/middleware.py` ### Permissions - Permission classes: `backend/igny8_core/api/permissions.py` - Base viewsets: `backend/igny8_core/api/base.py` - Validation utils: `backend/igny8_core/auth/utils.py` ### Site/Sector - Site store: `frontend/src/store/siteStore.ts` - Sector store: `frontend/src/store/sectorStore.ts` - PageHeader: `frontend/src/components/common/PageHeader.tsx` ### Billing - Billing API: `frontend/src/services/billing.api.ts` - Plans page: `frontend/src/pages/account/PlansAndBillingPage.tsx` - Plan model: `backend/igny8_core/auth/models.py` --- **End of Knowledge Base** *Update this document when architectural patterns change or new common issues are discovered.*