# Architecture Knowledge Base
**Last Updated:** December 14, 2025  
**Purpose:** Critical architectural patterns, common issues, and solutions reference

---

## 🔥 CRITICAL FIXES - December 2025

### PERMANENT FIX: Django Admin Custom Sidebar Not Showing on Subpages
**ROOT CAUSE**: Django's `ModelAdmin` view methods (`changelist_view`, `change_view`, etc.) do not call `AdminSite.each_context()`, so custom sidebar logic defined in `site.py` was bypassed on model list/detail/edit pages.

**SOLUTION IMPLEMENTED**:
1. ✅ Created `Igny8ModelAdmin` base class extending `UnfoldModelAdmin`
2. ✅ Overrides all view methods to inject `extra_context` with custom sidebar
3. ✅ Applied to 46+ admin classes across all modules
4. ✅ Sidebar now consistent on homepage, app index, and ALL model pages

**Files Modified**: `backend/igny8_core/admin/base.py`, all `*/admin.py` files

### 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 `<Routes>` 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 `<Router>` context
- Uses `useLocation()` and `useNavigate()` hooks
- Cannot be used outside `<Routes>` tree

**Common Error:**
```
Error: useLocation() may be used only in the context of a <Router> component
```

**Cause:** Component rendered outside React Router context

**Solution:** Ensure component is within `<Route>` 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 <Router>" 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 `</div>`)

**Debugging Strategy:**
1. Use TypeScript compiler: `npx tsc --noEmit <file>`
2. Count opening vs closing tags: `grep -c "<div" vs grep -c "</div>"`
3. Check conditionals have matching closing parens/braces

**Common Pattern:**
```tsx
{condition && (
  <div>
    {/* Content */}
  </div>
  {/* 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.*
