Replace locally-defined duplicate interfaces in page.tsx,
CandleChart.tsx, SpanAnnotationManager.tsx, Toolbox.tsx,
SpanAnnotationList.tsx, and SpanPopover.tsx with imports from @/types.
- SpanAnnotation, SpanLabelType: replaced in all 6 files
- Candle, AnnotationType: replaced in CandleChart.tsx, SpanAnnotationManager.tsx, Toolbox.tsx
- Annotation (with geometry): replaced in CandleChart.tsx and Toolbox.tsx
- Chart: kept local in page.tsx (shared type has created_at: Date|number vs local number)
- Annotation in page.tsx: kept local (geometry: any) but added missing color field for compatibility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add SpanAnnotation, SpanLabelType, and SubSpan interfaces extracted from
duplicate inline definitions across SpanAnnotationManager, SpanAnnotationList,
SpanPopover, Toolbox, and page.tsx components.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
src/types/predictions.ts already existed with PredictionSpan, PredictionState, and ModelInfoResponse interfaces matching actual usage across page.tsx, CandleChart.tsx, and PredictionPanel.tsx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a shared Chart interface matching the DB schema (id, name, created_at)
and the existing usages in page.tsx and ChartSelector.tsx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates src/types/annotations.ts with typed interfaces matching actual
usage in CandleChart.tsx, page.tsx, and DB schema. Marks task 9.2 done.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Move the inline 'new Set<string>()' default prop to a module-level constant
(EMPTY_STRING_SET) to prevent unnecessary Set instance creation on every render,
which could cause unnecessary re-renders in children or effects that depend on
reference equality.
Replace inline magic numbers (8px, 60s, 2, 1, 0.15, 0.25, 100) and hardcoded
color strings with named module-level constants for better maintainability and
clarity. Organized constants into logical groups:
- Magic number constants (tolerance, intervals, sizes, alphas)
- Dark theme colors
- Light theme colors
- Candlestick colors
- Prediction overlay colors
- Predefined label colors
This improves code readability and makes it easier to adjust UI parameters globally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the hardcoded 60-second assumption in CandleChart.tsx with
dynamic interval detection: compute interval as candles[1].time -
candles[0].time when at least 2 candles are available, fall back to
60 otherwise. Used for span-to-prediction-time mapping iteration.
Marks task 8.6 as done in tasks.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Before inserting into predictionCacheRef, check if size >= 100
- If so, evict oldest entry via cache.keys().next().value (FIFO)
- Applied at both cache write sites (lines ~549 and ~671 in page.tsx)
- Marks task 8.5 as done in tasks.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Separate the single reconciliation effect into:
1. Full reconciliation effect — runs only when spanAnnotations list,
series, chart, or candles change. Rebuilds all primitives.
2. Selection-only effect — runs only when selectedSpanId changes.
Calls setSelected() on existing primitives instead of detaching
and reattaching all of them.
This avoids tearing down and rebuilding every primitive on each
selection change, which was the main unnecessary overhead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move chart.timeScale().fitContent() out of the span annotation reconciliation
effect (which ran on every annotation/selection change) into a dedicated effect
guarded by a hasInitializedRef, so it fires only once when chart and series
first become available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove resolvedTheme from the chart initialization useEffect dependency
array so theme changes no longer destroy and re-create the entire chart.
Add a separate useEffect that calls chartRef.current.applyOptions() with
the new layout, grid, timeScale and rightPriceScale colors whenever
resolvedTheme changes, avoiding the memory leak and flicker caused by
full chart reconstruction on every theme toggle.
Closes task 8.2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The preview primitive in SpanAnnotationManager was stored in useState,
causing unnecessary re-renders and potential memory leaks since the
primitive was not cleaned up on unmount. Changed to useRef, updated all
read/write sites, removed from dependency arrays, and added unmount
cleanup effect that detaches the primitive from the series.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use selectedSpanIdRef to avoid capturing stale selectedSpanId in the
keyboard event handler closure. Wrap handleDeleteSpan in useCallback
with stable dependencies (reads selectedSpanIdRef.current instead of
the prop directly). Remove selectedSpanId from the useEffect dependency
array since the ref is used instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert drawingState, selectedLineId, dragState, and annotations to
refs that stay in sync with state via useEffect. The chart click handler
(subscribeClick) captured stale closure values for these variables.
Now reads from refs (e.g. drawingStateRef.current) so the handler
always sees the latest state, and removed the stale state variables
from the useEffect dependency array.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevent race conditions by aborting in-flight requests when a new
request is triggered. Each function now:
- Aborts the previous request via a stored AbortController ref
- Passes signal to all fetch() calls
- Silently discards AbortError in catch blocks
Completes task 7.2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The modelInfoRef with useEffect sync was already in place in page.tsx
(lines 201-205), and fetchPredictions/generateCacheKey already use
modelInfoRef.current instead of the stale closure value.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace wget with curl in root Dockerfile healthcheck command (line 50)
- Add curl to apk dependencies in root Dockerfile (line 22)
- Align healthcheck parameters between Dockerfile and docker-compose.yml
- Mark task 6.8 as complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add TODO comments above each FROM instruction in Dockerfile and
services/ml/Dockerfile instructing how to pin base images to sha256
digests for reproducible builds. Marks task 6.7 as complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Standalone Next.js output includes all required dependencies in the
/app/.next/standalone directory, making the separate node_modules
copy unnecessary. This reduces image size and build complexity.
Closes task 6.6.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Splits the monolithic TA-Lib build RUN command to insert an ARG for the
expected SHA256 hash and a sha256sum -c verification step immediately after
the wget download, before extraction and build. Marks task 6.5 complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updated the wget command in services/ml/Dockerfile line 10 to use HTTPS instead of HTTP for downloading TA-Lib source. This improves security by ensuring encrypted transport.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create system user appuser with useradd, set ownership of /app,
and switch to non-root user before CMD to reduce container attack surface.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add async headers() function with X-Frame-Options, X-Content-Type-Options,
Referrer-Policy, Permissions-Policy, and Content-Security-Policy headers
applied to all routes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add new GET endpoint for retrieving a specific training run by run_id
- Validate run_id format with regex pattern ^[a-zA-Z0-9_-]+$ before DB access
- Return HTTP 400 for invalid run_id format, HTTP 404 for non-existent runs
- Ensure DELETE endpoint validation is correctly placed before any DB access
- Both endpoints now provide consistent security validation
- Mark task 5.8 as completed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Import concurrent.futures for timeout support
- In _run_training_background: check df.memory_usage(deep=True).sum()
after loading the labeled dataset; raise ValueError if > 500MB
- Wrap model.fit() in a ThreadPoolExecutor with a 1800s timeout;
on TimeoutError update DB status to "failed" with message
"Training timed out after 30 minutes" and return early
- Mark task 5.7 as done in openspec/changes/code-review-fix/tasks.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>