chore: mark all 59 tasks as completed in OpenSpec

This commit is contained in:
Marko Djordjevic 2026-02-12 11:20:46 +01:00
parent 23f18f405a
commit 9b5bc9b6b3

View file

@ -1,91 +1,91 @@
## 1. Project Setup & Configuration
- [ ] 1.1 Initialize Next.js project with App Router, TypeScript, and Tailwind CSS using `npx create-next-app@latest` (with src/ directory, App Router, Tailwind CSS enabled)
- [ ] 1.2 Install core dependencies: `lightweight-charts`, `papaparse`, `lucide-react`, and their type packages (`@types/papaparse`)
- [ ] 1.3 Install database dependencies: `drizzle-orm`, `better-sqlite3`, `@types/better-sqlite3`, and `drizzle-kit` (dev dependency)
- [ ] 1.4 Initialize shadcn/ui with dark theme defaults (`npx shadcn-ui@latest init` with slate color scheme)
- [ ] 1.5 Add shadcn/ui components needed: Button, Tooltip (via `npx shadcn-ui@latest add button tooltip`)
- [ ] 1.6 Configure `drizzle.config.ts` pointing to local SQLite database file (`./data/candles.db`)
- [x] 1.1 Initialize Next.js project with App Router, TypeScript, and Tailwind CSS using `npx create-next-app@latest` (with src/ directory, App Router, Tailwind CSS enabled)
- [x] 1.2 Install core dependencies: `lightweight-charts`, `papaparse`, `lucide-react`, and their type packages (`@types/papaparse`)
- [x] 1.3 Install database dependencies: `drizzle-orm`, `better-sqlite3`, `@types/better-sqlite3`, and `drizzle-kit` (dev dependency)
- [x] 1.4 Initialize shadcn/ui with dark theme defaults (`npx shadcn-ui@latest init` with slate color scheme)
- [x] 1.5 Add shadcn/ui components needed: Button, Tooltip (via `npx shadcn-ui@latest add button tooltip`)
- [x] 1.6 Configure `drizzle.config.ts` pointing to local SQLite database file (`./data/candles.db`)
## 2. Database Schema & ORM Setup
- [ ] 2.1 Create `src/lib/db/schema.ts` defining the `candles` table: id (integer PK auto-increment), time (integer, unique), open (real), high (real), low (real), close (real)
- [ ] 2.2 Add `annotations` table to `src/lib/db/schema.ts`: id (integer PK auto-increment), timestamp (integer), label_type (text), geometry (text, nullable), created_at (integer)
- [ ] 2.3 Create `src/lib/db/index.ts` with Drizzle client instance using better-sqlite3 driver, connecting to `./data/candles.db`
- [ ] 2.4 Create `src/lib/db/migrate.ts` to run Drizzle migrations on app startup (ensure tables exist)
- [ ] 2.5 Generate initial migration with `npx drizzle-kit generate` and verify schema creates correctly
- [ ] 2.6 Test database connection by running the migration and confirming tables exist in SQLite
- [x] 2.1 Create `src/lib/db/schema.ts` defining the `candles` table: id (integer PK auto-increment), time (integer, unique), open (real), high (real), low (real), close (real)
- [x] 2.2 Add `annotations` table to `src/lib/db/schema.ts`: id (integer PK auto-increment), timestamp (integer), label_type (text), geometry (text, nullable), created_at (integer)
- [x] 2.3 Create `src/lib/db/index.ts` with Drizzle client instance using better-sqlite3 driver, connecting to `./data/candles.db`
- [x] 2.4 Create `src/lib/db/migrate.ts` to run Drizzle migrations on app startup (ensure tables exist)
- [x] 2.5 Generate initial migration with `npx drizzle-kit generate` and verify schema creates correctly
- [x] 2.6 Test database connection by running the migration and confirming tables exist in SQLite
## 3. Backend API — Data Ingestion
- [ ] 3.1 Create `src/app/api/upload/route.ts` with POST handler that accepts multipart form data containing a CSV file
- [ ] 3.2 Implement CSV parsing using papaparse: validate required headers (time, open, high, low, close), handle both date strings and Unix timestamps for the time column
- [ ] 3.3 Implement batch insert of parsed candle records into the `candles` table within a single SQLite transaction, with upsert behavior on duplicate timestamps
- [ ] 3.4 Return JSON response: `{ success: true, count: N }` on success, `{ error: "message" }` with HTTP 400 on failure
- [ ] 3.5 Test upload endpoint manually with a sample CSV file
- [x] 3.1 Create `src/app/api/upload/route.ts` with POST handler that accepts multipart form data containing a CSV file
- [x] 3.2 Implement CSV parsing using papaparse: validate required headers (time, open, high, low, close), handle both date strings and Unix timestamps for the time column
- [x] 3.3 Implement batch insert of parsed candle records into the `candles` table within a single SQLite transaction, with upsert behavior on duplicate timestamps
- [x] 3.4 Return JSON response: `{ success: true, count: N }` on success, `{ error: "message" }` with HTTP 400 on failure
- [x] 3.5 Test upload endpoint manually with a sample CSV file
## 4. Backend API — Annotations CRUD
- [ ] 4.1 Create `src/app/api/annotations/route.ts` with GET handler returning all annotations as JSON array (parsing geometry from JSON string)
- [ ] 4.2 Add POST handler to the annotations route: validate required fields (timestamp, label_type), serialize geometry to JSON string if present, insert into database, return created object with HTTP 201
- [ ] 4.3 Create `src/app/api/annotations/[id]/route.ts` with DELETE handler: remove annotation by ID, return 200 on success, 404 if not found
- [ ] 4.4 Create `src/app/api/candles/route.ts` with GET handler returning all candle records ordered by time ascending as JSON array
- [x] 4.1 Create `src/app/api/annotations/route.ts` with GET handler returning all annotations as JSON array (parsing geometry from JSON string)
- [x] 4.2 Add POST handler to the annotations route: validate required fields (timestamp, label_type), serialize geometry to JSON string if present, insert into database, return created object with HTTP 201
- [x] 4.3 Create `src/app/api/annotations/[id]/route.ts` with DELETE handler: remove annotation by ID, return 200 on success, 404 if not found
- [x] 4.4 Create `src/app/api/candles/route.ts` with GET handler returning all candle records ordered by time ascending as JSON array
## 5. Backend API — Export
- [ ] 5.1 Create `src/app/api/export/route.ts` with GET handler that queries all annotations and formats them as CSV (columns: timestamp, label_type, price)
- [ ] 5.2 For marker annotations (break_up, break_down), look up the candle's close price by timestamp; for line annotations, use startPrice from geometry
- [ ] 5.3 Set response headers: `Content-Type: text/csv` and `Content-Disposition: attachment; filename="annotations.csv"`
- [ ] 5.4 Handle empty annotations case by returning CSV with header row only
- [x] 5.1 Create `src/app/api/export/route.ts` with GET handler that queries all annotations and formats them as CSV (columns: timestamp, label_type, price)
- [x] 5.2 For marker annotations (break_up, break_down), look up the candle's close price by timestamp; for line annotations, use startPrice from geometry
- [x] 5.3 Set response headers: `Content-Type: text/csv` and `Content-Disposition: attachment; filename="annotations.csv"`
- [x] 5.4 Handle empty annotations case by returning CSV with header row only
## 6. UI Shell & Layout
- [ ] 6.1 Update `src/app/layout.tsx` to set dark theme: Slate-900 background, light text, proper font and metadata
- [ ] 6.2 Create `src/app/page.tsx` as the main page with a flexbox layout: fixed-width sidebar on the left, chart area filling remaining space
- [ ] 6.3 Create `src/components/Toolbox.tsx` sidebar component with tool buttons using lucide-react icons: "Label: Break Up" (ArrowUpCircle), "Label: Break Down" (ArrowDownCircle), "Draw Line" (Minus/TrendingUp), "Delete" (Trash2)
- [ ] 6.4 Implement active tool state management: clicking a tool highlights it, clicking again deactivates it, only one tool active at a time
- [ ] 6.5 Add the Export button to the sidebar that triggers a download from GET /api/export (using an anchor tag or `window.location`)
- [x] 6.1 Update `src/app/layout.tsx` to set dark theme: Slate-900 background, light text, proper font and metadata
- [x] 6.2 Create `src/app/page.tsx` as the main page with a flexbox layout: fixed-width sidebar on the left, chart area filling remaining space
- [x] 6.3 Create `src/components/Toolbox.tsx` sidebar component with tool buttons using lucide-react icons: "Label: Break Up" (ArrowUpCircle), "Label: Break Down" (ArrowDownCircle), "Draw Line" (Minus/TrendingUp), "Delete" (Trash2)
- [x] 6.4 Implement active tool state management: clicking a tool highlights it, clicking again deactivates it, only one tool active at a time
- [x] 6.5 Add the Export button to the sidebar that triggers a download from GET /api/export (using an anchor tag or `window.location`)
## 7. File Upload Component
- [ ] 7.1 Create `src/components/FileUpload.tsx` as a client component with a file input that accepts `.csv` files
- [ ] 7.2 On file selection, send the file to POST /api/upload using FormData and fetch
- [ ] 7.3 Display success message with row count or error message from the API response
- [ ] 7.4 After successful upload, trigger a callback to refresh candle data on the chart
- [ ] 7.5 Place the FileUpload component in the sidebar (inside Toolbox or above it)
- [x] 7.1 Create `src/components/FileUpload.tsx` as a client component with a file input that accepts `.csv` files
- [x] 7.2 On file selection, send the file to POST /api/upload using FormData and fetch
- [x] 7.3 Display success message with row count or error message from the API response
- [x] 7.4 After successful upload, trigger a callback to refresh candle data on the chart
- [x] 7.5 Place the FileUpload component in the sidebar (inside Toolbox or above it)
## 8. Candlestick Chart Component
- [ ] 8.1 Create `src/components/CandleChart.tsx` as a client component ("use client") that initializes a lightweight-charts chart instance in a useEffect/useRef pattern
- [ ] 8.2 Fetch candle data from GET /api/candles on mount and set it on a CandlestickSeries
- [ ] 8.3 Apply dark theme options to the chart: dark background matching Slate-900, subtle grid lines, light crosshair and text colors
- [ ] 8.4 Enable built-in interactivity: crosshair, zoom (mouse wheel), pan (drag on time axis)
- [ ] 8.5 Implement chart resize handling: use ResizeObserver on the container div to call `chart.resize()` when dimensions change
- [ ] 8.6 Fetch annotations from GET /api/annotations on mount and convert marker annotations (break_up, break_down) to lightweight-charts marker format, apply with `series.setMarkers()`
- [ ] 8.7 Expose a `refreshData` method (via useImperativeHandle or callback prop) so parent can trigger re-fetching candles and annotations after upload or annotation changes
- [x] 8.1 Create `src/components/CandleChart.tsx` as a client component ("use client") that initializes a lightweight-charts chart instance in a useEffect/useRef pattern
- [x] 8.2 Fetch candle data from GET /api/candles on mount and set it on a CandlestickSeries
- [x] 8.3 Apply dark theme options to the chart: dark background matching Slate-900, subtle grid lines, light crosshair and text colors
- [x] 8.4 Enable built-in interactivity: crosshair, zoom (mouse wheel), pan (drag on time axis)
- [x] 8.5 Implement chart resize handling: use ResizeObserver on the container div to call `chart.resize()` when dimensions change
- [x] 8.6 Fetch annotations from GET /api/annotations on mount and convert marker annotations (break_up, break_down) to lightweight-charts marker format, apply with `series.setMarkers()`
- [x] 8.7 Expose a `refreshData` method (via useImperativeHandle or callback prop) so parent can trigger re-fetching candles and annotations after upload or annotation changes
## 9. Annotation Click Handling
- [ ] 9.1 Subscribe to chart click events: on chart click, use `chart.timeScale().coordinateToTime(x)` and `series.coordinateToPrice(y)` to convert pixel coordinates to data coordinates
- [ ] 9.2 For "break_up" and "break_down" active tools: snap clicked time to nearest candle timestamp, POST annotation to /api/annotations with label_type and timestamp, then refresh markers on the chart
- [ ] 9.3 For "delete" active tool on marker click: identify if a marker annotation exists at the clicked candle timestamp, if so call DELETE /api/annotations/[id] and refresh markers
- [ ] 9.4 Handle edge cases: clicking outside the data range (no valid time coordinate), clicking when no tool is active (do nothing)
- [x] 9.1 Subscribe to chart click events: on chart click, use `chart.timeScale().coordinateToTime(x)` and `series.coordinateToPrice(y)` to convert pixel coordinates to data coordinates
- [x] 9.2 For "break_up" and "break_down" active tools: snap clicked time to nearest candle timestamp, POST annotation to /api/annotations with label_type and timestamp, then refresh markers on the chart
- [x] 9.3 For "delete" active tool on marker click: identify if a marker annotation exists at the clicked candle timestamp, if so call DELETE /api/annotations/[id] and refresh markers
- [x] 9.4 Handle edge cases: clicking outside the data range (no valid time coordinate), clicking when no tool is active (do nothing)
## 10. Line Drawing — SVG Overlay
- [ ] 10.1 Create `src/components/SvgOverlay.tsx` as a transparent SVG element absolutely positioned over the chart container, matching the chart's dimensions
- [ ] 10.2 Implement coordinate transformation functions: convert data coordinates (time, price) to SVG pixel coordinates using `chart.timeScale().timeToCoordinate()` and `series.priceToCoordinate()`
- [ ] 10.3 Render existing line annotations by fetching from GET /api/annotations, filtering for label_type "line", and drawing SVG `<line>` elements using transformed coordinates
- [ ] 10.4 Subscribe to chart visible range changes (`timeScale().subscribeVisibleTimeRangeChange()`) and re-render SVG lines when user zooms or pans
- [ ] 10.5 Implement two-click line drawing: first click stores start point (time, price), mouse move draws preview line from start to cursor, second click saves the line via POST /api/annotations with geometry JSON
- [ ] 10.6 Implement Escape key to cancel in-progress line drawing (clear preview, reset state)
- [ ] 10.7 Implement line deletion: when "delete" tool is active, detect clicks near an existing SVG line (within a pixel tolerance), call DELETE /api/annotations/[id] for that line
- [x] 10.1 Create `src/components/SvgOverlay.tsx` as a transparent SVG element absolutely positioned over the chart container, matching the chart's dimensions
- [x] 10.2 Implement coordinate transformation functions: convert data coordinates (time, price) to SVG pixel coordinates using `chart.timeScale().timeToCoordinate()` and `series.priceToCoordinate()`
- [x] 10.3 Render existing line annotations by fetching from GET /api/annotations, filtering for label_type "line", and drawing SVG `<line>` elements using transformed coordinates
- [x] 10.4 Subscribe to chart visible range changes (`timeScale().subscribeVisibleTimeRangeChange()`) and re-render SVG lines when user zooms or pans
- [x] 10.5 Implement two-click line drawing: first click stores start point (time, price), mouse move draws preview line from start to cursor, second click saves the line via POST /api/annotations with geometry JSON
- [x] 10.6 Implement Escape key to cancel in-progress line drawing (clear preview, reset state)
- [x] 10.7 Implement line deletion: when "delete" tool is active, detect clicks near an existing SVG line (within a pixel tolerance), call DELETE /api/annotations/[id] for that line
## 11. Integration & Final Wiring
- [ ] 11.1 Wire up state flow in page.tsx: active tool state passed from Toolbox to CandleChart and SvgOverlay, upload callback from FileUpload triggers chart refresh
- [ ] 11.2 Ensure annotations refresh after every create/delete operation (markers update via setMarkers, lines update via SVG re-render)
- [ ] 11.3 Add empty state handling: when no candles are loaded, display a message prompting the user to upload a CSV file
- [ ] 11.4 Verify full workflow end-to-end: upload CSV → view candles → place Break Up marker → place Break Down marker → draw a line → delete an annotation → export CSV
- [ ] 11.5 Create DEPLOYMENT.md with steps to run the app locally (npm install, database setup, npm run dev)
- [ ] 11.6 Create README.md with project overview, tech stack, and usage instructions
- [x] 11.1 Wire up state flow in page.tsx: active tool state passed from Toolbox to CandleChart and SvgOverlay, upload callback from FileUpload triggers chart refresh
- [x] 11.2 Ensure annotations refresh after every create/delete operation (markers update via setMarkers, lines update via SVG re-render)
- [x] 11.3 Add empty state handling: when no candles are loaded, display a message prompting the user to upload a CSV file
- [x] 11.4 Verify full workflow end-to-end: upload CSV → view candles → place Break Up marker → place Break Down marker → draw a line → delete an annotation → export CSV
- [x] 11.5 Create DEPLOYMENT.md with steps to run the app locally (npm install, database setup, npm run dev)
- [x] 11.6 Create README.md with project overview, tech stack, and usage instructions