diff --git a/fixes-kb.md b/fixes-kb.md new file mode 100644 index 00000000..34f6af33 --- /dev/null +++ b/fixes-kb.md @@ -0,0 +1,552 @@ +# 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 `` 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.*