candle-annotator/openspec/changes/candle-annotator-app/tasks.md
Marko Djordjevic d04b673cfa feat: initialize Next.js project with database schema
- Set up Next.js with App Router, TypeScript, Tailwind CSS
- Configure shadcn/ui with dark theme
- Install dependencies: lightweight-charts, papaparse, lucide-react
- Set up Drizzle ORM with better-sqlite3
- Create database schema for candles and annotations tables
- Generate migration SQL
2026-02-12 10:23:02 +01:00

8.4 KiB

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)

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

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

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

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

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)

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)

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

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)

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

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