2682e0268c
- Integration tests: SSE auth, connection limits, lifecycle hooks, event persistence (6 tests)
- Instance events history API: GET /instances/{id}/events
- Documentation updates: terminal.md, backend.md, frontend.md
- Performance: SSE max 5 connections, health monitor write-on-change
Quality gates: pytest 21 monitoring passed, 172 unit passed (4 pre-existing), vitest 14 passed, tsc clean, eslint clean, ruff clean
9.0 KiB
9.0 KiB
Frontend Architecture
Overview
The Headquarter frontend is a React-based single-page application (SPA) built with modern tooling and designed for modularity and maintainability.
Tech Stack
| Layer | Technology | Version |
|---|---|---|
| Framework | React | ^18.2.0 |
| Router | React Router | ^6.20.0 |
| Bundler | Vite | ^5.0.0 |
| Language | TypeScript | ^5.3.0 |
| Styling | CSS3 with CSS Variables | - |
| Testing | Vitest + React Testing Library | ^4.1.6 |
Directory Structure
apps/web/src/
├── api/ # API clients
│ ├── auth.ts # Authentication API
│ ├── projects.ts # Project API
│ ├── git_repositories.ts # Repository API
│ ├── ssh_keys.ts # SSH key API
│ ├── tool_types.ts # Tool type API
│ ├── users.ts # User API
│ ├── events.ts # SSE events API
│ └── settings.ts # Settings API
├── components/ # Reusable components
│ ├── app-shell.tsx # Main app layout
│ ├── protected-route.tsx # Auth guard
│ ├── event-toast-bridge.tsx # Events → toasts
│ └── [more...]
├── state/ # Global state
│ ├── auth.tsx # Auth state management
│ ├── events.tsx # Event provider (SSE)
│ └── toast.tsx # Toast notifications
├── hooks/ # Custom hooks
│ ├── use-auth.ts # Auth hook
│ ├── use-theme.ts # Theme hook
│ └── use-events.ts # SSE events hook
├── pages/ # Page components (routes)
│ ├── dashboard.tsx # Dashboard
│ ├── projects.tsx # Project list
│ ├── repo-workspace.tsx # Repository workspace
│ ├── git-history.tsx # Git history
│ ├── git-repositories.tsx # Repository management
│ ├── profile.tsx # User profile
│ ├── settings.tsx # User settings
│ ├── tool-types.tsx # Tool types
│ ├── ssh-keys.tsx # SSH keys
│ └── [more...]
├── styles/ # Global styles
│ ├── index.css # Main stylesheet
│ └── [more...]
├── types.ts # Shared TypeScript types
├── router.tsx # Route definitions
└── main.tsx # Entry point
Architecture Patterns
1. Component Architecture
Page Components: Top-level components mapped to routes
- Own data fetching
- Manage page-level state
- Compose reusable components
Reusable Components: Shared UI elements
- No data fetching
- Receive data via props
- Emit events via callbacks
Example:
// Page component
const RepoWorkspace = () => {
const [files, setFiles] = useState([]);
// ... fetch data, manage state
return (
<div className="workspace">
<FileTree files={files} onFileClick={handleFileClick} />
<FileViewer content={content} />
</div>
);
};
// Reusable component
const FileTree = ({ files, onFileClick }: FileTreeProps) => {
return (
<ul>
{files.map(file => (
<li onClick={() => onFileClick(file)}>{file.name}</li>
))}
</ul>
);
};
2. State Management
URL State: Shareable, bookmarkable state
// Sync selections to URL
const [searchParams, setSearchParams] = useSearchParams();
// ?repo=123&branch=main&path=src/main.py
React Context: Global auth state
// Auth context provides user, login, logout
const { user, isAuthenticated } = useAuth();
Local State: Component-specific state
const [isEditing, setIsEditing] = useState(false);
3. API Client Pattern
Centralized API clients with type safety:
// api/git_repositories.ts
export const getRepositories = async (projectId: string) => {
const response = await fetch(`${API_BASE_URL}/projects/${projectId}/repositories`, {
credentials: 'include',
});
return response.json();
};
// Usage in component
const repos = await getRepositories(projectId);
4. Authentication Flow
User clicks Login
→ Redirect to /auth/login (backend)
→ Backend redirects to Authentik OAuth
→ User authenticates with Authentik
→ Authentik redirects to /auth/callback
→ Backend creates session cookie
→ Backend redirects to frontend
→ Frontend checks /auth/me
→ User is authenticated!
Auth State:
interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
}
Real-Time Events (SSE)
The frontend receives real-time instance events via Server-Sent Events:
EventSource → useEvents() hook → EventProvider → EventToastBridge → ToastContainer
Components:
useEvents(): Manages SSE connection with auto-reconnectEventProvider: Shares event stream across componentsEventToastBridge: Maps events to toast notificationsToastContainer: Displays and manages toast stack
Event-to-Toast Mapping:
| Event | Toast Severity | Auto-dismiss |
|---|---|---|
instance.starting |
Info | 3s |
instance.running |
Success | 3s |
instance.error |
Error | Persistent |
instance.stopped |
Info | 3s |
5. Routing Structure
// router.tsx
<Route path="/" element={<Dashboard />} />
<Route path="/projects" element={<ProjectsPage />} />
<Route path="/projects/:projectId" element={<RepoWorkspace />} />
<Route path="/projects/:projectId/repositories" element={<GitRepositories />} />
<Route path="/projects/:projectId/repositories/:repoId/history" element={<GitHistory />} />
<Route path="/profile" element={<ProfilePage />} />
<Route path="/settings" element={<SettingsPage />} />
<Route path="/ssh-keys" element={<SSHKeysPage />} />
<Route path="/tool-types" element={<ToolTypesPage />} />
Data Flow
Repository Workspace Example
1. User clicks project
→ Navigate to /projects/:id
2. RepoWorkspace mounts
→ Fetch project repositories
→ Select first repo (or from URL)
3. Repo selected
→ Fetch branches
→ Fetch file tree (default branch)
4. User clicks file
→ Fetch file content
→ Display in viewer
→ Update URL: ?path=src/main.py
5. User switches branch
→ Fetch file tree for branch
→ Re-fetch current file if viewing
→ Update URL: ?branch=develop
Component Communication
┌─────────────────────────────────────┐
│ RepoWorkspace │
│ ┌──────────┐ ┌──────────────┐ │
│ │ FileTree │───▶│ FileViewer │ │
│ │ │ │ │ │
│ │ onFileClick │ content │ │
│ │ │ │ onEdit │ │
│ └──────────┘ └──────────────┘ │
│ ▲ │
│ │ │
│ ┌──────────┐ │
│ │ Branch │───▶ fetch tree │
│ │ Selector │ │
│ └──────────┘ │
└─────────────────────────────────────┘
Styling Strategy
CSS Variables (Design Tokens)
:root {
--color-primary: #007bff;
--color-bg: #ffffff;
--color-text: #333333;
--sidebar-width: 250px;
--border-radius: 4px;
}
[data-theme="dark"] {
--color-bg: #1a1a1a;
--color-text: #e0e0e0;
}
Component Styles
- Each page/component has scoped CSS
- Global utilities in
styles/index.css - No CSS-in-JS library (keep it simple)
Testing Strategy
Unit Tests (Vitest)
// Component test
import { render, screen } from '@testing-library/react';
import { FileTree } from './file-tree';
test('renders file list', () => {
const files = [{ name: 'test.py', type: 'file' }];
render(<FileTree files={files} onFileClick={() => {}} />);
expect(screen.getByText('test.py')).toBeInTheDocument();
});
Test Coverage
- Component rendering
- User interactions
- Auth state changes
- API mocking
Performance Considerations
- Code Splitting: Vite handles automatic chunking
- Lazy Loading: React.lazy() for heavy pages
- Debouncing: URL updates debounced (300ms)
- Caching: Browser caches API responses (ETags)
- Optimistic UI: Immediate feedback before API response
Future Improvements
- Implement real-time updates (SSE)
- Add React Query for server state management
- Implement virtual scrolling for large file trees
- Add service worker for offline support
- Add error boundary components
Development Workflow
# Start dev server
cd apps/web && npm run dev
# Run tests
npm run test
# Type check
npm run typecheck
# Lint
npm run lint
# Build for production
npm run build