## 1. Security Critical — Git & Credentials

- [x] 1.1 `[haiku]` Add `.env` to `.gitignore` and run `git rm --cached .env` to untrack it
- [x] 1.2 `[haiku]` Add `models/` and `*.pkl` to `.gitignore`
- [x] 1.3 `[haiku]` Replace real credentials in `.env.example` with placeholders (`POSTGRES_PASSWORD=change_me_to_a_strong_password`)
- [x] 1.4 `[haiku]` Remove SQL comment with credentials from `services/ml/app/db.py` and add fail-fast check for missing `DATABASE_URL`
- [x] 1.5 `[sonnet]` Update `docker-compose.yml` to use `${POSTGRES_USER}`, `${POSTGRES_PASSWORD}`, `${POSTGRES_DB}` env var interpolation in all DATABASE_URL values
- [x] 1.6 `[haiku]` Bind PostgreSQL port to `127.0.0.1:5432:5432` in `docker-compose.yml`
- [x] 1.7 `[haiku]` Bind MLflow port to `127.0.0.1:5000:5000` in `docker-compose.yml`
- [x] 1.8 `[haiku]` Bind ML service port to `127.0.0.1:8001:8001` in `docker-compose.yml`

## 2. Security Critical — Input Validation & CORS

- [x] 2.1 `[haiku]` Validate `run_id` matches `/^[a-zA-Z0-9_-]+$/` in `src/app/api/training/runs/[run_id]/route.ts` before interpolation
- [x] 2.2 `[sonnet]` Validate `run_id` format and use `Path.resolve()` + directory containment check in `services/ml/app/main.py` (model load at line 1203, delete at line 1312)
- [x] 2.3 `[sonnet]` Add file size check (reject >10MB) and row count limit (500,000) to `src/app/api/upload/route.ts`
- [x] 2.4 `[haiku]` Add file type validation (`.csv` extension, text MIME type) to `src/app/api/upload/route.ts`
- [x] 2.5 `[haiku]` Fix CORS in `services/ml/app/main.py`: replace `allow_origins=["*"]` with `["http://localhost:3000"]` and support `CORS_ORIGINS` env var

## 3. Authentication

- [x] 3.1 `[sonnet]` Create `src/middleware.ts` with API key auth middleware: read `API_KEY` env var, check `X-API-Key` header on all `/api/*` routes except `/api/health`, return 401 if invalid
- [x] 3.2 `[sonnet]` Add FastAPI `Depends()` API key dependency in `services/ml/app/main.py`: read `API_KEY` env var, check `X-API-Key` header, exempt `/health` endpoint
- [x] 3.3 `[sonnet]` Update all Next.js proxy routes to forward `X-API-Key` header to ML service
- [x] 3.4 `[haiku]` Add `API_KEY` to `.env.example` with placeholder value and instructions

## 4. API Route Hardening (Next.js)

- [x] 4.1 `[sonnet]` Add Zod schema validation to `src/app/api/predict/route.ts` (validate pair, timeframe, candles array)
- [x] 4.2 `[sonnet]` Add Zod schema validation to `src/app/api/predict/batch/route.ts` (validate pair, timeframe, start_date, end_date)
- [x] 4.3 `[sonnet]` Add Zod schema validation to `src/app/api/model/load/route.ts` (validate run_id)
- [x] 4.4 `[sonnet]` Add Zod schema validation to `src/app/api/training/start/route.ts` (validate model_type)
- [x] 4.5 `[sonnet]` Add Zod schema validation to `src/app/api/patterns/detect/route.ts` (validate candles, patterns array)
- [x] 4.6 `[sonnet]` Replace `error.message` with generic `"Internal server error"` in all catch blocks across 7+ route files: `health/route.ts`, `candles/route.ts`, `annotations/route.ts`, `annotations/[id]/route.ts`, `upload/route.ts`, `export/route.ts`, `span-annotations/export/route.ts`
- [x] 4.7 `[sonnet]` Require `chartId` for bulk delete in `src/app/api/annotations/route.ts` — reject `?all=true` without chartId with HTTP 400
- [x] 4.8 `[sonnet]` Wrap chart cascade delete in `db.transaction()` and add `spanAnnotations` deletion in `src/app/api/charts/[id]/route.ts`
- [x] 4.9 `[haiku]` Add `parseInt(value, 10)` with `isNaN()` guard to all routes parsing integer query params
- [x] 4.10 `[sonnet]` Add CSV injection protection (prefix `=+@-` cells with `'`) to all export routes
- [x] 4.11 `[sonnet]` Add `response.ok` checks before `.json()` in `src/app/page.tsx` (lines 214, 230, 245, 257)
- [x] 4.12 `[sonnet]` Add `response.ok` checks before `.json()` in `src/components/CandleChart.tsx` (lines 163, 178, 192)

## 5. ML Service Hardening (Python)

- [x] 5.1 `[sonnet]` Replace `error.message` / traceback details with generic `"Internal server error"` in FastAPI exception handlers at lines 640, 778, 1091, 1134, 1199, 1296 of `services/ml/app/main.py`
- [x] 5.2 `[opus]` Add SHA256 model integrity check: create `models/checksums.sha256` manifest, verify hash before `joblib.load()` in `services/ml/app/main.py:266`
- [x] 5.3 `[sonnet]` Add `_model_swap_lock` to prediction reads (not just writes) in `services/ml/app/main.py` for thread-safe model access
- [x] 5.4 `[sonnet]` Add date range validation (max 1 year) to `POST /predict/batch` in `services/ml/app/main.py`
- [x] 5.5 `[sonnet]` Add candle time-sort validation/auto-sort to `POST /predict` in `services/ml/app/main.py`
- [x] 5.6 `[sonnet]` Implement real health checks: `SELECT 1` for PostgreSQL, MLflow API ping in `services/ml/app/main.py:396-409`
- [x] 5.7 `[sonnet]` Add training resource limits: 500MB dataset size check, 30-minute timeout with status update on expiry in `services/ml/app/main.py:907-1030`
- [x] 5.8 `[haiku]` Add `run_id` format validation to `DELETE /training/runs/{run_id}` and `GET /training/runs/{run_id}` endpoints

## 6. Infrastructure & Docker

- [x] 6.1 `[sonnet]` Add `headers()` function to `next.config.js` with X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Content-Security-Policy
- [x] 6.2 `[sonnet]` Add `USER appuser` to `services/ml/Dockerfile`: create user with `useradd`, set ownership, add USER directive before CMD
- [x] 6.3 `[haiku]` Create `.dockerignore` with `.git`, `.env`, `.env*`, `node_modules`, `.next`, `data/`, `*.md`, `__pycache__/`, `mlruns/`, `models/`
- [x] 6.4 `[haiku]` Change TA-Lib download URL to HTTPS in `services/ml/Dockerfile:10`
- [x] 6.5 `[sonnet]` Add SHA256 checksum verification for TA-Lib download in `services/ml/Dockerfile`
- [x] 6.6 `[haiku]` Remove `COPY --from=builder /app/node_modules ./node_modules` line from `Dockerfile:29` (standalone doesn't need it)
- [x] 6.7 `[sonnet]` Pin Docker base images to `@sha256:` digests in both Dockerfiles
- [x] 6.8 `[haiku]` Fix healthcheck tool mismatch: use same tool (curl) in Dockerfile and docker-compose.yml

## 7. Frontend — Stale Closures & Race Conditions

- [x] 7.1 `[opus]` Fix stale closure in `fetchPredictions`: extract `modelInfo` into a `useRef` that stays in sync with state, use ref in `generateCacheKey` (`src/app/page.tsx:489-552`)
- [x] 7.2 `[opus]` Add AbortController to `fetchPredictions` and `handleFetchBatchPredictions`: store controller in ref, abort previous on new request, discard stale responses (`src/app/page.tsx:494-663`)
- [x] 7.3 `[opus]` Fix stale closure in CandleChart click handler: convert `drawingState`, `selectedLineId`, `dragState`, `annotations` to refs, update refs alongside setState, read refs in handler (`src/components/CandleChart.tsx:572-971`)
- [x] 7.4 `[opus]` Fix stale closure in SpanAnnotationManager keyboard handler: wrap `handleDeleteSpan` in `useCallback`, use ref for `selectedSpanId` (`src/components/SpanAnnotationManager.tsx:461-535`)

## 8. Frontend — Memory Leaks & Performance

- [x] 8.1 `[opus]` Fix SpanAnnotationManager preview primitive memory leak: replace `useState` with `useRef` for preview primitive, add cleanup on unmount (`src/components/SpanAnnotationManager.tsx:265-324`)
- [x] 8.2 `[sonnet]` Use `chart.applyOptions()` for theme changes instead of re-creating chart (`src/components/CandleChart.tsx:333`)
- [x] 8.3 `[sonnet]` Remove `fitContent()` from reconciliation effect, only call on initial load (`src/components/SpanAnnotationManager.tsx:160`)
- [x] 8.4 `[opus]` Implement incremental primitive updates in SpanAnnotationManager: update only selection state on selection change, full reconciliation only on annotation list changes (`src/components/SpanAnnotationManager.tsx:104-161`)
- [x] 8.5 `[sonnet]` Add bounded prediction cache (max 100 entries, FIFO eviction) to `predictionCacheRef` (`src/app/page.tsx:195-199`)
- [x] 8.6 `[sonnet]` Fix hardcoded 1-minute candle interval: detect interval from data, use for span iteration (`src/components/CandleChart.tsx:452`)
- [x] 8.7 `[haiku]` Extract magic numbers (8px, 60s, colors) to named constants in `CandleChart.tsx`
- [x] 8.8 `[haiku]` Move `new Set<string>()` default prop to module-level constant in `CandleChart.tsx:125`

## 9. Frontend — Shared Types & Type Safety

- [x] 9.1 `[sonnet]` Create `src/types/candles.ts` with `Candle` interface
- [x] 9.2 `[sonnet]` Create `src/types/annotations.ts` with `Annotation`, `AnnotationType`, `Geometry` interfaces
- [x] 9.3 `[sonnet]` Create `src/types/charts.ts` with `Chart` interface
- [x] 9.4 `[sonnet]` Create `src/types/predictions.ts` with `PredictionSpan`, `PredictionState`, `ModelInfo` interfaces
- [x] 9.5 `[sonnet]` Create `src/types/span-annotations.ts` with `SpanAnnotation`, `SpanLabelType`, `SubSpan` interfaces
- [x] 9.6 `[haiku]` Create `src/types/index.ts` barrel file re-exporting all types
- [x] 9.7 `[sonnet]` Replace duplicate interfaces in `page.tsx`, `CandleChart.tsx`, `SpanAnnotationManager.tsx`, `Toolbox.tsx`, `SpanAnnotationList.tsx`, `SpanPopover.tsx` with imports from `@/types`
- [x] 9.8 `[sonnet]` Replace all `any` types with proper interfaces: `geometry` → `Geometry | null`, `sub_spans` → `SubSpan[]`, candle arrays → `Candle[]`, prediction cache → `Map<string, PredictionSpan[]>`

## 10. Frontend — Error Boundary & UX

- [x] 10.1 `[sonnet]` Create `src/components/ErrorBoundary.tsx` React class component with error state, fallback UI (error message + "Try Again" + "Reload" buttons), and `console.error` logging
- [x] 10.2 `[haiku]` Wrap `{children}` with ErrorBoundary in `src/app/layout.tsx`
- [x] 10.3 `[sonnet]` Add confirmation dialog before delete-all annotations in `src/app/page.tsx:412-425` using Radix Dialog
- [x] 10.4 `[haiku]` Fix `SpanAnnotationList.tsx:110-115` confidence check: replace falsy check with `!= null` for confidence value 0

## 11. Frontend — Accessibility

- [ ] 11.1 `[sonnet]` Add `aria-label` attributes to all toolbar buttons in `Toolbox.tsx`
- [ ] 11.2 `[sonnet]` Add `role="dialog"`, `aria-modal="true"`, and focus trapping to `KeyboardShortcutsModal.tsx`
- [ ] 11.3 `[sonnet]` Make `ChartSelector.tsx` keyboard accessible: add arrow key navigation and click-outside close handler
- [ ] 11.4 `[sonnet]` Add `aria-pressed` to toggle buttons (span drawing mode, prediction visibility)

## 12. Frontend — Polish & Cleanup

- [ ] 12.1 `[haiku]` Remove unused imports `TrendingUp`, `ChevronUp` from `Toolbox.tsx:4`
- [ ] 12.2 `[haiku]` Remove `@ts-ignore` in `Toolbox.tsx:246-247`, replace with proper type assertion for CSS custom property
- [ ] 12.3 `[sonnet]` Fix dark theme on `annotation-types/page.tsx` and `span-label-types/page.tsx`: replace hardcoded light colors with theme-aware CSS variables
- [ ] 12.4 `[sonnet]` Add debounce (150ms) or `onPointerUp` to confidence slider in `PredictionPanel.tsx:148-155`
- [ ] 12.5 `[sonnet]` Replace Google Font CSS `@import` in `globals.css` with `next/font/google` in `layout.tsx`
- [ ] 12.6 `[haiku]` Remove manual Escape handler in `SpanPopover.tsx:114-117` (conflicts with Radix Dialog)
- [ ] 12.7 `[sonnet]` Fix `TalibPatternPanel.tsx:39` re-fetch when API returns empty array: add `hasLoaded` flag
- [ ] 12.8 `[haiku]` Add `[candles]` to `useImperativeHandle` dependency array in `CandleChart.tsx:202-221`
- [ ] 12.9 `[haiku]` Add `activeChartId` to primitive cleanup effect dependency arrays in `CandleChart.tsx:1059-1110`
- [ ] 12.10 `[sonnet]` Implement or remove no-op Tooltip component in `ui/tooltip.tsx` (replace with Radix Tooltip if used)
- [ ] 12.11 `[haiku]` Remove `eslint-disable` in `TrainingPanel.tsx:128-129`, include stable callbacks in dependency array
- [ ] 12.12 `[haiku]` Remove dead filter code (TODO comment, no-op) in `page.tsx:49-53`

## 13. Package & Dependencies

- [ ] 13.1 `[haiku]` Move `@types/node`, `@types/react`, `@types/react-dom`, `@types/papaparse`, `@types/pg` from `dependencies` to `devDependencies` in `package.json`
- [ ] 13.2 `[haiku]` Move `typescript`, `eslint`, `eslint-config-next`, `autoprefixer`, `postcss` from `dependencies` to `devDependencies` in `package.json`
- [ ] 13.3 `[haiku]` Add `zod` to `dependencies` in `package.json`

## 14. Dead Code Removal

- [ ] 14.1 `[haiku]` Delete `src/lib/db/migrate.ts` (dead SQLite migration code)
- [ ] 14.2 `[haiku]` Remove `get_db_session()` function from `services/ml/app/db.py:99-111`
- [ ] 14.3 `[haiku]` Remove dead `inference*` package reference from `pyproject.toml:32`
- [ ] 14.4 `[haiku]` Fix inconsistent `uuid as uuid_lib` import in `services/ml/app/main.py:9` — use standard `import uuid`
- [ ] 14.5 `[haiku]` Remove duplicate `TALIB_PATTERNS` dict — import from one location (`app/patterns.py` or `generate_talib_annotations.py`)

## 15. Deprecated Python API Replacements

- [ ] 15.1 `[sonnet]` Replace `@app.on_event("startup")` with FastAPI lifespan pattern in `services/ml/app/main.py:307`
- [ ] 15.2 `[haiku]` Replace `declarative_base()` with `class Base(DeclarativeBase)` in `services/ml/app/db.py:45`
- [ ] 15.3 `[haiku]` Replace all `datetime.utcnow()` with `datetime.now(datetime.UTC)` in `services/ml/app/main.py:325,1000,1019,1080`
- [ ] 15.4 `[haiku]` Add missing fallback return for volume indicators in `features/talib_features.py:169-190`
