candle-annotator/openspec/changes/candle-annotator-app/design.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

6.8 KiB

Context

This is a greenfield Next.js application for annotating EUR/USD candlestick charts. A trader uploads historical OHLC data as CSV, views it on an interactive chart, labels candles with pattern types (Break Up, Break Down), draws trend lines, and exports annotations for ML training pipelines. There is no existing codebase — the project starts from scratch.

The primary user is a single trader/researcher working locally. The app runs on localhost with a local SQLite database. There is no authentication, multi-user, or deployment requirement at this stage.

Goals / Non-Goals

Goals:

  • Provide a responsive candlestick chart with TradingView-like feel using lightweight-charts
  • Enable point annotations (Break Up/Down markers) via click-on-candle interaction
  • Enable two-click line drawing (trend lines) with persistent storage
  • Store all candle data and annotations in local SQLite via Drizzle ORM
  • Support CSV import of OHLC data and CSV export of annotations
  • Dark mode UI with a sidebar toolbox

Non-Goals:

  • Real-time data feeds or live trading integration
  • Multi-user collaboration or authentication
  • Cloud deployment or remote database
  • Complex drawing tools beyond basic two-point lines
  • Undo/redo system (v1 uses delete instead)
  • Mobile-optimized layout

Decisions

1. Next.js App Router with Server-Side SQLite

Choice: Next.js App Router with Route Handlers for API, SQLite accessed server-side only.

Rationale: App Router provides a clean separation between client components (chart, toolbox) and server-side API routes that access SQLite. SQLite + better-sqlite3 is synchronous and fast for a single-user local app — no need for a separate database server.

Alternatives considered:

  • Pages Router: Older pattern, App Router is the current Next.js standard
  • PostgreSQL: Overkill for local single-user; requires a running server
  • API-less architecture (server components only): Doesn't work well since the chart is a heavy client component that needs to fetch data dynamically

2. Lightweight Charts for Charting

Choice: lightweight-charts v4 by TradingView.

Rationale: Purpose-built for financial candlestick charts. Has built-in setMarkers() API for annotation display. Performant with large datasets. Free and open source.

Alternatives considered:

  • D3.js: Too low-level; would require building candlestick rendering from scratch
  • Chart.js: Not designed for financial charts; no candlestick support without plugins
  • Full TradingView widget: Requires API key and has usage restrictions

3. Annotation Overlay Architecture

Choice: Use lightweight-charts native setMarkers() for point annotations. For line drawings, use an absolutely-positioned transparent SVG overlay on top of the chart container that transforms coordinates using the chart's timeScale() and priceScale() APIs.

Rationale: setMarkers() handles Break Up/Down arrows natively with proper zoom/scroll behavior. Lines require custom rendering since lightweight-charts doesn't have a built-in line drawing tool. An SVG overlay keeps drawing logic separate from chart internals while allowing coordinate synchronization.

Alternatives considered:

  • Canvas overlay: More complex coordinate handling; SVG is simpler for lines
  • lightweight-charts plugins API: Still experimental and poorly documented
  • Price lines only: Limited to horizontal lines; can't draw diagonal trend lines

4. Coordinate Mapping for Click Interaction

Choice: On chart click, use chart.timeScale().coordinateToTime(x) and series.coordinateToPrice(y) to map pixel coordinates to candle time/price. Snap to the nearest candle timestamp for point annotations.

Rationale: This is the documented approach from lightweight-charts. Snapping to candle timestamps ensures annotations align with actual data points rather than arbitrary positions.

5. Drizzle ORM with better-sqlite3

Choice: Drizzle ORM with better-sqlite3 driver.

Rationale: Type-safe schema definitions, lightweight migration system, synchronous SQLite access. Drizzle's schema-as-code approach keeps the database definition in TypeScript alongside the app code.

Alternatives considered:

  • Prisma: Heavier, async-only, overkill for SQLite
  • Raw better-sqlite3: No type safety, manual query building
  • Turso/libsql: Cloud-oriented; unnecessary for local use

6. UI Component Strategy

Choice: Tailwind CSS + shadcn/ui for UI components, lucide-react for icons.

Rationale: shadcn/ui provides accessible, composable components that are copied into the project (not a runtime dependency). Dark mode is straightforward with Tailwind's dark: variants. lucide-react provides the icon set needed for toolbox buttons.

7. Database Schema

Two tables:

  • candles: id (integer PK), time (integer, unix timestamp), open (real), high (real), low (real), close (real). Unique constraint on time.
  • annotations: id (integer PK), timestamp (integer, references candle time), label_type (text: "break_up", "break_down", "line"), geometry (text, nullable JSON for line coordinates: {startTime, startPrice, endTime, endPrice}), created_at (integer, unix timestamp).

8. File Structure

src/
  app/
    layout.tsx          — Root layout with dark theme
    page.tsx            — Main page composing chart + toolbox
    api/
      upload/route.ts   — POST: parse CSV, store candles
      annotations/route.ts — GET: fetch annotations, POST: create annotation
      export/route.ts   — GET: download annotations as CSV
  components/
    CandleChart.tsx     — Chart wrapper (client component)
    Toolbox.tsx         — Sidebar with tool buttons
    FileUpload.tsx      — CSV upload dropzone
    SvgOverlay.tsx      — Line drawing overlay
  lib/
    db/
      index.ts          — Drizzle client instance
      schema.ts         — Table definitions
      migrate.ts        — Migration runner

Risks / Trade-offs

  • [SVG overlay sync] The SVG overlay for lines must stay in sync with chart zoom/scroll state. → Mitigation: Subscribe to timeScale().subscribeVisibleTimeRangeChange() and re-render overlay coordinates on every change.
  • [Large CSV files] Very large CSV files (100k+ rows) may cause slow uploads. → Mitigation: Use streaming parse with papaparse and batch inserts in SQLite transactions.
  • [Single-user SQLite] SQLite doesn't support concurrent writes. → Mitigation: Acceptable for a single-user local tool. Not a concern unless the app is deployed for multiple users (explicitly a non-goal).
  • [No undo] Users can only delete annotations, not undo placement. → Mitigation: Delete tool is sufficient for v1. Undo/redo can be added later if needed.
  • [lightweight-charts v4 breaking changes] The library has had breaking API changes between major versions. → Mitigation: Pin to a specific v4.x version in package.json.