Developer Guide
Quick reference for all APIs, hooks, components, and design tokens available in the Flightdeck web application.
Tech Stack
| Layer | Technology |
|---|---|
| Runtime | Node.js with npm workspaces monorepo |
| Frontend | React 19.2, TypeScript 5.9, Vite 8, Tailwind CSS 4 |
| State Management | Zustand 5 (stores in src/stores/), TanStack Query 5 (server state) |
| Testing | Vitest 4 + @testing-library/react 16, Playwright for E2E |
| Backend | Express 5, better-sqlite3, Drizzle ORM, WebSocket (ws) |
| Shared | @flightdeck/shared — Zod schemas, shared types (workspace dependency) |
| Charts | visx (d3-based) |
| Build | Vite 8 with @vitejs/plugin-react, dev server proxies API/WS to :3001 |
API Endpoints
Agents
| Method | Endpoint | Description |
|---|---|---|
| GET | /agents | List all agents |
| DELETE | /agents/:id | Terminate agent |
| POST | /agents/:id/interrupt | Interrupt agent |
| POST | /agents/:id/restart | Restart agent |
| POST | /agents/:id/message | Send message to agent |
| GET | /agents/:id/messages?limit=200 | Get agent messages |
| POST | /agents/:id/permission | Resolve permission request |
Sessions & Leads
| Method | Endpoint | Description |
|---|---|---|
| GET | /lead | List active leads |
| POST | /lead/start | Start new lead session |
| GET | /lead/:id/progress | Get lead progress |
| GET | /lead/:id/dag | Get task DAG |
| GET | /lead/:id/decisions | Get pending decisions |
| POST | /lead/:id/message | Send message to lead |
| GET | /lead/:id/groups | List groups |
| POST | /lead/:id/groups | Create group |
| DELETE | /lead/:id | Delete lead |
Decisions
| Method | Endpoint | Description |
|---|---|---|
| POST | /decisions/:id/approve | Approve decision |
| POST | /decisions/:id/reject | Reject decision |
| POST | /decisions/:id/respond | Respond with text |
| POST | /decisions/:id/feedback | Send feedback |
Natural Language
| Method | Endpoint | Description |
|---|---|---|
| GET | /nl/commands | List all 30 NL commands |
| POST | /nl/preview | Preview command before executing |
| POST | /nl/execute | Execute NL command |
| POST | /nl/undo | Undo last command |
| GET | /nl/suggestions | Context-aware suggestions |
Onboarding
| Method | Endpoint | Description |
|---|---|---|
| GET | /onboarding/status | Get mastery status |
| POST | /onboarding/progress | Update progress |
Predictions (Removed)
Note: The Predictions API has been removed. The endpoints below are no longer active. See Removed Components for details.
Workflows (Removed)
Note: The Workflow Automation API has been removed. This feature was incomplete and is no longer available.
| GET | /commits | List commits | | GET | /commits/by-task/:taskId | Commits for task |
Conflicts
| Method | Endpoint | Description |
|---|---|---|
| GET | /conflicts | Active conflicts |
| POST | /conflicts/:id/resolve | Resolve conflict |
| POST | /conflicts/:id/dismiss | Dismiss conflict |
| GET | /conflicts/config | Get config |
| PUT | /conflicts/config | Update config |
Intent Rules (Removed)
Note: The Intent Rules API has been removed. Use the Oversight System instead.
Recovery (Removed)
Note: The Recovery/Handoff API has been removed. Agent crash recovery is now handled automatically by the orchestrator.
Oversight
| Method | Endpoint | Description |
|---|---|---|
| GET | /config/yaml | Get full config (includes oversight section) |
| PATCH | /config | Update oversight level and custom instructions |
Playbooks (Removed)
Note: The Playbooks API has been removed.
Roles
| Method | Endpoint | Description |
|---|---|---|
| GET | /roles | List roles |
| POST | /roles | Create role |
| PUT | /roles/:id | Update role |
| DELETE | /roles/:id | Delete role |
| POST | /roles/test | Test role config |
Session Replay
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/replay/:leadId/keyframes | Get keyframes |
| POST | /replay/:leadId/share | Create share link |
| GET | /api/shared/:token | Get shared replay |
Notifications
| Method | Endpoint | Description |
|---|---|---|
| PUT | /notifications/settings | Update notification preferences |
| GET | /notifications/routing | Get notification routing config |
Analytics & Token Usage
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/costs/by-agent | Costs by agent |
| GET | /api/costs/by-task | Costs by task |
| GET | /api/coordination/status | Coordination status |
| GET | /api/coordination/timeline | Timeline events |
React Hooks
All hooks are in src/hooks/. Import example: import { useProjects } from '../hooks/useProjects';
Data Fetching Hooks
| Hook | Returns | Description |
|---|---|---|
useApi() | { spawnAgent, terminateAgent, updateConfig, createRole, ... } | Core API client |
useFocusAgent(agentId) | { data, loading, error, refresh } | Polls agent activity, diffs, decisions |
useDiffSummary(agentId) | { summary, loading } | Lightweight diff stats |
useProjects() | { projects, loading } | Fetch projects from REST API |
useHistoricalAgents(projectId) | Agent[] | Derive agent data from keyframes for historical sessions |
useConflicts() | { conflicts, activeConflicts, loading, resolve, dismiss } | Conflict alerts |
useConflictConfig() | { config, saveConfig } | Detection config |
useSessionReplay(leadId) | { keyframes, worldState, playing, currentTime, seek, setSpeed, ... } | Session replay |
UI State Hooks
| Hook | Returns | Description |
|---|---|---|
useCommandPalette() | { isOpen, open, close, toggle } | ⌘K palette state |
useRecentCommands() | { recent, addRecent, clearRecent } | Recent command history (localStorage) |
useProgressiveRoutes() | { tier, visibleRoutes, hiddenRoutes } | Progressive sidebar disclosure |
useDashboardLayout() | { panels, allPanels, togglePanel, movePanel, reorderPanels } | Dashboard panel config |
useAutoScroll(ref, deps) | void | Auto-scroll container to bottom |
useIdleTimer(timeout) | { isIdle } | User inactivity detection |
useAttachments() | { attachments, addAttachment, removeAttachment } | File attachment state |
useFileDrop(onDrop) | { isDragOver, handleDragOver, handleDrop, ... } | Drag-and-drop files |
useGlassTooltips() | void | Global [title] → glass tooltip conversion |
WebSocket
import { useWebSocket } from '../hooks/useWebSocket';
import { sendWsMessage } from '../hooks/useWebSocket';
const ws = useWebSocket(); // Full WS client
sendWsMessage({ type: 'queue_open' }); // Send from anywhereShared Components
Import: import { EmptyState, SkeletonCard, SkeletonList, ErrorPage } from '../components/Shared';
EmptyState
<EmptyState
icon="📊"
title="No active sessions"
description="Sessions appear when you create a project and agents start working."
action={{ label: "Create Project →", onClick: () => navigate('/') }}
/>
// compact variant for inline use:
<EmptyState icon="📋" title="No items" compact />Props: icon?: string, title: string, description?: string, action?: { label, onClick }, compact?: boolean, children?: ReactNode
SkeletonCard / SkeletonList
<SkeletonCard lines={3} showHeader showAvatar />
<SkeletonList count={5} />SkeletonCard props: lines?: number, showHeader?: boolean, showAvatar?: boolean, className?: string SkeletonList props: count?: number, cardProps?: SkeletonCardProps, className?: string
ErrorPage
<ErrorPage
title="Failed to load agents"
message="The server is unavailable."
detail="ECONNREFUSED localhost:3001"
statusCode={503}
onRetry={() => refetch()}
onGoHome={() => navigate('/')}
/>Props: title?: string, message?: string, detail?: string, statusCode?: number, onRetry?: () => void, onGoHome?: () => void
Motion System
Import: styles are global via src/styles/motion.css. Just add class names.
Animation Classes
| Class | Duration | Use for |
|---|---|---|
motion-fade-in | 250ms | Universal entry |
motion-slide-in | 250ms | Content entering from left |
motion-slide-in-right | 250ms | Panels, slide-overs |
motion-slide-up | 250ms | Toasts, bottom sheets |
motion-slide-down | 250ms | Dropdowns, notifications |
motion-scale-in | 250ms | Modals, popovers |
motion-scale-in-spring | 450ms | Playful/dramatic entrances |
motion-pulse | 2s ∞ | Status indicators |
motion-pulse-border | 2s ∞ | Attention-drawing borders |
motion-glow | 2s ∞ | Active/selected state |
motion-fade-out | 120ms | Exit animation |
Transition Presets
| Class | Duration | Use for |
|---|---|---|
transition-micro | 120ms | Hover states, toggles |
transition-standard | 250ms | Panel transitions |
transition-dramatic | 450ms | Page transitions |
Stagger Pattern
{items.map((item, i) => (
<div
key={item.id}
className="motion-stagger"
style={{ '--stagger-index': i } as React.CSSProperties}
>
{/* content */}
</div>
))}Reduced Motion
All animations respect prefers-reduced-motion: reduce automatically (0ms duration).
Chart Theme
Import: styles are global via src/styles/chart-theme.css. Use CSS custom properties.
Series Colors
--chart-1 through --chart-8 — indigo, cyan, amber, purple, green, rose, blue, orange
Semantic Colors
| Variable | Use |
|---|---|
--chart-success | Positive/completed |
--chart-warning | Caution/amber |
--chart-danger | Error/red |
--chart-info | Informational/blue |
--chart-neutral | Inactive/muted |
Agent Status Colors
--chart-running, --chart-idle, --chart-completed, --chart-failed, --chart-creating, --chart-terminated
Communication Edge Colors
--chart-edge-delegation, --chart-edge-message, --chart-edge-group, --chart-edge-broadcast, --chart-edge-report
Structural
--chart-grid, --chart-axis, --chart-tooltip-bg, --chart-tooltip-text, --chart-tooltip-border
Usage with visx
<LinePath
stroke={`rgb(var(--chart-1))`}
// ...
/>
<AxisBottom
stroke={`rgb(var(--chart-axis))`}
tickStroke={`rgb(var(--chart-axis))`}
tickLabelProps={{ fill: `rgb(var(--chart-neutral))` }}
/>All variables use RGB triplets for opacity support: rgba(var(--chart-1), 0.5).
Key Patterns
TanStack Query (Server State)
Use useQuery from @tanstack/react-query for all server data fetching:
import { useQuery } from '@tanstack/react-query';
import { apiFetch } from '../hooks/useApi';
const { data, isLoading, error } = useQuery({
queryKey: ['agents', projectId],
queryFn: ({ signal }) => apiFetch(`/api/agents?project=${projectId}`, { signal }),
refetchInterval: 5000,
});API Fetching (Low-Level)
import { apiFetch } from '../hooks/useApi';
// GET
const data = await apiFetch<MyType[]>('/projects');
// POST
await apiFetch('/nl/execute', {
method: 'POST',
body: JSON.stringify({ commandId: 'nl-pause-all' }),
});Always handle both response shapes:
const data = await apiFetch<unknown>('/endpoint');
const items = Array.isArray(data) ? data : data?.items ?? [];Zustand Store (Client State)
Stores live in src/stores/. Define stores with Zustand 5's create:
import { create } from 'zustand';
const useAppStore = create<AppState>((set, get) => ({
agents: [],
setAgents: (agents) => set({ agents }),
}));
// Testing: useAppStore.getState().setAgents([...])Use selectors to avoid unnecessary re-renders:
// ✅ Good — re-renders only when agents changes
const agents = useAppStore(s => s.agents);
// ❌ Bad — re-renders on ANY store change
const { agents, config } = useAppStore();Theme Classes
Always use th- prefixed classes, never raw colors:
- Background:
bg-th-bg,bg-th-bg-alt - Text:
text-th-text,text-th-text-muted - Border:
border-th-border,border-th-border-muted - Accent:
bg-accent/20 text-accent