diff --git a/docs/plans/2026-02-08-admin-panel-design.md b/docs/plans/2026-02-08-admin-panel-design.md
new file mode 100644
index 00000000..ce2bcd81
--- /dev/null
+++ b/docs/plans/2026-02-08-admin-panel-design.md
@@ -0,0 +1,1703 @@
+# Admin Panel Design - ResolutionFlow
+
+**Date:** February 8, 2026
+**Status:** Design Complete - Ready for Implementation
+**Scope:** Phase 1 - Essential Admin Tools
+
+## Overview
+
+This document specifies a comprehensive admin panel for ResolutionFlow administrators to manage site-wide settings, users, and platform configuration without direct database access.
+
+### Goals
+
+1. **Operational Support** - Manage users, invite codes, and troubleshoot issues
+2. **Growth Insights** - Track platform metrics and user activity
+3. **Platform Control** - Configure plans, features, and settings
+
+### Primary Pain Point
+
+Currently managing settings manually in database via SQL commands. This admin panel eliminates the need for direct database access.
+
+---
+
+## Architecture
+
+### Route Structure
+
+```
+/admin (super_admin only)
+ ├── /dashboard - Overview metrics and activity
+ ├── /users - User management
+ ├── /invite-codes - Invite code management
+ ├── /plan-limits - Plan configuration + account overrides
+ ├── /feature-flags - Feature flags with plan defaults
+ ├── /settings - Platform settings (maintenance mode)
+ ├── /audit-logs - Audit log viewer
+ └── /categories - Global categories (NEW)
+
+/account (account_owner only)
+ └── /categories - Team categories (NEW)
+```
+
+### Layout Components
+
+**AdminLayout** (240px sidebar):
+```typescript
+export function AdminLayout() {
+ const [sidebarOpen, setSidebarOpen] = useState(true)
+
+ return (
+
+
setSidebarOpen(false)} />
+
+
+ )
+}
+```
+
+**Sidebar Behavior:**
+- Desktop (≥1024px): Always visible, no overlay
+- Mobile (<1024px): Overlay with backdrop fade animation
+- No state persistence (always starts open on desktop)
+
+**AccountLayout** (simple wrapper):
+```typescript
+export function AccountLayout() {
+ return (
+
+
+
+
+
+ )
+}
+```
+
+### Navigation
+
+**Admin Sidebar Items:**
+```typescript
+const navItems = [
+ { path: '/admin/dashboard', label: 'Dashboard', icon: LayoutDashboard },
+ { path: '/admin/users', label: 'Users', icon: Users },
+ { path: '/admin/invite-codes', label: 'Invite Codes', icon: Mail },
+ { path: '/admin/plan-limits', label: 'Plan Limits', icon: Layers },
+ { path: '/admin/feature-flags', label: 'Feature Flags', icon: Flag },
+ { path: '/admin/settings', label: 'Platform Settings', icon: Settings },
+ { path: '/admin/audit-logs', label: 'Audit Logs', icon: FileText },
+ { path: '/admin/categories', label: 'Global Categories', icon: FolderTree }
+]
+```
+
+**Main App Navigation:**
+- "Admin Panel" link (Shield icon) in user dropdown (super_admin only)
+- "Account Settings" dropdown menu item for account_owner (includes "Team Categories")
+- "Back to App" always navigates to `/trees`
+
+### Permissions & Protection
+
+**Layout Level:**
+```typescript
+
+
+
+
+
+```
+
+**Behavior:**
+- Silent redirect to `/trees` on permission denied (no toast notification)
+- Backend always validates permissions (defense in depth)
+- Error boundary catches React errors, shows fallback UI
+
+### Routing Configuration
+
+```typescript
+{
+ path: 'admin',
+ element: (
+
+
+
+
+
+ ),
+ children: [
+ { index: true, element: },
+ { path: 'dashboard', element: }> },
+ { path: 'users', element: }> },
+ { path: 'invite-codes', element: }> },
+ { path: 'plan-limits', element: }> },
+ { path: 'feature-flags', element: }> },
+ { path: 'settings', element: }> },
+ { path: 'audit-logs', element: }> },
+ { path: 'categories', element: }> },
+ { path: '*', element: }
+ ]
+},
+{
+ path: 'account',
+ element: (
+
+
+
+
+
+ ),
+ children: [
+ { index: true, element: },
+ { path: 'categories', element: }> },
+ { path: '*', element: }
+ ]
+}
+```
+
+---
+
+## Reusable Components
+
+### DataTable
+
+Generic paginated table with sorting support.
+
+```typescript
+interface Column {
+ key: string
+ header: string
+ render: (item: T) => ReactNode // REQUIRED - no fallback
+ sortable?: boolean
+ width?: string
+}
+
+interface DataTableProps {
+ columns: Column[]
+ data: T[]
+ isLoading?: boolean
+ emptyMessage?: string
+}
+
+ {user.email},
+ sortable: true
+ },
+ {
+ key: 'role',
+ header: 'Role',
+ render: (user) => {user.role}
+ }
+ ]}
+ data={users}
+ isLoading={isLoading}
+/>
+```
+
+**Features:**
+- Generic type support (``)
+- Optional loading state (skeleton rows)
+- Empty state via `EmptyState` component
+- Sortable columns (client-side)
+- Responsive width control
+
+### Pagination
+
+Smart pagination with ellipsis for large page counts.
+
+```typescript
+interface PaginationProps {
+ currentPage: number
+ totalPages: number
+ onPageChange: (page: number) => void
+ itemsPerPage?: number
+ totalItems?: number
+}
+
+
+```
+
+**Ellipsis Logic:**
+- Always show: First, Last, Current, Current±1
+- Show ellipsis when gap > 1
+- Example: `1 ... 5 6 [7] 8 9 ... 20`
+
+### ActionMenu
+
+Three-dot dropdown menu for row actions.
+
+```typescript
+interface MenuItem {
+ label: string
+ icon: LucideIcon
+ onClick: () => void
+ variant?: 'default' | 'destructive'
+ disabled?: boolean
+}
+
+interface ActionMenuProps {
+ items: MenuItem[]
+ align?: 'start' | 'end'
+}
+
+ handleEdit(item.id) },
+ { label: 'Delete', icon: Trash2, onClick: () => handleDelete(item.id), variant: 'destructive' }
+ ]}
+/>
+```
+
+**Phase 1:** No submenus (single-level only)
+
+### StatusBadge
+
+Consistent badge styling with 4 variants.
+
+```typescript
+interface StatusBadgeProps {
+ variant: 'success' | 'destructive' | 'warning' | 'default'
+ children: ReactNode
+}
+
+Active
+Pending
+Deactivated
+```
+
+**Styling:**
+```typescript
+const variants = {
+ success: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400',
+ destructive: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400',
+ warning: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400',
+ default: 'bg-muted text-muted-foreground'
+}
+```
+
+### EmptyState
+
+Consistent empty state component.
+
+```typescript
+interface EmptyStateProps {
+ icon: LucideIcon
+ title: string
+ description?: string
+ action?: {
+ label: string
+ onClick: () => void
+ }
+}
+
+
+```
+
+### SearchInput
+
+Debounced search input with 300ms delay.
+
+```typescript
+interface SearchInputProps {
+ value: string
+ onChange: (value: string) => void
+ placeholder?: string
+ className?: string
+}
+
+
+```
+
+**Implementation:**
+```typescript
+import { debounce } from 'lodash'
+
+const debouncedOnChange = useMemo(
+ () => debounce((value: string) => onChange(value), 300),
+ [onChange]
+)
+
+useEffect(() => {
+ return () => debouncedOnChange.cancel()
+}, [debouncedOnChange])
+```
+
+### PageHeader
+
+Consistent page header with title and optional action.
+
+```typescript
+interface PageHeaderProps {
+ title: string
+ description?: string
+ action?: {
+ label: string
+ icon?: LucideIcon
+ onClick: () => void
+ }
+}
+
+ setShowCreateModal(true) }}
+/>
+```
+
+**Phase 1:** Single action only (no multiple actions)
+
+---
+
+## Page Specifications
+
+### 1. Admin Dashboard
+
+**Route:** `/admin/dashboard`
+**Permission:** super_admin
+**Scope:** Minimal (read-only overview)
+
+**Layout:**
+```
+┌─────────────────────────────────────────────┐
+│ Dashboard [Refresh] │
+├─────────────────────────────────────────────┤
+│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
+│ │Total │ │Active│ │Paid │ │Trees │ │
+│ │Users │ │ Subs │ │ Accts│ │Count │ │
+│ └──────┘ └──────┘ └──────┘ └──────┘ │
+│ │
+│ System Status │
+│ ┌──────────────────────────────────────────┐│
+│ │ API: Healthy DB: Connected Cache: OK ││
+│ └──────────────────────────────────────────┘│
+│ │
+│ Recent Activity │
+│ ┌──────────────────────────────────────────┐│
+│ │ • User X registered (2 min ago) ││
+│ │ • Admin Y updated plan limits (5 min ago)││
+│ │ • User Z started session (10 min ago) ││
+│ └──────────────────────────────────────────┘│
+│ │
+│ Quick Links │
+│ [Manage Users] [View Audit Logs] [...] │
+└─────────────────────────────────────────────┘
+```
+
+**Metrics (4 cards):**
+- Total Users
+- Active Subscriptions (status = 'active' OR 'trialing')
+- Paid Accounts (plan IN ('pro', 'team'))
+- Total Trees
+
+**System Status:**
+- API: Check if server is responding
+- Database: Check connection status
+- Cache: Check settings cache (if applicable)
+
+**Recent Activity:**
+- Last 10 audit log entries
+- Format: `{user_name} {action} {entity_type} ({time_ago})`
+- Click to view full audit logs (filtered by item)
+
+**Quick Links:**
+- Manage Users → `/admin/users`
+- View Audit Logs → `/admin/audit-logs`
+- Platform Settings → `/admin/settings`
+
+**API Endpoints (NEW):**
+```python
+# GET /api/v1/admin/dashboard/metrics
+{
+ "total_users": 142,
+ "active_subscriptions": 38,
+ "paid_accounts": 12,
+ "total_trees": 567
+}
+
+# GET /api/v1/admin/dashboard/activity
+[
+ {
+ "id": "uuid",
+ "user_name": "John Doe",
+ "user_email": "john@example.com",
+ "action": "user_role_changed",
+ "entity_type": "user",
+ "entity_id": "uuid",
+ "timestamp": "2026-02-08T10:30:00Z",
+ "details": { "old_role": "viewer", "new_role": "engineer" }
+ }
+]
+```
+
+**Features:**
+- Manual refresh only (no auto-refresh)
+- Metric cards do NOT link to filtered pages
+- Activity feed links to audit logs with filter
+
+---
+
+### 2. User Management
+
+**Route:** `/admin/users`
+**Permission:** super_admin
+**Uses:** Existing `/api/v1/admin/users/*` endpoints
+
+**Layout:**
+```
+┌─────────────────────────────────────────────┐
+│ Users │
+│ Manage platform users and permissions │
+├─────────────────────────────────────────────┤
+│ [Search users...] [Filter: All ▼] │
+│ │
+│ ┌──────────────────────────────────────────┐│
+│ │ Email │ Name │ Role │ Status ││
+│ ├──────────────────────────────────────────┤│
+│ │ john@msp.com │ John │ Engineer│ Active││
+│ │ jane@msp.com │ Jane │ Viewer │ Active││
+│ └──────────────────────────────────────────┘│
+│ │
+│ < 1 2 3 ... 10 > (50 per page) │
+└─────────────────────────────────────────────┘
+```
+
+**Columns:**
+1. Email (sortable, primary identifier)
+2. Name (sortable)
+3. Account (display_code + account_role badge)
+4. Role (engineer/viewer badge)
+5. Status (active/deactivated badge)
+6. Last Login (sortable, time ago)
+7. Actions (dropdown)
+
+**Actions (per user):**
+- Change Role (engineer ↔ viewer)
+- Toggle Team Admin (if role = engineer)
+- Deactivate / Activate
+- Move to Different Account (NEW)
+- View Audit Logs (filtered by user_id)
+
+**Filters:**
+- All / Active / Deactivated
+- Role: All / Engineer / Viewer
+- Team Admin: All / Yes / No
+- Search: Name or Email (debounced)
+
+**Validation Rules:**
+- Cannot change role of last account_owner (show error toast)
+- Deactivating user logs them out next token refresh
+- Moving user to different account requires account_id input (modal)
+
+**Pagination:** 50 per page (fixed)
+
+**No Bulk Actions in Phase 1**
+
+---
+
+### 3. Invite Code Management
+
+**Route:** `/admin/invite-codes`
+**Permission:** super_admin
+**System:** Single-use only (no max_uses)
+
+**Layout:**
+```
+┌─────────────────────────────────────────────┐
+│ Invite Codes [Create Code] │
+│ Manage platform invite codes │
+├─────────────────────────────────────────────┤
+│ [Search codes...] [Status: All ▼] │
+│ │
+│ ┌──────────────────────────────────────────┐│
+│ │ Code │ Created│ Used By│ Status│ ... ││
+│ ├──────────────────────────────────────────┤│
+│ │ ABC12345│ Jan 1 │ - │ Active│ ... ││
+│ │ XYZ67890│ Jan 2 │ john@..│ Used │ ... ││
+│ └──────────────────────────────────────────┘│
+└─────────────────────────────────────────────┘
+```
+
+**Columns:**
+1. Code (8-char, monospace)
+2. Created By (admin name)
+3. Created At (date, sortable)
+4. Used By (email or "-")
+5. Used At (date or "-")
+6. Expires At (date or "Never")
+7. Status (Available/Used/Expired badge)
+8. Actions (dropdown)
+
+**Actions:**
+- Copy Code
+- Deactivate (sets expires_at to now)
+- Delete (if never used)
+- View Usage Details
+
+**Create Code Modal:**
+```
+Create Invite Code
+┌─────────────────────────────┐
+│ Expiration (Optional) │
+│ [Date Picker] │
+│ │
+│ [Cancel] [Create] │
+└─────────────────────────────┘
+```
+
+**Code Generation:** Backend generates 8-char code (existing logic)
+
+**Filters:**
+- All / Available / Used / Expired
+- Search: Code or Used By email
+
+**Pagination:** 50 per page
+
+---
+
+### 4. Plan Limits Configuration
+
+**Route:** `/admin/plan-limits`
+**Permission:** super_admin
+**Tables:** `plan_limits` (existing) + `account_limit_overrides` (NEW)
+
+**Layout:**
+```
+┌─────────────────────────────────────────────┐
+│ Plan Limits [Edit Plans] │
+│ Configure plan limits and account overrides │
+├─────────────────────────────────────────────┤
+│ Plan Configuration │
+│ ┌──────────────────────────────────────────┐│
+│ │ Plan │ Trees│ Sessions│ Users│ Export ││
+│ ├──────────────────────────────────────────┤│
+│ │ Free │ 5 │ 50/mo │ 1 │ MD,Text ││
+│ │ Pro │ ∞ │ 500/mo │ 5 │ All ││
+│ │ Team │ ∞ │ ∞ │ ∞ │ All+API ││
+│ └──────────────────────────────────────────┘│
+│ │
+│ Account Overrides [Create Override]│
+│ ┌──────────────────────────────────────────┐│
+│ │ Account │ Plan│ Override │ Note ││
+│ ├──────────────────────────────────────────┤│
+│ │ ABC12345 │ Free│ 50 trees │ Beta... ││
+│ └──────────────────────────────────────────┘│
+└─────────────────────────────────────────────┘
+```
+
+**Plan Configuration Table:**
+- Read-only display of `plan_limits` table
+- Edit Plans button opens modal with inline editing
+- Shows: max_trees, max_sessions_per_month, max_users, export_formats, custom_branding, priority_support
+- ∞ symbol for NULL (unlimited)
+
+**Account Overrides Table:**
+- Columns: Account (display_code + account name), Plan (current), Override Fields, Note, Actions
+- Override Fields: Only show non-NULL overrides (e.g., "50 trees, ∞ sessions")
+- Actions: Edit, Delete
+
+**Create Override Modal:**
+```
+Create Account Override
+┌─────────────────────────────┐
+│ Account ID (display_code) │
+│ [ABC12345] │
+│ │
+│ Override Max Trees │
+│ [50] or [∞ Unlimited] │
+│ │
+│ Override Max Sessions/Month │
+│ [500] or [∞ Unlimited] │
+│ │
+│ Override Max Users │
+│ [10] or [∞ Unlimited] │
+│ │
+│ Note (Optional) │
+│ [Beta partner access] │
+│ │
+│ [Cancel] [Create] │
+└─────────────────────────────┘
+```
+
+**Validation:**
+- Account display_code must exist
+- At least one override field must be set
+- Unlimited checkbox sends NULL to backend
+
+**Database Schema (NEW):**
+```sql
+CREATE TABLE account_limit_overrides (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
+ override_max_trees INTEGER, -- NULL = use plan default
+ override_max_sessions_per_month INTEGER,
+ override_max_users INTEGER,
+ note TEXT,
+ created_by_id UUID NOT NULL REFERENCES users(id),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ UNIQUE(account_id)
+);
+
+CREATE INDEX idx_account_limit_overrides_account ON account_limit_overrides(account_id);
+```
+
+**API Endpoints (NEW):**
+```python
+# GET /api/v1/admin/plan-limits
+# PUT /api/v1/admin/plan-limits
+# GET /api/v1/admin/account-overrides
+# POST /api/v1/admin/account-overrides
+# PUT /api/v1/admin/account-overrides/{id}
+# DELETE /api/v1/admin/account-overrides/{id}
+```
+
+**Backend Logic:**
+```python
+def get_effective_limits(account: Account) -> dict:
+ """Get effective limits for an account (overrides take precedence)."""
+ override = db.query(AccountLimitOverrides).filter_by(account_id=account.id).first()
+ plan_limits = db.query(PlanLimits).filter_by(plan=account.subscription.plan).first()
+
+ return {
+ "max_trees": override.override_max_trees if override and override.override_max_trees is not None else plan_limits.max_trees,
+ "max_sessions_per_month": override.override_max_sessions_per_month if override and override.override_max_sessions_per_month is not None else plan_limits.max_sessions_per_month,
+ "max_users": override.override_max_users if override and override.override_max_users is not None else plan_limits.max_users,
+ }
+```
+
+---
+
+### 5. Feature Flags
+
+**Route:** `/admin/feature-flags`
+**Permission:** super_admin
+**Approach:** Plan-based defaults (not global)
+
+**Layout:**
+```
+┌─────────────────────────────────────────────┐
+│ Feature Flags [Create Feature] │
+│ Manage plan-based feature access │
+├─────────────────────────────────────────────┤
+│ Feature Matrix │
+│ ┌──────────────────────────────────────────┐│
+│ │ Feature │ Free│ Pro │ Team│ ... ││
+│ ├──────────────────────────────────────────┤│
+│ │ Advanced Search │ ✗ │ ✓ │ ✓ │ ... ││
+│ │ Custom Branding │ ✗ │ ✗ │ ✓ │ ... ││
+│ │ API Access │ ✗ │ ✓ │ ✓ │ ... ││
+│ └──────────────────────────────────────────┘│
+│ │
+│ Account Overrides [Create Override]│
+│ ┌──────────────────────────────────────────┐│
+│ │ Account │ Feature │ Enabled│ Note││
+│ ├──────────────────────────────────────────┤│
+│ │ ABC12345 │ API Access │ ✓ │ Beta││
+│ └──────────────────────────────────────────┘│
+└─────────────────────────────────────────────┘
+```
+
+**Database Schema (NEW):**
+```sql
+-- Feature flag definitions
+CREATE TABLE feature_flags (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ flag_key VARCHAR(100) NOT NULL UNIQUE, -- 'advanced_search', 'api_access'
+ display_name VARCHAR(255) NOT NULL,
+ description TEXT,
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
+);
+
+-- Plan defaults (which features each plan gets)
+CREATE TABLE plan_feature_defaults (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ plan VARCHAR(50) NOT NULL REFERENCES plan_limits(plan),
+ flag_id UUID NOT NULL REFERENCES feature_flags(id) ON DELETE CASCADE,
+ enabled BOOLEAN NOT NULL DEFAULT false,
+ UNIQUE(plan, flag_id)
+);
+
+-- Per-account exceptions
+CREATE TABLE account_feature_overrides (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
+ flag_id UUID NOT NULL REFERENCES feature_flags(id) ON DELETE CASCADE,
+ enabled BOOLEAN NOT NULL,
+ note TEXT,
+ created_by_id UUID NOT NULL REFERENCES users(id),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ UNIQUE(account_id, flag_id)
+);
+
+CREATE INDEX idx_plan_feature_defaults_plan ON plan_feature_defaults(plan);
+CREATE INDEX idx_account_feature_overrides_account ON account_feature_overrides(account_id);
+```
+
+**Feature Matrix:**
+- Rows: Feature flags (display_name)
+- Columns: Plan names (free, pro, team)
+- Cells: Checkmark (✓) or X (✗)
+- Click cell to toggle (updates plan_feature_defaults)
+
+**Account Overrides:**
+- Columns: Account (display_code), Feature (display_name), Enabled (toggle), Note, Actions
+- Actions: Edit Note, Delete Override
+
+**Create Override Modal:**
+```
+Override Feature for Account
+┌─────────────────────────────┐
+│ Account ID (display_code) │
+│ [ABC12345] │
+│ │
+│ Feature │
+│ [Advanced Search ▼] │
+│ │
+│ Enable Feature │
+│ [Toggle Switch: ON] │
+│ │
+│ Note (Optional) │
+│ [Beta partner access] │
+│ │
+│ [Cancel] [Create] │
+└─────────────────────────────┘
+```
+
+**Backend Logic:**
+```python
+def is_feature_enabled(account: Account, flag_key: str) -> bool:
+ """Check if a feature is enabled for an account (override > plan default)."""
+ flag = db.query(FeatureFlag).filter_by(flag_key=flag_key).first()
+ if not flag:
+ return False
+
+ # Check override first
+ override = db.query(AccountFeatureOverride).filter_by(
+ account_id=account.id, flag_id=flag.id
+ ).first()
+ if override:
+ return override.enabled
+
+ # Fall back to plan default
+ plan_default = db.query(PlanFeatureDefault).filter_by(
+ plan=account.subscription.plan, flag_id=flag.id
+ ).first()
+ return plan_default.enabled if plan_default else False
+```
+
+**API Endpoints (NEW):**
+```python
+# Feature flags
+# GET /api/v1/admin/feature-flags
+# POST /api/v1/admin/feature-flags
+# PUT /api/v1/admin/feature-flags/{id}
+# DELETE /api/v1/admin/feature-flags/{id}
+
+# Plan defaults (batch update for matrix)
+# PUT /api/v1/admin/feature-flags/plan-defaults
+# Body: { "plan": "pro", "flag_id": "uuid", "enabled": true }
+
+# Account overrides
+# GET /api/v1/admin/feature-flags/account-overrides
+# POST /api/v1/admin/feature-flags/account-overrides
+# DELETE /api/v1/admin/feature-flags/account-overrides/{id}
+```
+
+---
+
+### 6. Platform Settings
+
+**Route:** `/admin/settings`
+**Permission:** super_admin
+**Scope:** Minimal - Maintenance Mode Only
+
+**Layout:**
+```
+┌─────────────────────────────────────────────┐
+│ Platform Settings │
+│ Configure platform-wide settings │
+├─────────────────────────────────────────────┤
+│ Maintenance Mode │
+│ ┌──────────────────────────────────────────┐│
+│ │ Enable Maintenance Mode ││
+│ │ [Toggle Switch: OFF] ││
+│ │ ││
+│ │ Maintenance Message ││
+│ │ ┌────────────────────────────────────┐ ││
+│ │ │ We're performing scheduled │ ││
+│ │ │ maintenance. We'll be back soon! │ ││
+│ │ └────────────────────────────────────┘ ││
+│ │ ││
+│ │ [Save Changes] ││
+│ └──────────────────────────────────────────┘│
+└─────────────────────────────────────────────┘
+```
+
+**Database Schema (NEW):**
+```sql
+CREATE TABLE platform_settings (
+ setting_key VARCHAR(100) PRIMARY KEY,
+ setting_value TEXT, -- JSON string for complex types
+ data_type VARCHAR(20), -- 'boolean', 'integer', 'string', 'json'
+ is_sensitive BOOLEAN DEFAULT false, -- Hide value in audit logs
+ updated_by_id UUID REFERENCES users(id),
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
+);
+
+-- Seed maintenance mode settings
+INSERT INTO platform_settings (setting_key, setting_value, data_type) VALUES
+ ('maintenance_mode', 'false', 'boolean'),
+ ('maintenance_message', 'We''re performing scheduled maintenance. We''ll be back soon!', 'string');
+```
+
+**Settings Manager (NEW):**
+```python
+# backend/app/core/settings_manager.py
+from datetime import datetime
+from typing import Any, Optional
+from sqlalchemy.ext.asyncio import AsyncSession
+from sqlalchemy import select
+from app.models.platform_settings import PlatformSetting
+
+class SettingsManager:
+ """Manage runtime platform settings with in-memory cache."""
+
+ _cache: dict[str, Any] = {}
+ _cache_time: float = 0
+ CACHE_TTL = 60 # 1 minute cache (safe for single Railway instance)
+
+ @classmethod
+ async def get(cls, key: str, db: AsyncSession, default: Any = None) -> Any:
+ """Get a setting value (cached)."""
+ import time
+
+ # Check cache
+ if time.time() - cls._cache_time < cls.CACHE_TTL and key in cls._cache:
+ return cls._cache[key]
+
+ # Query database
+ result = await db.execute(
+ select(PlatformSetting).where(PlatformSetting.setting_key == key)
+ )
+ setting = result.scalar_one_or_none()
+
+ if not setting:
+ return default
+
+ # Parse value based on data type
+ value = cls._parse_value(setting.setting_value, setting.data_type)
+
+ # Update cache
+ cls._cache[key] = value
+ cls._cache_time = time.time()
+
+ return value
+
+ @classmethod
+ async def set(cls, key: str, value: Any, db: AsyncSession, user_id: uuid.UUID) -> None:
+ """Set a setting value (invalidates cache)."""
+ result = await db.execute(
+ select(PlatformSetting).where(PlatformSetting.setting_key == key)
+ )
+ setting = result.scalar_one_or_none()
+
+ if setting:
+ setting.setting_value = str(value)
+ setting.updated_by_id = user_id
+ setting.updated_at = datetime.now(timezone.utc)
+ else:
+ setting = PlatformSetting(
+ setting_key=key,
+ setting_value=str(value),
+ data_type=cls._infer_type(value),
+ updated_by_id=user_id
+ )
+ db.add(setting)
+
+ # Invalidate cache
+ cls._cache_time = 0
+ if key in cls._cache:
+ del cls._cache[key]
+
+ @staticmethod
+ def _parse_value(value: str, data_type: str) -> Any:
+ if data_type == 'boolean':
+ return value.lower() == 'true'
+ elif data_type == 'integer':
+ return int(value)
+ elif data_type == 'json':
+ import json
+ return json.loads(value)
+ return value # string
+
+ @staticmethod
+ def _infer_type(value: Any) -> str:
+ if isinstance(value, bool):
+ return 'boolean'
+ elif isinstance(value, int):
+ return 'integer'
+ elif isinstance(value, (dict, list)):
+ return 'json'
+ return 'string'
+```
+
+**Maintenance Mode Middleware (NEW):**
+```python
+# backend/app/core/middleware.py
+from fastapi import Request
+from fastapi.responses import JSONResponse
+from app.core.settings_manager import SettingsManager
+from app.core.database import get_db
+
+@app.middleware("http")
+async def maintenance_mode_middleware(request: Request, call_next):
+ # Skip for admin endpoints
+ if request.url.path.startswith("/api/v1/admin"):
+ return await call_next(request)
+
+ # Check maintenance mode
+ async for db in get_db():
+ is_maintenance = await SettingsManager.get("maintenance_mode", db, default=False)
+
+ if is_maintenance:
+ message = await SettingsManager.get(
+ "maintenance_message",
+ db,
+ default="We're performing scheduled maintenance."
+ )
+ return JSONResponse(
+ status_code=503,
+ content={"detail": message}
+ )
+
+ return await call_next(request)
+```
+
+**API Endpoints (NEW):**
+```python
+# GET /api/v1/admin/settings
+# PUT /api/v1/admin/settings
+# Body: { "maintenance_mode": true, "maintenance_message": "..." }
+```
+
+**Features:**
+- Toggle maintenance mode (affects all non-admin endpoints)
+- Edit maintenance message (markdown support)
+- Preview message before saving
+- Audit log on save
+
+**Phase 2 Settings:**
+- Registration settings (REQUIRE_INVITE_CODE)
+- Rate limit overrides
+- SMTP configuration
+- Session timeout
+
+---
+
+### 7. Audit Log Viewer
+
+**Route:** `/admin/audit-logs`
+**Permission:** super_admin
+**Scope:** Minimal - Essential viewing + CSV export
+
+**Layout:**
+```
+┌─────────────────────────────────────────────┐
+│ Audit Logs [Export CSV] │
+│ View platform activity and changes │
+├─────────────────────────────────────────────┤
+│ [Search...] [Action ▼] [Entity ▼] [User ▼] │
+│ [Date: Last 30 days ▼] [Apply Filters]│
+│ │
+│ ┌──────────────────────────────────────────┐│
+│ │ Time │ User │ Action│ Entity│ Details ││
+│ ├──────────────────────────────────────────┤│
+│ │ 10:30 │ Admin │ Role │ User │ {...} ││
+│ │ 10:25 │ Admin │ Delete│ Tree │ {...} ││
+│ └──────────────────────────────────────────┘│
+│ │
+│ < 1 2 3 ... 10 > (50 per page) │
+└─────────────────────────────────────────────┘
+```
+
+**Columns:**
+1. Timestamp (date + time, sortable)
+2. User (name + email, click to filter)
+3. Action (badge: user_created, user_role_changed, tree_deleted, etc.)
+4. Entity Type (user, tree, plan_limits, etc.)
+5. Entity ID (first 8 chars, monospace)
+6. IP Address
+7. Details (expandable JSON viewer)
+
+**Filters:**
+- Action: All / User Actions / Tree Actions / Settings Changes / etc.
+- Entity Type: All / User / Tree / Account / Subscription / etc.
+- User: Dropdown (autocomplete)
+- Date Range: Last 7/30/90 days, Custom, All Time
+- Search: Entity ID or details JSON (server-side)
+
+**Auto-Apply Filters:**
+- Changing any filter automatically applies it (no "Apply" button needed)
+- URL params track filter state (`?action=user_created&entity=user`)
+- Pagination resets to page 1 on filter change
+
+**CSV Export:**
+- 10,000 row limit (show warning if more)
+- Respects current filters
+- Columns: timestamp, user_email, action, entity_type, entity_id, ip_address, details_json
+- Filename: `audit-logs-{date}.csv`
+
+**Backend Optimization:**
+```python
+# N+1 query fix: LEFT JOIN to get user email in single query
+query = (
+ select(AuditLog, User.email.label('user_email'))
+ .outerjoin(User, AuditLog.user_id == User.id)
+ .order_by(AuditLog.created_at.desc())
+)
+```
+
+**API Endpoints (Existing + NEW):**
+```python
+# GET /api/v1/admin/audit-logs?page=1&per_page=50&action=user_created&entity_type=user&user_id=uuid&date_from=2026-01-01&date_to=2026-02-08&search=keyword
+# GET /api/v1/admin/audit-logs/export (CSV, 10k limit, respects filters)
+```
+
+**Phase 2 Features:**
+- Real-time updates (WebSocket or polling)
+- JSON export
+- "View Related Logs" action (filter by entity_id)
+
+---
+
+### 8. Global Categories (NEW)
+
+**Route:** `/admin/categories`
+**Permission:** super_admin
+**Scope:** Manage global categories (account_id = NULL)
+
+**Layout:**
+```
+┌─────────────────────────────────────────────┐
+│ Global Categories [Create Category]│
+│ Manage global decision tree categories │
+├─────────────────────────────────────────────┤
+│ [Search categories...] │
+│ │
+│ ┌──────────────────────────────────────────┐│
+│ │ Name │ Slug │ Trees│ ... ││
+│ ├──────────────────────────────────────────┤│
+│ │ 🌍 Networking │ networking│ 45 │ ... ││
+│ │ 🌍 Security │ security │ 32 │ ... ││
+│ │ 🌍 Hardware │ hardware │ 18 │ ... ││
+│ └──────────────────────────────────────────┘│
+└─────────────────────────────────────────────┘
+```
+
+**Features:**
+- Create/Edit/Archive global categories
+- Globe icon (🌍) prefix for all global categories
+- Tree count (simple N+1 query acceptable)
+- Archive instead of delete (allows undo)
+- Can archive even if trees are using it (trees keep reference)
+
+**API Endpoints (NEW):**
+```python
+# Global categories only (account_id IS NULL)
+# GET /api/v1/admin/categories/global
+# POST /api/v1/admin/categories/global
+# PUT /api/v1/admin/categories/global/{id}
+# DELETE /api/v1/admin/categories/global/{id} # Sets is_archived=true
+```
+
+---
+
+### 9. Team Categories (NEW)
+
+**Route:** `/account/categories`
+**Permission:** account_owner
+**Scope:** Manage team-specific categories (account_id = current user's account)
+
+**Layout:**
+```
+┌─────────────────────────────────────────────┐
+│ Team Categories [Create Category]│
+│ Manage your team's decision tree categories │
+├─────────────────────────────────────────────┤
+│ [Search categories...] │
+│ │
+│ ┌──────────────────────────────────────────┐│
+│ │ Name │ Slug │ Trees│ ... ││
+│ ├──────────────────────────────────────────┤│
+│ │ 👥 Client ABC │ client-abc│ 12 │ ... ││
+│ │ 👥 Internal IT │ internal │ 8 │ ... ││
+│ └──────────────────────────────────────────┘│
+└─────────────────────────────────────────────┘
+```
+
+**Features:**
+- Create/Edit/Archive team categories
+- Users icon (👥) prefix for all team categories
+- Tree count (simple N+1 query acceptable)
+- Archive instead of delete
+- Can archive even if trees are using it
+
+**API Endpoints (Existing):**
+```python
+# Team categories (account_id = current user's account)
+# GET /api/v1/categories (filtered by account_id)
+# POST /api/v1/categories
+# PUT /api/v1/categories/{id}
+# DELETE /api/v1/categories/{id} # Sets is_archived=true
+```
+
+**Navigation:**
+- Add "Account Settings" dropdown to user menu
+- Menu items: "Team Categories", "Billing" (future), "Team Members" (future)
+
+---
+
+## Technical Implementation
+
+### Frontend Structure
+
+**New Files:**
+```
+frontend/src/
+├── components/
+│ ├── admin/
+│ │ ├── AdminLayout.tsx
+│ │ ├── AdminSidebar.tsx
+│ │ ├── DataTable.tsx
+│ │ ├── Pagination.tsx
+│ │ ├── ActionMenu.tsx
+│ │ ├── StatusBadge.tsx
+│ │ ├── EmptyState.tsx
+│ │ ├── SearchInput.tsx
+│ │ └── PageHeader.tsx
+│ └── account/
+│ └── AccountLayout.tsx
+├── pages/
+│ ├── admin/
+│ │ ├── DashboardPage.tsx
+│ │ ├── UsersPage.tsx
+│ │ ├── InviteCodesPage.tsx
+│ │ ├── PlanLimitsPage.tsx
+│ │ ├── FeatureFlagsPage.tsx
+│ │ ├── SettingsPage.tsx
+│ │ ├── AuditLogsPage.tsx
+│ │ └── GlobalCategoriesPage.tsx
+│ └── account/
+│ └── TeamCategoriesPage.tsx
+└── api/
+ └── admin.ts (single file for all admin endpoints)
+```
+
+### Backend Structure
+
+**New Files:**
+```
+backend/app/
+├── models/
+│ ├── account_limit_overrides.py
+│ ├── feature_flags.py (3 models)
+│ └── platform_settings.py
+├── api/endpoints/
+│ ├── admin_audit.py (audit log endpoints)
+│ ├── admin_dashboard.py (dashboard metrics)
+│ └── admin_categories.py (global categories)
+└── core/
+ └── settings_manager.py (runtime settings cache)
+```
+
+**Updated Files:**
+- `backend/app/api/endpoints/admin.py` - Add move user to account endpoint
+- `backend/app/api/router.py` - Add new admin endpoints
+- `backend/app/core/middleware.py` - Add maintenance mode middleware
+
+### Migrations
+
+**New Alembic Migrations:**
+1. `024_account_limit_overrides.py` - Account-specific limit overrides
+2. `025_feature_flags.py` - Feature flag system (3 tables)
+3. `026_platform_settings.py` - Runtime settings table
+
+### Dependencies
+
+**Frontend:**
+```json
+{
+ "lodash": "^4.17.21", // Debounce utility
+ "@types/lodash": "^4.14.202" // TypeScript types
+}
+```
+
+**Backend:**
+- No new dependencies (uses existing FastAPI, SQLAlchemy, etc.)
+
+---
+
+## Security & Permissions
+
+### Role Hierarchy
+
+```
+super_admin (is_super_admin=true)
+ └─ Can access /admin/* routes
+ └─ Can view/modify all accounts
+ └─ Cannot be demoted by other admins
+
+account_owner (account_role='owner')
+ └─ Can access /account/* routes
+ └─ Can manage team categories
+ └─ Cannot access admin panel
+
+engineer (role='engineer')
+ └─ Can create/edit trees, steps
+ └─ Cannot access admin or account settings
+
+viewer (role='viewer')
+ └─ Can browse trees, start sessions
+ └─ Cannot create/edit content
+```
+
+### Permission Checks
+
+**Backend (always validates):**
+```python
+# Existing dependency
+from app.api.deps import get_current_active_user, require_admin
+
+# All admin endpoints
+@router.get("/admin/users")
+async def list_users(
+ current_user: Annotated[User, Depends(require_admin)],
+ db: Annotated[AsyncSession, Depends(get_db)]
+):
+ ...
+```
+
+**Frontend (UI only):**
+```typescript
+const { isSuperAdmin, isAccountOwner } = usePermissions()
+
+// Route protection
+
+
+
+
+// Conditional rendering
+{isSuperAdmin && Admin Panel}
+{isAccountOwner && Team Categories}
+```
+
+### Audit Logging
+
+All admin actions must be logged:
+
+```python
+from app.core.audit import log_audit
+
+await log_audit(
+ db=db,
+ user_id=current_user.id,
+ action="user_role_changed",
+ entity_type="user",
+ entity_id=user.id,
+ ip_address=request.client.host,
+ details={
+ "old_role": old_role,
+ "new_role": new_role
+ }
+)
+```
+
+**Logged Actions:**
+- User role changes
+- Team admin toggles
+- User deactivations
+- Account moves
+- Plan limit edits
+- Feature flag toggles
+- Platform settings changes
+- Category create/edit/archive
+
+---
+
+## Phase 2 Enhancements
+
+Features deferred to Phase 2:
+
+### Dashboard
+- Real-time metrics (auto-refresh)
+- Charts/graphs (revenue trends, usage over time)
+- Quick actions section
+- Clickable metric cards (link to filtered views)
+
+### User Management
+- Bulk actions (bulk role change, bulk deactivate)
+- Advanced filters (account search, date ranges)
+- User activity timeline
+- Export users to CSV
+
+### Invite Codes
+- Multi-use codes with usage tracking
+- Code templates/presets
+- Email integration (send invite via email)
+
+### Audit Logs
+- Real-time updates (WebSocket)
+- JSON export format
+- "View Related Logs" action
+- Advanced search (full-text on details JSON)
+
+### Settings
+- Registration settings (toggle REQUIRE_INVITE_CODE)
+- Rate limit overrides (per-user/per-account)
+- SMTP configuration (email settings)
+- Session timeout settings
+
+### Categories
+- Drag-and-drop reordering
+- Category icons/colors
+- Tree count optimization (cached/precomputed)
+- Nested categories (hierarchy)
+
+### Feature Flags
+- A/B testing support
+- Gradual rollout (percentage-based)
+- User-level overrides (not just account-level)
+- Flag usage analytics
+
+### New Pages
+- Account Management (list all accounts, edit plan/status)
+- Subscription Management (Stripe integration)
+- Analytics Dashboard (detailed usage metrics)
+- Support Tickets (if support system is added)
+
+---
+
+## Estimation
+
+### Development Time
+
+| Component | Hours |
+|-----------|-------|
+| Reusable Components (7) | 8-12 |
+| Backend Models & Migrations (3) | 6-8 |
+| Backend API Endpoints (20+) | 12-16 |
+| Dashboard Page | 4-6 |
+| User Management Page | 6-8 |
+| Invite Codes Page | 4-6 |
+| Plan Limits Page | 6-8 |
+| Feature Flags Page | 8-10 |
+| Settings Page | 3-4 |
+| Audit Logs Page | 6-8 |
+| Global Categories Page | 4-6 |
+| Team Categories Page | 3-4 |
+| Settings Manager & Middleware | 4-6 |
+| Testing & Bug Fixes | 10-15 |
+| **Total** | **84-117 hours** |
+
+### Phase Breakdown
+
+**Phase 1A - Foundation (20-25 hours):**
+- Reusable components
+- AdminLayout & AccountLayout
+- Backend models & migrations
+- Settings manager
+
+**Phase 1B - Core Pages (30-40 hours):**
+- Dashboard
+- User Management
+- Invite Codes
+- Audit Logs
+
+**Phase 1C - Configuration (25-35 hours):**
+- Plan Limits
+- Feature Flags
+- Platform Settings
+- Categories (global + team)
+
+**Phase 1D - Testing & Polish (10-15 hours):**
+- Integration tests
+- Permission checks
+- UI polish
+- Documentation
+
+---
+
+## Testing Strategy
+
+### Backend Tests
+
+**Unit Tests:**
+- Settings manager cache behavior
+- Feature flag resolution logic
+- Effective limits calculation
+- Audit log helper
+
+**Integration Tests:**
+```python
+# test_admin_users.py
+async def test_list_users_as_admin(client, test_admin):
+ """Super admin can list all users."""
+ response = await client.get("/api/v1/admin/users", headers=auth_headers(test_admin))
+ assert response.status_code == 200
+
+async def test_change_user_role(client, test_admin, test_user):
+ """Super admin can change user roles."""
+ response = await client.put(
+ f"/api/v1/admin/users/{test_user.id}/role",
+ headers=auth_headers(test_admin),
+ json={"role": "viewer"}
+ )
+ assert response.status_code == 200
+
+# test_admin_permissions.py
+async def test_non_admin_cannot_access_admin_endpoints(client, test_user):
+ """Engineers cannot access admin endpoints."""
+ response = await client.get("/api/v1/admin/users", headers=auth_headers(test_user))
+ assert response.status_code == 403
+
+# test_feature_flags.py
+async def test_feature_flag_override(db, test_account):
+ """Account overrides take precedence over plan defaults."""
+ # Setup: Free plan has advanced_search = False by default
+ # Create override: Enable advanced_search for this account
+ # Assert: is_feature_enabled returns True
+
+# test_plan_limits.py
+async def test_effective_limits_with_override(db, test_account):
+ """Account overrides take precedence over plan limits."""
+ # Setup: Free plan has max_trees = 5
+ # Create override: max_trees = 50
+ # Assert: get_effective_limits returns 50
+```
+
+### Frontend Tests
+
+**Component Tests:**
+- DataTable rendering & sorting
+- Pagination ellipsis logic
+- SearchInput debounce behavior
+- ActionMenu dropdown functionality
+
+**Integration Tests:**
+- Admin route protection
+- Permission-based UI rendering
+- API error handling
+- Form validation
+
+**E2E Tests (Cypress/Playwright):**
+```javascript
+describe('Admin Panel', () => {
+ it('super admin can access admin panel', () => {
+ cy.loginAs('admin@example.com')
+ cy.visit('/admin/dashboard')
+ cy.contains('Dashboard').should('be.visible')
+ })
+
+ it('engineer cannot access admin panel', () => {
+ cy.loginAs('engineer@example.com')
+ cy.visit('/admin/dashboard')
+ cy.url().should('eq', '/trees') // Redirected
+ })
+
+ it('can change user role', () => {
+ cy.loginAs('admin@example.com')
+ cy.visit('/admin/users')
+ cy.get('[data-testid="user-row-1"]').find('[data-testid="action-menu"]').click()
+ cy.contains('Change Role').click()
+ cy.get('[data-testid="role-select"]').select('Viewer')
+ cy.contains('Save').click()
+ cy.contains('Role updated successfully').should('be.visible')
+ })
+})
+```
+
+---
+
+## Migration Path
+
+### Existing Users
+
+No impact - admin panel is new functionality. Existing permissions continue to work.
+
+### Existing Data
+
+- Plan limits: Existing `plan_limits` table is read-only in UI
+- Categories: Existing `/categories` endpoint works for team categories
+- Audit logs: Existing `audit_logs` table is queried with optimizations
+
+### Deployment Steps
+
+1. **Backend:**
+ ```bash
+ cd backend
+ alembic upgrade head # Run 3 new migrations
+ python -m scripts.seed_plan_limits # Seed plan defaults if needed
+ ```
+
+2. **Frontend:**
+ ```bash
+ cd frontend
+ npm install # Install lodash
+ npm run build
+ ```
+
+3. **Verify:**
+ - Create first super admin (manually set `is_super_admin=true` in DB)
+ - Login as super admin
+ - Verify `/admin/dashboard` is accessible
+ - Test one CRUD operation per page
+
+---
+
+## Documentation
+
+### For Admins
+
+Create `docs/admin-guide.md`:
+- How to access admin panel
+- Overview of each page
+- Common tasks (create user, change plan, enable feature)
+- Troubleshooting (cannot access admin, changes not applying)
+
+### For Developers
+
+Update `CLAUDE.md`:
+- Add admin panel architecture section
+- Document settings manager usage
+- Add feature flag check pattern
+- Update API endpoints reference
+
+### API Documentation
+
+Auto-generated via FastAPI OpenAPI at `/api/docs` - no manual updates needed.
+
+---
+
+## Appendix
+
+### Database ER Diagram
+
+```
+┌─────────────┐ ┌──────────────────┐
+│ users │──────>│ accounts │
+│ - id │ │ - id │
+│ - email │ │ - name │
+│ - is_super_admin│ │ - display_code │
+└─────────────┘ └──────────────────┘
+ │ │
+ │ V
+ │ ┌──────────────────┐
+ │ │ subscriptions │
+ │ │ - account_id │
+ │ │ - plan │
+ │ └──────────────────┘
+ │ │
+ V │
+┌─────────────┐ │
+│ audit_logs │ │
+│ - user_id │ │
+│ - action │ │
+│ - details │ │
+└─────────────┘ │
+ │
+ ┌─────────────────────┤
+ V │
+┌──────────────────┐ │
+│ plan_limits │ │
+│ - plan (PK) │<────────┘
+│ - max_trees │
+│ - max_sessions │
+└──────────────────┘
+ ^
+ │
+┌──────────────────────────┐
+│ account_limit_overrides │
+│ - account_id │
+│ - override_max_trees │
+└──────────────────────────┘
+
+┌──────────────────┐ ┌──────────────────────┐
+│ feature_flags │<──────│ plan_feature_defaults│
+│ - flag_key │ │ - plan │
+│ - display_name │ │ - flag_id │
+└──────────────────┘ │ - enabled │
+ ^ └──────────────────────┘
+ │
+┌──────────────────────────┐
+│ account_feature_overrides│
+│ - account_id │
+│ - flag_id │
+│ - enabled │
+└──────────────────────────┘
+
+┌──────────────────┐
+│ platform_settings│
+│ - setting_key │
+│ - setting_value │
+│ - data_type │
+└──────────────────┘
+```
+
+### Key Design Decisions
+
+1. **Plan-Based Feature Flags** (not global defaults) - Aligns with SaaS model
+2. **Database-Backed Settings** (not Redis) - Simpler for Phase 1, single instance
+3. **60s Cache for Settings** - Acceptable latency for single Railway instance
+4. **Simple N+1 for Tree Counts** - Low volume (<1k categories), acceptable
+5. **Archive Instead of Delete** - All destructive actions are soft deletes
+6. **Single Admin API File** - Easier to maintain than split across modules
+7. **No Bulk Actions** - Deferred to Phase 2 to reduce complexity
+8. **Minimal Dashboard** - Focus on configuration tools over analytics
+
+### Related Issues
+
+- **Issue #40** - Admin panel foundation (this design)
+- **Issue #41** - Account management (Phase 2)
+- **Issue #42** - Subscription billing (Phase 2)
+- **Issue #43** - Analytics dashboard (Phase 2)
+
+---
+
+## Sign-Off
+
+**Design Status:** Complete - Ready for Implementation
+**Estimated Effort:** 84-117 hours (10-15 working days)
+**Phase 1 Scope:** All 9 pages + reusable components + backend infrastructure
+**Phase 2 Deferred:** Bulk actions, real-time updates, advanced analytics
+
+**Next Steps:**
+1. Review this design document
+2. Approve Phase 1 scope
+3. Create feature branch: `feat/admin-panel`
+4. Begin implementation (Foundation phase)
+5. Iterative testing and refinement
+
+**Questions or Concerns:** Discuss before implementation begins.