--- inclusion: fileMatch fileMatchPattern: "frontend/**" --- # Frontend Conventions ## Stack - React 19, TypeScript strict mode, Vite 8 - Tailwind CSS with custom dark theme (surface-*, brand-* colors) - TanStack Router (file-based routes in `routes.tsx`) - TanStack Query for data fetching (hooks in `api/hooks.ts`) - Recharts for charts, Monaco Editor for SQL, Lucide for icons ## API Client - `api/client.ts` — shared fetch wrapper with `apiGet`, `apiPost`, `apiPut`, `apiDelete` - Three API bases: `query` (→ `/api/`), `registry` (→ `/registry/`), `risk` (→ `/risk/`) - Base URLs use `||` fallback (not `??`) because Vite inlines empty string for undefined env vars - All hooks in `api/hooks.ts` — typed with TanStack Query ## Testing - Vitest + MSW (Mock Service Worker) for deterministic tests - Requires Node.js 24 via nvm — load before running: `export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && nvm use 24` - Test setup: `src/test/setup.ts` starts MSW server - Mock handlers: `src/test/mocks/handlers.ts` - Test helper: `src/test/render.tsx` provides `renderRoute(path)` with QueryClient + Router - Run: `npx vitest --run` ## Components - Shared UI in `components/ui.tsx`: StatusBadge, ConfidenceBar, TrendArrow, DateRangeSelector, TickerFilter, LoadingSpinner, ErrorBoundary, Card - DataTable in `components/DataTable.tsx`: generic sortable/filterable/paginated table - AppLayout in `components/AppLayout.tsx`: sidebar nav + main content area ## Docker - `frontend/Dockerfile`: multi-stage node:24-alpine → nginxinc/nginx-unprivileged:alpine - Listens on port 8080 (not 80) for K8s security context compatibility - `frontend/nginx.conf`: SPA fallback + `/api/`, `/registry/`, `/risk/` reverse proxies ## Adding a New Page 1. Create `src/pages/MyPage.tsx` 2. Add route in `src/routes.tsx` 3. Add nav item in `components/AppLayout.tsx` navItems array 4. Add API hooks in `api/hooks.ts` if needed 5. Add MSW handler in `test/mocks/handlers.ts` 6. Add test in `test/pages.test.tsx`