# Officer.dev Frontend Architecture

## Executive Summary

Officer's frontend is a modern React 19 SPA (Single Page Application) built with:
- **React 19** with automatic compiler optimizations (no useCallback/useMemo needed)
- **React Router 7** for client-side navigation
- **React Query** for server state management
- **Tailwind CSS 4** with custom components for styling
- **Shadcn/ui** component library base
- **WebSockets** for real-time terminal and AI chat
- **Modular architecture** with shared component and hook libraries

This document covers the frontend architecture, component patterns, state management, and development practices.

---

## Table of Contents

1. [Architecture Overview](#architecture-overview)
2. [Technology Stack](#technology-stack)
3. [Directory Structure](#directory-structure)
4. [Routing & Navigation](#routing--navigation)
5. [Component Architecture](#component-architecture)
6. [State Management](#state-management)
7. [API Communication](#api-communication)
8. [Real-Time Features](#real-time-features)
9. [Styling & Theming](#styling--theming)
10. [Performance & Optimization](#performance--optimization)
11. [Development Workflow](#development-workflow)

---

## Architecture Overview

### High-Level Architecture

```
┌──────────────────────────────────────────────────────────┐
│                    Browser Application                    │
├──────────────────────────────────────────────────────────┤
│                                                           │
│  App.tsx (Router Setup)                                 │
│    ↓                                                     │
│  BrowserRouter                                          │
│    ├─ Authentication Routes (public)                    │
│    │  └─ Landing, SignIn, SignUp, ForgotPassword       │
│    │                                                     │
│    └─ Dashboard Routes (protected)                      │
│       ├─ HomeScreen                                     │
│       ├─ SettingsPages (Profile, System, Resources)    │
│       ├─ Automation                                     │
│       ├─ SessionListPage (Chat with Pi)                │
│       ├─ PlansScreen                                   │
│       ├─ FilesScreen                                   │
│       ├─ CodeEditorScreen                              │
│       ├─ SkillsScreen                                  │
│       ├─ TasksScreen                                   │
│       ├─ ProcessesScreen                               │
│       ├─ TaskLogsScreen                                │
│       ├─ WorkspacesScreen                              │
│       ├─ ProjectListScreen                             │
│       ├─ ProjectScreen                                 │
│       └─ TerminalScreen                                │
│                                                         │
│  ↓ Shared State/Context                                │
│                                                         │
│  ├─ AuthContext (useAuth)                              │
│  ├─ GlobalState (useGlobalState)                       │
│  ├─ ServerSettings (useServerSettings)                │
│  └─ ReactQuery (useQuery, useMutation)                 │
│                                                         │
│  ↓ WebSocket Connections                               │
│                                                         │
│  ├─ Terminal WebSocket                                 │
│  │  └─ Interactive shell in xterm.js                   │
│  │                                                      │
│  └─ Pi Chat WebSocket                                  │
│     └─ Real-time streaming chat with Claude            │
│                                                         │
└──────────────────────────────────────────────────────────┘
         ↓ HTTP/REST & WebSocket
    ┌────────────────┐
    │  Officer API   │
    │  (port 5000)   │
    └────────────────┘
```

### Data Flow

```
User Interaction (Click, Type, etc.)
    ↓
Component Event Handler
    ↓
State Update (useState/Context/Query)
    ↓
API Call (HTTP or WebSocket)
    ↓
Server Processing
    ↓
Response
    ↓
Update Component State
    ↓
Re-render
    ↓
Updated UI
```

---

## Technology Stack

### Core Frontend Framework
- **React 19** - Latest with automatic compiler optimizations
- **React DOM 19** - DOM rendering
- **React Router 7** - Client-side routing (file-based patterns support)
- **TypeScript 5.9** - Strict mode enabled

### State Management
- **React Context** - Component tree data sharing
- **React Query (TanStack)** - Server state, caching, synchronization
- **Custom hooks** - Encapsulated business logic
- **Zustand** - Optional lightweight state (if used)

### UI & Styling
- **Tailwind CSS 4** - Utility-first CSS framework
- **Shadcn/ui** - Accessible component library base
- **Radix UI** - Headless components for accessibility
- **Lucide React** - Icon library
- **Tabler Icons** - Additional icons
- **Class Variance Authority (CVA)** - Component style variations

### Real-Time Communication
- **WebSocket API** - Native browser WebSocket
- **Json RPC** - Message protocol over WebSocket

### Code Editor & Terminal
- **Monaco Editor** (@monaco-editor/react) - VS Code-like editor
- **xterm.js** (@xterm/xterm) - Terminal emulator
- **@uiw/react-textarea-code-editor** - Simple code editing

### Data Visualization & Rich Content
- **Recharts** - Chart library
- **React Markdown** - Markdown rendering
- **Shiki** - Syntax highlighting
- **HTML2Canvas** - Screenshot capture
- **React Three Fiber** - 3D rendering (if used)

### Form & Validation
- **React Hook Form** - Form state management
- **Zod** - Schema validation
- **@hookform/resolvers** - Form validation resolvers

### UI Components & Utilities
- **Sonner** - Toast notifications
- **Vaul** - Drawer component
- **React Resizable Panels** - Resizable layout panels
- **React Virtual** - Virtual scrolling
- **Input OTP** - OTP input component
- **React Spinners** - Loading animations
- **React CountUp** - Animated numbers

### Authentication
- **@simplewebauthn/browser** - WebAuthn/passkey support
- **@react-oauth/google** - Google OAuth integration
- **JWT Decode** - Token decoding

### External Services
- **Googleapis** - Google API integration
- **Nodemailer** (backend) - Email sending

### Development & Build Tools
- **Bun** - Runtime and package manager
- **Vite** - Build tool and dev server (if used)
- **Playwright** - E2E testing
- **Testing Library** - Component testing utilities
- **Happy DOM** - DOM testing

---

## Directory Structure

### Frontend Layout

```
src/
├── apps/
│   └── officer-web/                 # Main dashboard app
│       ├── App.tsx                  # Root component & routing
│       ├── frontend.tsx             # Vite entry point (if applicable)
│       ├── index.html               # HTML template
│       │
│       ├── Screens/                 # Page-level components
│       │   ├── Authentication/      # Auth screens (public)
│       │   │   ├── LandingPage.tsx
│       │   │   ├── VerifyScreen.tsx
│       │   │   ├── SignInScreen.tsx
│       │   │   ├── SignUpScreen.tsx
│       │   │   ├── ForgotPassword.tsx
│       │   │   ├── ResetPassword.tsx
│       │   │   └── SignoutScreen.tsx
│       │   │
│       │   └── Dashboard/            # Dashboard screens (protected)
│       │       ├── HomeScreen.tsx
│       │       ├── SettingsPages/
│       │       │   ├── ProfileSettings.tsx
│       │       │   ├── SystemSettings.tsx
│       │       │   └── ResourceSettings.tsx
│       │       ├── Automation.tsx
│       │       ├── SessionListPage.tsx (Pi Chat)
│       │       ├── PlansScreen.tsx
│       │       ├── FilesScreen.tsx
│       │       ├── CodeEditorScreen.tsx
│       │       ├── SkillsScreen.tsx
│       │       ├── TasksScreen.tsx
│       │       ├── ProcessesScreen.tsx
│       │       ├── TaskLogsScreen.tsx
│       │       ├── WorkspacesScreen.tsx
│       │       ├── WorkspaceScreen.tsx
│       │       ├── ProjectListScreen.tsx
│       │       ├── ProjectScreen.tsx
│       │       └── TerminalScreen.tsx
│       │
│       ├── state/                   # Component state
│       │   ├── useAuth.ts          # Authentication state
│       │   ├── useInitialData.ts   # Initial data loading
│       │   └── useServerSettings.ts # Server configuration
│       │
│       ├── lib/                     # Client utilities
│       │   ├── api.ts              # API client
│       │   └── websocket.ts        # WebSocket utilities
│       │
│       ├── styles/                 # Global styles
│       │   ├── global.css
│       │   └── tailwind.css
│       │
│       └── locales/                # Translations
│           ├── en.json
│           └── ...
│
└── workspaces/                      # Shared libraries
    ├── components/                  # Reusable React components
    │   ├── ui/                      # Basic UI components
    │   │   ├── button.tsx
    │   │   ├── input.tsx
    │   │   ├── dialog.tsx
    │   │   ├── dropdown-menu.tsx
    │   │   ├── select.tsx
    │   │   ├── tabs.tsx
    │   │   ├── card.tsx
    │   │   └── ...more
    │   │
    │   ├── Complex/                 # Feature components
    │   │   ├── DataTable/
    │   │   ├── MarkdownEditor.tsx
    │   │   ├── ColorPicker.tsx
    │   │   ├── CommandBlock.tsx
    │   │   └── ...
    │   │
    │   ├── Workspace/               # Workspace-specific
    │   ├── Dialogs/                 # Dialog components
    │   ├── ErrorDialogs/            # Error handling UI
    │   ├── Logos/                   # Logo variants
    │   └── package.json
    │
    ├── hooks/                       # Custom React hooks
    │   ├── useAuth.ts              # Auth state hook
    │   ├── useClient.ts            # API client hook
    │   ├── useQuery.ts             # Data fetching
    │   ├── useMutation.ts          # Data mutation
    │   └── ...
    │
    ├── helpers/                     # Utility functions
    │   ├── formatters.ts           # Date, number formatting
    │   ├── validators.ts           # Input validation
    │   ├── converters.ts           # Type conversions
    │   └── ...
    │
    ├── state/                       # Global state
    │   ├── useGlobalState.ts       # Global state hook
    │   └── ...
    │
    ├── types/                       # Type definitions
    │   └── index.ts                # Export all types
    │
    ├── config/                      # Configuration
    │   ├── constants.ts
    │   └── env.ts
    │
    ├── definitions/                 # Enums and constants
    │   ├── roles.ts
    │   ├── statuses.ts
    │   └── ...
    │
    ├── widgets/                     # Complex UI widgets
    │   └── ...
    │
    └── i18n/                        # Internationalization
        └── ...
```

---

## Routing & Navigation

### React Router Setup: `App.tsx`

```typescript
import { BrowserRouter, Routes, Route, Navigate } from 'react-router';
import { useAuth } from 'hooks/useAuth';
import { useServerSettings } from 'state/useServerSettings';

export function App() {
  const { isLoading, isAuthenticated } = useAuth();
  const { onboardingComplete, isLoading: isServerSettingsLoading } = useServerSettings();
  
  // Show nothing while loading auth state
  if (isLoading || isServerSettingsLoading) return null;

  return (
    <BrowserRouter>
      {!isAuthenticated && (
        <Authentication.AuthenticationLayout>
          <Routes>
            <Route path="/" element={<Authentication.LandingPage />} />
            <Route path="/auth/verify" element={<Authentication.VerifyScreen />} />
            <Route path="/auth/forgot-password" element={<Authentication.ForgotPassword />} />
            <Route path="/auth/reset-password" element={<Authentication.ResetPassword />} />
            <Route path="*" element={<Navigate to="/" replace />} />
          </Routes>
        </Authentication.AuthenticationLayout>
      )}
      
      {isAuthenticated && onboardingComplete && (
        <Dashboard.DashboardLayout>
          <Routes>
            {/* Home & Settings */}
            <Route path="/" element={<Dashboard.HomeScreen />} />
            <Route path="/settings/profile" element={<Dashboard.ProfileSettings />} />
            <Route path="/settings/system" element={<Dashboard.SystemSettings />} />
            <Route path="/settings/resources" element={<Dashboard.ResourceSettings />} />
            
            {/* Features */}
            <Route path="/automation" element={<Dashboard.Automation />} />
            <Route path="/chat/new" element={<Dashboard.SessionListPage isNew />} />
            <Route path="/chat/:sessionId" element={<Dashboard.SessionListPage />} />
            <Route path="/plans" element={<Dashboard.Plans />} />
            <Route path="/files" element={<Dashboard.FilesScreen />} />
            <Route path="/code-editor" element={<Dashboard.CodeEditor />} />
            <Route path="/skills" element={<Dashboard.Skills />} />
            <Route path="/tasks" element={<Dashboard.Tasks />} />
            <Route path="/processes" element={<Dashboard.Processes />} />
            <Route path="/task-logs" element={<Dashboard.TaskLogs />} />
            
            {/* Workspaces & Projects */}
            <Route path="/workspaces" element={<Dashboard.WorkspacesScreen />} />
            <Route path="/workspaces/:id" element={<Dashboard.WorkspaceScreen />} />
            <Route path="/projects" element={<Dashboard.ProjectListScreen />} />
            <Route path="/projects/new" element={<Dashboard.NewProjectRedirect />} />
            <Route path="/projects/:id" element={<Dashboard.ProjectScreen />} />
            
            {/* Development */}
            <Route path="/terminal" element={<Dashboard.TerminalScreen />} />
            
            {/* Auth */}
            <Route path="/auth/signout" element={<Authentication.SignoutScreen />} />
            
            {/* Fallback */}
            <Route path="*" element={<Navigate to="/" replace />} />
          </Routes>
        </Dashboard.DashboardLayout>
      )}
    </BrowserRouter>
  );
}
```

### Layout Components

**AuthenticationLayout** - Wrapper for public pages
```typescript
export const AuthenticationLayout = ({ children }: Props) => {
  return (
    <div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800">
      {children}
    </div>
  );
};
```

**DashboardLayout** - Wrapper for protected pages
```typescript
export const DashboardLayout = ({ children }: Props) => {
  return (
    <div className="flex h-screen">
      <Sidebar />
      <main className="flex-1 flex flex-col">
        <Header />
        <div className="flex-1 overflow-auto">
          {children}
        </div>
      </main>
    </div>
  );
};
```

---

## Component Architecture

### Component Patterns

#### 1. Functional Components with Props

All components are functional components using React 19:

```typescript
type ButtonProps = {
  children: React.ReactNode;
  onClick: () => void;
  variant?: 'primary' | 'secondary' | 'outline';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
};

export const Button = ({ 
  children, 
  onClick, 
  variant = 'primary',
  size = 'md',
  disabled = false,
}: ButtonProps) => {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={cn(
        'font-medium rounded-lg transition-colors',
        {
          'bg-blue-600 text-white hover:bg-blue-700': variant === 'primary',
          'bg-slate-200 text-slate-900 hover:bg-slate-300': variant === 'secondary',
          'border-2 border-blue-600 text-blue-600 hover:bg-blue-50': variant === 'outline',
        },
        {
          'px-3 py-1 text-sm': size === 'sm',
          'px-4 py-2 text-base': size === 'md',
          'px-6 py-3 text-lg': size === 'lg',
        },
        disabled && 'opacity-50 cursor-not-allowed',
      )}
    >
      {children}
    </button>
  );
};
```

#### 2. Component Organization

Complex components follow a structured pattern:

```typescript
// Directory structure
ComponentName/
├── index.tsx          # Exports the component
├── ComponentName.tsx  # Main implementation
├── hooks/             # Local hooks
│   └── useComponentState.ts
├── types.ts           # Component types
└── utils.ts           # Helper functions

// index.tsx
export { ComponentName } from './ComponentName';
export type { ComponentNameProps } from './types';

// ComponentName.tsx
import { useComponentState } from './hooks/useComponentState';
import type { ComponentNameProps } from './types';

export const ComponentName = ({ prop1, prop2 }: ComponentNameProps) => {
  const { state, actions } = useComponentState();
  
  return (
    <div>
      {/* Render */}
    </div>
  );
};
```

#### 3. React 19 Patterns

**NO useCallback - React 19's compiler handles optimization:**

```typescript
// ❌ BAD - Unnecessary useCallback
export const Form = () => {
  const handleSubmit = useCallback((data) => {
    api.post('/data', data);
  }, []);
  
  return <FormInput onSubmit={handleSubmit} />;
};

// ✅ GOOD - Plain function
export const Form = () => {
  const handleSubmit = (data) => {
    api.post('/data', data);
  };
  
  return <FormInput onSubmit={handleSubmit} />;
};
```

**NO useMemo - Compiler handles memoization:**

```typescript
// ❌ BAD - Unnecessary useMemo
export const List = ({ items, filter }) => {
  const filtered = useMemo(
    () => items.filter(i => i.type === filter),
    [items, filter]
  );
  
  return <ul>{filtered.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
};

// ✅ GOOD - Direct calculation
export const List = ({ items, filter }) => {
  const filtered = items.filter(i => i.type === filter);
  
  return <ul>{filtered.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
};
```

**Avoid stale closures - access object properties directly:**

```typescript
// ❌ BAD - Destructuring creates stale references
export const Editor = ({ state }) => {
  const { content, save } = state;
  
  useEffect(() => {
    const timer = setTimeout(() => {
      save(content); // 'content' is stale!
    }, 1000);
    return () => clearTimeout(timer);
  }, [content, save]);
};

// ✅ GOOD - Direct property access
export const Editor = ({ state }) => {
  useEffect(() => {
    const timer = setTimeout(() => {
      state.save(state.content); // Always fresh
    }, 1000);
    return () => clearTimeout(timer);
  }, [state]);
};
```

#### 4. Keyboard Event Handling

Always prevent default for game/editor controls:

```typescript
export const CodeEditor = () => {
  const handleKeyDown = (ev: KeyboardEvent) => {
    // Prevent Space from scrolling page
    if (ev.code === 'Space') {
      ev.preventDefault();
      // Handle space key
    }
    
    // Prevent Escape from closing dialogs
    if (ev.key === 'Escape') {
      ev.preventDefault();
      // Handle escape
    }
  };
  
  return <div onKeyDown={handleKeyDown}>{/* ... */}</div>;
};
```

#### 5. Button Focus Management

Buttons retain focus after clicking, interfering with keyboard shortcuts:

```typescript
export const Toolbar = () => {
  return (
    <>
      <button
        onClick={(e) => {
          handleSave();
          e.currentTarget.blur(); // Remove focus to prevent Space re-triggering
        }}
      >
        Save
      </button>
    </>
  );
};
```

---

## State Management

### Authentication State: `useAuth`

```typescript
// Location: workspaces/hooks/useAuth.ts

export const useAuth = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [user, setUser] = useState<User | null>(null);
  const [token, setToken] = useState<string | null>(
    () => localStorage.getItem('token')
  );

  // Load auth state on mount
  useEffect(() => {
    const load = async () => {
      if (!token) {
        setIsAuthenticated(false);
        setIsLoading(false);
        return;
      }

      try {
        // Verify token validity
        const response = await fetch('/api/auth/verify', {
          headers: { Authorization: `Bearer ${token}` },
        });

        if (response.ok) {
          const user = await response.json();
          setUser(user);
          setIsAuthenticated(true);
        } else {
          setIsAuthenticated(false);
          localStorage.removeItem('token');
        }
      } catch {
        setIsAuthenticated(false);
      } finally {
        setIsLoading(false);
      }
    };

    load();
  }, [token]);

  const login = async (email: string, password: string) => {
    const response = await fetch('/api/auth/signin', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });

    if (response.ok) {
      const { token, user } = await response.json();
      localStorage.setItem('token', token);
      setToken(token);
      setUser(user);
      setIsAuthenticated(true);
      return { success: true };
    }

    return { success: false, error: await response.text() };
  };

  const logout = async () => {
    try {
      await fetch('/api/auth/signout', {
        method: 'POST',
        headers: { Authorization: `Bearer ${token}` },
      });
    } catch {
      // Ignore errors during logout
    } finally {
      localStorage.removeItem('token');
      setToken(null);
      setUser(null);
      setIsAuthenticated(false);
    }
  };

  return {
    isLoading,
    isAuthenticated,
    user,
    token,
    login,
    logout,
  };
};

// Usage in components
export const Dashboard = () => {
  const { user, logout } = useAuth();
  
  return (
    <div>
      <p>Welcome, {user?.email}</p>
      <button onClick={logout}>Logout</button>
    </div>
  );
};
```

### Server Settings State: `useServerSettings`

```typescript
export const useServerSettings = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [onboardingComplete, setOnboardingComplete] = useState(false);
  const [plugins, setPlugins] = useState<Plugin[]>([]);
  const [settings, setSettings] = useState<ServerSettings | null>(null);

  useEffect(() => {
    const load = async () => {
      try {
        const response = await fetch('/api/server-settings');
        if (response.ok) {
          const data = await response.json();
          setOnboardingComplete(data.onboardingComplete);
          setPlugins(data.plugins);
          setSettings(data.settings);
        }
      } catch {
        console.error('Failed to load server settings');
      } finally {
        setIsLoading(false);
      }
    };

    load();
  }, []);

  return {
    isLoading,
    onboardingComplete,
    plugins,
    settings,
  };
};
```

### Global State: `useGlobalState`

For component-tree-wide state, use Context:

```typescript
type GlobalContextType = {
  theme: 'light' | 'dark';
  setTheme: (theme: 'light' | 'dark') => void;
  sidebarOpen: boolean;
  setSidebarOpen: (open: boolean) => void;
};

const GlobalContext = createContext<GlobalContextType | null>(null);

export const GlobalProvider = ({ children }: { children: React.ReactNode }) => {
  const [theme, setTheme] = useState<'light' | 'dark'>(() => {
    const saved = localStorage.getItem('theme');
    return (saved as 'light' | 'dark') || 'dark';
  });

  const [sidebarOpen, setSidebarOpen] = useState(true);

  useEffect(() => {
    localStorage.setItem('theme', theme);
    document.documentElement.classList.toggle('dark', theme === 'dark');
  }, [theme]);

  return (
    <GlobalContext.Provider value={{ theme, setTheme, sidebarOpen, setSidebarOpen }}>
      {children}
    </GlobalContext.Provider>
  );
};

export const useGlobalState = () => {
  const context = useContext(GlobalContext);
  if (!context) {
    throw new Error('useGlobalState must be used within GlobalProvider');
  }
  return context;
};
```

### React Query for Server State

```typescript
// useQueryPlan.ts
export const useQueryPlan = (id: number) => {
  return useQuery({
    queryKey: ['plans', id],
    queryFn: async () => {
      const response = await fetch(`/api/plans/${id}`, {
        headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
      });
      if (!response.ok) throw new Error('Failed to load plan');
      return response.json();
    },
  });
};

// useMutationCreatePlan.ts
export const useMutationCreatePlan = () => {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: async (data: CreatePlanInput) => {
      const response = await fetch('/api/plans', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${localStorage.getItem('token')}`,
        },
        body: JSON.stringify(data),
      });
      if (!response.ok) throw new Error('Failed to create plan');
      return response.json();
    },
    onSuccess: () => {
      // Invalidate related queries
      queryClient.invalidateQueries({ queryKey: ['plans'] });
    },
  });
};

// In component
export const CreatePlanForm = () => {
  const { mutate, isPending } = useMutationCreatePlan();
  
  const handleSubmit = (data: CreatePlanInput) => {
    mutate(data);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
      <button type="submit" disabled={isPending}>
        {isPending ? 'Creating...' : 'Create'}
      </button>
    </form>
  );
};
```

---

## API Communication

### Client Initialization: `src/apps/officer-web/lib/api.ts`

```typescript
// Centralized API client
const API_BASE = import.meta.env.VITE_API_URL || '/api';

export const apiClient = {
  async fetch(
    endpoint: string,
    options: RequestInit = {},
  ): Promise<Response> {
    const token = localStorage.getItem('token');
    
    return fetch(`${API_BASE}${endpoint}`, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...(token && { Authorization: `Bearer ${token}` }),
        ...options.headers,
      },
    });
  },

  async get<T>(endpoint: string): Promise<T> {
    const response = await this.fetch(endpoint);
    if (!response.ok) throw new Error(`GET ${endpoint} failed`);
    return response.json();
  },

  async post<T>(endpoint: string, body?: any): Promise<T> {
    const response = await this.fetch(endpoint, {
      method: 'POST',
      body: body ? JSON.stringify(body) : undefined,
    });
    if (!response.ok) throw new Error(`POST ${endpoint} failed`);
    return response.json();
  },

  async put<T>(endpoint: string, body?: any): Promise<T> {
    const response = await this.fetch(endpoint, {
      method: 'PUT',
      body: body ? JSON.stringify(body) : undefined,
    });
    if (!response.ok) throw new Error(`PUT ${endpoint} failed`);
    return response.json();
  },

  async delete<T>(endpoint: string): Promise<T> {
    const response = await this.fetch(endpoint, { method: 'DELETE' });
    if (!response.ok) throw new Error(`DELETE ${endpoint} failed`);
    return response.json();
  },
};

// Usage
const user = await apiClient.get<User>('/users/me');
await apiClient.post('/plans', { title: 'New Plan' });
```

---

## Real-Time Features

### Terminal WebSocket: `TerminalScreen.tsx`

```typescript
import { XTerm } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import '@xterm/xterm/css/xterm.css';

export const TerminalScreen = () => {
  const terminalRef = useRef<HTMLDivElement>(null);
  const xtermRef = useRef<XTerm | null>(null);
  const wsRef = useRef<WebSocket | null>(null);

  useEffect(() => {
    const term = new XTerm({
      cols: 120,
      rows: 40,
      theme: { background: '#0f172a', foreground: '#e2e8f0' },
    });

    const fitAddon = new FitAddon();
    term.loadAddon(fitAddon);

    // Mount terminal
    if (terminalRef.current) {
      term.open(terminalRef.current);
      fitAddon.fit();
    }

    xtermRef.current = term;

    // Connect WebSocket
    const token = localStorage.getItem('token');
    const cwd = '/home/user'; // Or from settings
    
    const ws = new WebSocket(
      `ws://localhost:5000/api/terminal/ws?token=${token}&cwd=${encodeURIComponent(cwd)}&cols=120&rows=40`
    );

    ws.onopen = () => {
      console.log('Terminal connected');
    };

    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      
      if (message.type === 'output') {
        term.write(message.data); // Display output
      } else if (message.type === 'error') {
        console.error('Terminal error:', message.error);
      }
    };

    ws.onerror = () => {
      term.write('\r\nConnection error\r\n');
    };

    ws.onclose = () => {
      term.write('\r\nDisconnected\r\n');
    };

    // Send input from terminal
    term.onData((data) => {
      ws.send(JSON.stringify({ type: 'input', data }));
    });

    wsRef.current = ws;

    // Handle resize
    const handleResize = () => {
      fitAddon.fit();
      const { cols, rows } = term;
      ws.send(JSON.stringify({ type: 'resize', cols, rows }));
    };

    window.addEventListener('resize', handleResize);

    // Cleanup
    return () => {
      window.removeEventListener('resize', handleResize);
      ws.close();
      term.dispose();
    };
  }, []);

  return <div ref={terminalRef} className="w-full h-full" />;
};
```

### Pi Chat WebSocket: `SessionListPage.tsx`

```typescript
export const SessionListPage = ({ sessionId, isNew }: Props) => {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [input, setInput] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const wsRef = useRef<WebSocket | null>(null);

  // Connect to Pi chat WebSocket
  useEffect(() => {
    const token = localStorage.getItem('token');
    const url = `ws://localhost:5000/api/pi/chat/ws?token=${token}&sessionId=${sessionId}`;

    const ws = new WebSocket(url);

    ws.onopen = () => {
      console.log('Pi chat connected');
    };

    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      
      switch (message.type) {
        case 'message_update':
          // Stream response text
          if (message.event.type === 'text_delta') {
            setMessages(prev => {
              const last = prev[prev.length - 1];
              if (last && last.role === 'assistant') {
                return [
                  ...prev.slice(0, -1),
                  { ...last, content: last.content + message.event.delta },
                ];
              }
              return prev;
            });
          }
          break;
          
        case 'tool_execution_start':
          setMessages(prev => [
            ...prev,
            { role: 'tool', toolName: message.toolName, content: 'Executing...' },
          ]);
          break;
          
        case 'agent_end':
          setIsLoading(false);
          break;
      }
    };

    ws.onerror = (error) => {
      console.error('Pi chat error:', error);
      setIsLoading(false);
    };

    wsRef.current = ws;

    return () => ws.close();
  }, [sessionId]);

  // Send message
  const handleSendMessage = () => {
    if (!input.trim() || !wsRef.current) return;

    const userMessage = input;
    setInput('');
    setIsLoading(true);

    // Add user message to UI
    setMessages(prev => [
      ...prev,
      { role: 'user', content: userMessage },
    ]);

    // Send to Pi
    wsRef.current.send(JSON.stringify({
      type: 'prompt',
      text: userMessage,
    }));
  };

  return (
    <div className="flex flex-col h-full">
      <div className="flex-1 overflow-auto p-4">
        {messages.map((msg, i) => (
          <div key={i} className={cn('mb-4', msg.role === 'user' ? 'text-right' : 'text-left')}>
            <div className={cn('inline-block p-2 rounded', 
              msg.role === 'user' ? 'bg-blue-600 text-white' : 'bg-slate-200'
            )}>
              {msg.content}
            </div>
          </div>
        ))}
        {isLoading && <div>Loading...</div>}
      </div>

      <div className="p-4 border-t">
        <div className="flex gap-2">
          <input
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
            placeholder="Ask Pi anything..."
            disabled={isLoading}
            className="flex-1 px-4 py-2 border rounded"
          />
          <button
            onClick={handleSendMessage}
            disabled={isLoading || !input.trim()}
            className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
          >
            Send
          </button>
        </div>
      </div>
    </div>
  );
};
```

---

## Styling & Theming

### Tailwind CSS Integration

Officer uses Tailwind CSS 4 with a custom configuration:

```javascript
// tailwind.config.js
export default {
  content: [
    './src/**/*.{ts,tsx}',
  ],
  theme: {
    extend: {
      colors: {
        // Custom color palette if needed
      },
      spacing: {
        // Custom spacing
      },
      animation: {
        // Custom animations
      },
    },
  },
  plugins: [
    require('tailwindcss-animate'),
  ],
};
```

### Shadcn/ui Components

Reusable UI components from shadcn/ui:

```typescript
// Button component wrapper
import { Button as ShadcnButton } from '@/components/ui/button';

export const Button = (props) => (
  <ShadcnButton {...props} />
);

// Usage
<Button variant="outline" size="lg">
  Click me
</Button>
```

### Custom Theming

Support for light/dark themes:

```typescript
// Global theme management
export const useTheme = () => {
  const { theme, setTheme } = useGlobalState();
  
  useEffect(() => {
    document.documentElement.classList.toggle('dark', theme === 'dark');
    localStorage.setItem('theme', theme);
  }, [theme]);
  
  return { theme, setTheme };
};

// In component
export const ThemeToggle = () => {
  const { theme, setTheme } = useTheme();
  
  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      {theme === 'dark' ? '☀️' : '🌙'}
    </button>
  );
};
```

---

## Performance & Optimization

### Image Optimization

Use Next Image component or lazy loading:

```typescript
import { lazy, Suspense } from 'react';

// Lazy load heavy components
const CodeEditor = lazy(() => import('./CodeEditor'));

export const FeaturePage = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <CodeEditor />
    </Suspense>
  );
};
```

### Virtual Scrolling for Large Lists

Use React Virtual for efficient rendering:

```typescript
import { useVirtualizer } from '@tanstack/react-virtual';

export const VirtualList = ({ items }: { items: Item[] }) => {
  const parentRef = useRef<HTMLDivElement>(null);
  
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} className="h-96 overflow-auto">
      <div style={{ height: `${virtualizer.getTotalSize()}px` }}>
        {virtualizer.getVirtualItems().map(virtualItem => (
          <div key={virtualItem.key} data-index={virtualItem.index}>
            {items[virtualItem.index]?.name}
          </div>
        ))}
      </div>
    </div>
  );
};
```

### Code Splitting with React Router

Routes are automatically code-split:

```typescript
// Lazy load screen components
const HomeScreen = lazy(() => import('./HomeScreen'));
const SettingsScreen = lazy(() => import('./SettingsScreen'));

<Suspense fallback={<LoadingSpinner />}>
  <Routes>
    <Route path="/" element={<HomeScreen />} />
    <Route path="/settings" element={<SettingsScreen />} />
  </Routes>
</Suspense>
```

### React Query Caching

Automatic caching and background updates:

```typescript
// Data is cached per queryKey
const { data: plans } = useQuery({
  queryKey: ['plans'], // Cached
  queryFn: fetchPlans,
  staleTime: 1000 * 60 * 5, // 5 minutes
  gcTime: 1000 * 60 * 10, // 10 minutes (was cacheTime)
});

// Background refetch when window regains focus
useQuery({
  queryKey: ['plans'],
  queryFn: fetchPlans,
  refetchOnWindowFocus: true,
});
```

---

## Development Workflow

### Development Server

```bash
# Start Bun dev server with hot reload
bun dev

# Runs on http://localhost:5000/
# Frontend and backend both reload on file changes
```

### Environment Variables

```bash
# .env (client-side, public)
VITE_API_URL=http://localhost:5000/api
```

### TypeScript Type Checking

```bash
# Run TypeScript compiler
tsc --noEmit

# Included in build process
bun run build
```

### Code Formatting

```bash
# Format all files
bun format

# Format specific files
bun format:check

# Prettier config in .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 120
}
```

### Component Development

When creating new features:

1. **Create component structure**
```
src/apps/officer-web/Screens/MyFeature/
├── index.tsx          # Exports
├── MyFeatureScreen.tsx # Component
├── hooks/
│   └── useMyFeature.ts
├── types.ts
└── utils.ts
```

2. **Define types**
```typescript
// types.ts
export type MyFeatureProps = {
  onSubmit: (data: FormData) => void;
  disabled?: boolean;
};
```

3. **Implement component**
```typescript
// MyFeatureScreen.tsx
import { useMyFeature } from './hooks/useMyFeature';
import type { MyFeatureProps } from './types';

export const MyFeatureScreen = ({ onSubmit, disabled }: MyFeatureProps) => {
  const { state, actions } = useMyFeature();
  
  return (/* JSX */);
};
```

4. **Export**
```typescript
// index.tsx
export { MyFeatureScreen } from './MyFeatureScreen';
export type { MyFeatureProps } from './types';
```

---

## Integration Points

### API Endpoints Used

- **Auth**: `/api/auth/*` (signin, signup, verify, etc.)
- **Plans**: `/api/plans` (GET, POST, PUT, DELETE)
- **Skills**: `/api/skills` (GET, POST)
- **Tasks**: `/api/tasks` (CRUD operations)
- **Files**: `/api/file-browser` (navigation, upload)
- **Settings**: `/api/user` (preferences)
- **Terminal WS**: `/api/terminal/ws` (real-time shell)
- **Pi Chat WS**: `/api/pi/chat/ws` (real-time AI assistant)

### State Flow from Server to UI

```
Database
  ↓
API Response
  ↓
React Query Cache
  ↓
Component State
  ↓
Rendered UI
```

---

## Debugging

### Browser DevTools

```javascript
// Log component props
console.log('Props:', props);

// Log state updates
console.log('State changed:', newState);

// React DevTools browser extension
// Inspect component tree and props
```

### React Query Devtools

```typescript
// Installed: @tanstack/react-query-devtools

import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

export function App() {
  return (
    <>
      {/* App content */}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  );
}
```

---

## Common Patterns

### Loading States

```typescript
export const DataComponent = () => {
  const { data, isLoading, error } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
  });

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!data) return <EmptyState />;

  return <DataView data={data} />;
};
```

### Form Handling with React Hook Form

```typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Min 8 chars'),
});

type FormData = z.infer<typeof schema>;

export const LoginForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = async (data: FormData) => {
    await apiClient.post('/auth/signin', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} placeholder="Email" />
      {errors.email && <span>{errors.email.message}</span>}
      
      <input {...register('password')} type="password" />
      {errors.password && <span>{errors.password.message}</span>}
      
      <button type="submit">Login</button>
    </form>
  );
};
```

### Toast Notifications

```typescript
import { toast } from 'sonner';

// Success
toast.success('Operation completed!');

// Error
toast.error('Something went wrong', {
  description: 'Please try again later',
});

// Custom
toast.custom((t) => (
  <div>Custom notification</div>
));
```

---

## Related Documentation

- **Backend Architecture**: See `OFFICERDEV_BACKEND.md`
- **CONVENTIONS.md**: Detailed code patterns and style
- **CLAUDE.md**: Project overview
- **apps/officer-web/CLAUDE.md**: Frontend-specific patterns

---

## Summary

Officer's frontend is a modern React 19 SPA featuring:
- Type-safe component architecture
- Real-time WebSocket communication
- Server state management with React Query
- Beautiful UI with Tailwind CSS and shadcn/ui
- Strong React patterns without useCallback/useMemo
- Comprehensive authentication and authorization
- Responsive design for all devices
- AI-powered development assistance via Pi
- Interactive terminal emulation

The architecture prioritizes developer experience, type safety, and performance while providing a rich, interactive user experience for life management and AI-assisted development.
