candle-annotator/openspec/changes/code-review-fix/tasks.md
Marko Djordjevic 8b40a2cb9a feat: create src/types/candles.ts with Candle interface (task 9.1)
Add shared Candle interface with time, open, high, low, close, and
optional volume fields. Volume is optional because the DB schema does
not store volume but the predict/patterns API routes accept it as an
optional field.

Mark task 9.1 as complete in tasks.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:26:14 +01:00

13 KiB

1. Security Critical — Git & Credentials

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

2. Security Critical — Input Validation & CORS

  • 2.1 [haiku] Validate run_id matches /^[a-zA-Z0-9_-]+$/ in src/app/api/training/runs/[run_id]/route.ts before interpolation
  • 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)
  • 2.3 [sonnet] Add file size check (reject >10MB) and row count limit (500,000) to src/app/api/upload/route.ts
  • 2.4 [haiku] Add file type validation (.csv extension, text MIME type) to src/app/api/upload/route.ts
  • 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

  • 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
  • 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
  • 3.3 [sonnet] Update all Next.js proxy routes to forward X-API-Key header to ML service
  • 3.4 [haiku] Add API_KEY to .env.example with placeholder value and instructions

4. API Route Hardening (Next.js)

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

5. ML Service Hardening (Python)

  • 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
  • 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
  • 5.3 [sonnet] Add _model_swap_lock to prediction reads (not just writes) in services/ml/app/main.py for thread-safe model access
  • 5.4 [sonnet] Add date range validation (max 1 year) to POST /predict/batch in services/ml/app/main.py
  • 5.5 [sonnet] Add candle time-sort validation/auto-sort to POST /predict in services/ml/app/main.py
  • 5.6 [sonnet] Implement real health checks: SELECT 1 for PostgreSQL, MLflow API ping in services/ml/app/main.py:396-409
  • 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
  • 5.8 [haiku] Add run_id format validation to DELETE /training/runs/{run_id} and GET /training/runs/{run_id} endpoints

6. Infrastructure & Docker

  • 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
  • 6.2 [sonnet] Add USER appuser to services/ml/Dockerfile: create user with useradd, set ownership, add USER directive before CMD
  • 6.3 [haiku] Create .dockerignore with .git, .env, .env*, node_modules, .next, data/, *.md, __pycache__/, mlruns/, models/
  • 6.4 [haiku] Change TA-Lib download URL to HTTPS in services/ml/Dockerfile:10
  • 6.5 [sonnet] Add SHA256 checksum verification for TA-Lib download in services/ml/Dockerfile
  • 6.6 [haiku] Remove COPY --from=builder /app/node_modules ./node_modules line from Dockerfile:29 (standalone doesn't need it)
  • 6.7 [sonnet] Pin Docker base images to @sha256: digests in both Dockerfiles
  • 6.8 [haiku] Fix healthcheck tool mismatch: use same tool (curl) in Dockerfile and docker-compose.yml

7. Frontend — Stale Closures & Race Conditions

  • 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)
  • 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)
  • 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)
  • 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

  • 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)
  • 8.2 [sonnet] Use chart.applyOptions() for theme changes instead of re-creating chart (src/components/CandleChart.tsx:333)
  • 8.3 [sonnet] Remove fitContent() from reconciliation effect, only call on initial load (src/components/SpanAnnotationManager.tsx:160)
  • 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)
  • 8.5 [sonnet] Add bounded prediction cache (max 100 entries, FIFO eviction) to predictionCacheRef (src/app/page.tsx:195-199)
  • 8.6 [sonnet] Fix hardcoded 1-minute candle interval: detect interval from data, use for span iteration (src/components/CandleChart.tsx:452)
  • 8.7 [haiku] Extract magic numbers (8px, 60s, colors) to named constants in CandleChart.tsx
  • 8.8 [haiku] Move new Set<string>() default prop to module-level constant in CandleChart.tsx:125

9. Frontend — Shared Types & Type Safety

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

10. Frontend — Error Boundary & UX

  • 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
  • 10.2 [haiku] Wrap {children} with ErrorBoundary in src/app/layout.tsx
  • 10.3 [sonnet] Add confirmation dialog before delete-all annotations in src/app/page.tsx:412-425 using Radix Dialog
  • 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