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
This commit is contained in:
Marko Djordjevic 2026-02-12 10:23:02 +01:00
parent 7d2fc42b73
commit d04b673cfa
25 changed files with 903 additions and 0 deletions

View file

@ -0,0 +1,70 @@
## ADDED Requirements
### Requirement: Active tool mode
The system SHALL maintain an "active tool" state that determines what happens when the user clicks on the chart. Available tool modes are: "select" (default, no action on click), "break_up" (label Break Up), "break_down" (label Break Down), "line" (draw trend line), and "delete" (remove annotation). Only one tool SHALL be active at a time.
#### Scenario: Tool activation
- **WHEN** user clicks a tool button in the sidebar
- **THEN** that tool becomes the active tool and the button appears visually selected
#### Scenario: Tool deactivation
- **WHEN** user clicks the already-active tool button
- **THEN** the tool deactivates and the mode returns to "select"
### Requirement: Break Up labeling
When the "break_up" tool is active and the user clicks on the chart, the system SHALL identify the nearest candle to the click coordinates using `chart.timeScale().coordinateToTime()`. The system SHALL save an annotation with `label_type: "break_up"` and the candle's timestamp to the database. A green upward arrow marker SHALL appear on the chart immediately.
#### Scenario: Place Break Up label
- **WHEN** "break_up" tool is active and user clicks on a candle
- **THEN** system saves a "break_up" annotation for that candle's timestamp and displays a green arrow marker above the bar
#### Scenario: Click between candles
- **WHEN** "break_up" tool is active and user clicks between two candles
- **THEN** system snaps to the nearest candle timestamp and places the annotation there
### Requirement: Break Down labeling
When the "break_down" tool is active and the user clicks on the chart, the system SHALL behave identically to Break Up labeling but save `label_type: "break_down"` and display a red downward arrow below the bar.
#### Scenario: Place Break Down label
- **WHEN** "break_down" tool is active and user clicks on a candle
- **THEN** system saves a "break_down" annotation for that candle's timestamp and displays a red arrow marker below the bar
### Requirement: Two-click line drawing
When the "line" tool is active, the system SHALL implement a two-click drawing interaction. The first click sets the start point (time, price). The second click sets the end point (time, price). After the second click, the system SHALL save an annotation with `label_type: "line"` and `geometry` containing JSON: `{"startTime": <unix>, "startPrice": <float>, "endTime": <unix>, "endPrice": <float>}`. The line SHALL render immediately on the SVG overlay.
#### Scenario: Draw a trend line
- **WHEN** "line" tool is active and user clicks two points on the chart
- **THEN** system saves a line annotation with start/end coordinates and renders the line on the overlay
#### Scenario: Visual feedback during line drawing
- **WHEN** "line" tool is active and user has clicked the first point but not the second
- **THEN** system displays a preview line from the first point to the current cursor position
#### Scenario: Cancel line drawing
- **WHEN** user presses Escape during a two-click line drawing (after first click)
- **THEN** system cancels the line drawing and clears the preview without saving
### Requirement: Delete annotation
When the "delete" tool is active and the user clicks on or near an existing annotation (marker or line), the system SHALL remove that annotation from the database and update the chart display immediately.
#### Scenario: Delete a marker annotation
- **WHEN** "delete" tool is active and user clicks on a candle that has a marker annotation
- **THEN** system removes the annotation from the database and the marker disappears from the chart
#### Scenario: Delete a line annotation
- **WHEN** "delete" tool is active and user clicks near an existing line on the overlay
- **THEN** system removes the line annotation from the database and the line disappears from the overlay
### Requirement: Coordinate mapping
The system SHALL convert mouse click pixel coordinates to chart data coordinates (time and price) using the lightweight-charts API: `chart.timeScale().coordinateToTime(x)` for time and `series.coordinateToPrice(y)` for price. For point annotations, the time SHALL be snapped to the nearest candle timestamp.
#### Scenario: Pixel to data coordinate conversion
- **WHEN** user clicks at pixel position (x, y) on the chart
- **THEN** system correctly converts to the corresponding time and price values using the chart API
### Requirement: Annotations database table
The system SHALL store annotations in an `annotations` table with columns: `id` (integer primary key, auto-increment), `timestamp` (integer, Unix timestamp referencing a candle time), `label_type` (text: "break_up", "break_down", or "line"), `geometry` (text, nullable, JSON string for line coordinates), `created_at` (integer, Unix timestamp of creation).
#### Scenario: Schema structure
- **WHEN** the database is initialized
- **THEN** the `annotations` table exists with all required columns

View file

@ -0,0 +1,75 @@
## ADDED Requirements
### Requirement: Upload endpoint
The system SHALL provide a `POST /api/upload` endpoint that accepts a CSV file via multipart form data. The endpoint SHALL parse the CSV using papaparse, validate the format, and insert all candle records into the `candles` table within a single database transaction. On success, the endpoint SHALL return a JSON response with the count of inserted records. On failure, it SHALL return an appropriate error status and message.
#### Scenario: Successful upload
- **WHEN** a valid CSV file is sent to POST /api/upload
- **THEN** endpoint returns `{ "success": true, "count": <number> }` with HTTP 200
#### Scenario: Invalid CSV upload
- **WHEN** a CSV with missing or invalid headers is sent to POST /api/upload
- **THEN** endpoint returns `{ "error": "<description>" }` with HTTP 400
#### Scenario: No file provided
- **WHEN** POST /api/upload is called without a file
- **THEN** endpoint returns `{ "error": "No file provided" }` with HTTP 400
### Requirement: Get annotations endpoint
The system SHALL provide a `GET /api/annotations` endpoint that returns all annotations from the database as a JSON array. Each annotation object SHALL include: `id`, `timestamp`, `label_type`, `geometry` (parsed from JSON string or null), and `created_at`.
#### Scenario: Fetch all annotations
- **WHEN** GET /api/annotations is called
- **THEN** endpoint returns a JSON array of all annotation objects with HTTP 200
#### Scenario: No annotations exist
- **WHEN** GET /api/annotations is called and no annotations are in the database
- **THEN** endpoint returns an empty JSON array `[]` with HTTP 200
### Requirement: Create annotation endpoint
The system SHALL provide a `POST /api/annotations` endpoint that accepts a JSON body with fields: `timestamp` (required, integer), `label_type` (required, string), and `geometry` (optional, object). The endpoint SHALL validate the input, serialize geometry to JSON string if present, and insert the record into the `annotations` table. On success, it SHALL return the created annotation object with its assigned `id`.
#### Scenario: Create a marker annotation
- **WHEN** POST /api/annotations is called with `{ "timestamp": 1700000000, "label_type": "break_up" }`
- **THEN** endpoint saves the annotation and returns the created object with `id` and HTTP 201
#### Scenario: Create a line annotation
- **WHEN** POST /api/annotations is called with `{ "timestamp": 1700000000, "label_type": "line", "geometry": { "startTime": 1700000000, "startPrice": 1.05, "endTime": 1700100000, "endPrice": 1.06 } }`
- **THEN** endpoint saves the annotation with serialized geometry JSON and returns the created object with HTTP 201
#### Scenario: Invalid annotation data
- **WHEN** POST /api/annotations is called with missing required fields
- **THEN** endpoint returns `{ "error": "<description>" }` with HTTP 400
### Requirement: Delete annotation endpoint
The system SHALL provide a `DELETE /api/annotations/[id]` endpoint that removes an annotation by its ID. On success, it SHALL return HTTP 200. If the annotation does not exist, it SHALL return HTTP 404.
#### Scenario: Delete existing annotation
- **WHEN** DELETE /api/annotations/5 is called and annotation with id 5 exists
- **THEN** endpoint removes the annotation and returns HTTP 200
#### Scenario: Delete non-existent annotation
- **WHEN** DELETE /api/annotations/999 is called and no annotation with that id exists
- **THEN** endpoint returns HTTP 404
### Requirement: Export annotations endpoint
The system SHALL provide a `GET /api/export` endpoint that returns all annotations as a downloadable CSV file. The CSV SHALL have columns: `timestamp`, `label_type`, `price` (extracted from geometry for lines, or the candle's close price for markers). The response SHALL set `Content-Type: text/csv` and `Content-Disposition: attachment; filename="annotations.csv"` headers.
#### Scenario: Export as CSV
- **WHEN** GET /api/export is called and annotations exist
- **THEN** endpoint returns a CSV file download with all annotations
#### Scenario: Export with no data
- **WHEN** GET /api/export is called and no annotations exist
- **THEN** endpoint returns a CSV file with only the header row
### Requirement: Get candles endpoint
The system SHALL provide a `GET /api/candles` endpoint that returns all candle records from the database as a JSON array, ordered by time ascending. Each object SHALL include: `time`, `open`, `high`, `low`, `close`.
#### Scenario: Fetch all candles
- **WHEN** GET /api/candles is called
- **THEN** endpoint returns a JSON array of candle objects ordered by time ascending with HTTP 200
#### Scenario: No candles exist
- **WHEN** GET /api/candles is called and no candle data is in the database
- **THEN** endpoint returns an empty JSON array `[]` with HTTP 200

View file

@ -0,0 +1,52 @@
## ADDED Requirements
### Requirement: Candlestick chart rendering
The system SHALL render candle data as a candlestick chart using the `lightweight-charts` library (v4). The chart MUST display OHLC data with standard candlestick visuals (green for bullish, red for bearish). The chart SHALL be a client-side React component.
#### Scenario: Chart renders candle data
- **WHEN** candle data exists in the database and the page loads
- **THEN** the chart renders all candles as a candlestick series with correct open/high/low/close values
#### Scenario: Empty state
- **WHEN** no candle data exists in the database
- **THEN** the chart area displays an empty chart with a prompt to upload CSV data
### Requirement: Chart interactivity
The chart SHALL support zooming (mouse wheel), panning (click and drag on time axis), and crosshair display (showing price/time on hover). These are built-in lightweight-charts behaviors that MUST be enabled.
#### Scenario: Zoom and pan
- **WHEN** user scrolls the mouse wheel over the chart
- **THEN** the chart zooms in or out on the time axis
#### Scenario: Crosshair display
- **WHEN** user hovers the mouse over the chart
- **THEN** a crosshair displays with the current price and time at the cursor position
### Requirement: Responsive chart layout
The chart MUST fill the available width of the main content area (excluding the sidebar). The chart height SHALL be responsive, using the full viewport height minus header/toolbar areas. The chart MUST resize when the browser window is resized.
#### Scenario: Window resize
- **WHEN** user resizes the browser window
- **THEN** the chart resizes to fill the available space without requiring a page reload
### Requirement: Dark theme chart
The chart SHALL use a dark color scheme consistent with the application's Slate-900 dark theme. Background MUST be dark, grid lines subtle, and text/crosshair in light colors.
#### Scenario: Dark theme applied
- **WHEN** the chart renders
- **THEN** the chart background, grid, text, and crosshair colors match the dark theme (dark background, light text)
### Requirement: Annotation markers on chart
The chart SHALL display visual markers for existing annotations using the `series.setMarkers()` API. Break Up annotations MUST appear as green upward arrows above the bar. Break Down annotations MUST appear as red downward arrows below the bar. Markers MUST update when annotations are added or deleted.
#### Scenario: Break Up marker display
- **WHEN** a Break Up annotation exists for a candle timestamp
- **THEN** a green upward arrow marker appears above that candle on the chart
#### Scenario: Break Down marker display
- **WHEN** a Break Down annotation exists for a candle timestamp
- **THEN** a red downward arrow marker appears below that candle on the chart
#### Scenario: Marker updates on annotation change
- **WHEN** user adds or deletes an annotation
- **THEN** chart markers update immediately without requiring a page reload

View file

@ -0,0 +1,42 @@
## ADDED Requirements
### Requirement: CSV file upload
The system SHALL provide a file upload component that accepts CSV files containing OHLC candle data. The CSV format MUST have columns: `time`, `open`, `high`, `low`, `close`. The `time` column SHALL accept both `YYYY-MM-DD` date strings and Unix timestamps (integer seconds).
#### Scenario: Valid CSV upload
- **WHEN** user uploads a CSV file with valid headers (time, open, high, low, close) and valid data rows
- **THEN** system parses all rows and stores them in the `candles` database table
#### Scenario: CSV with Unix timestamps
- **WHEN** user uploads a CSV where the `time` column contains Unix timestamps (e.g., 1700000000)
- **THEN** system stores the timestamps as integers in the database and renders candles correctly on the chart
#### Scenario: CSV with date strings
- **WHEN** user uploads a CSV where the `time` column contains date strings (e.g., "2024-01-15")
- **THEN** system converts dates to Unix timestamps and stores them in the database
#### Scenario: Invalid CSV format
- **WHEN** user uploads a CSV missing required headers or containing malformed data
- **THEN** system displays an error message describing the issue and does not store any partial data
#### Scenario: Duplicate upload
- **WHEN** user uploads a CSV containing candle times that already exist in the database
- **THEN** system replaces existing candle records with the new data (upsert behavior)
### Requirement: CSV parsing with papaparse
The system SHALL use the `papaparse` library for CSV parsing. Parsing SHALL handle large files by using streaming mode for files exceeding 10,000 rows. Parsed records SHALL be inserted into SQLite within a single database transaction for atomicity.
#### Scenario: Large file parsing
- **WHEN** user uploads a CSV with more than 10,000 rows
- **THEN** system uses streaming parse and batch inserts within a transaction, completing without memory issues
#### Scenario: Transaction atomicity
- **WHEN** a parse error occurs midway through a CSV file
- **THEN** system rolls back the entire transaction and no partial data is stored
### Requirement: Candles database table
The system SHALL store candle data in a `candles` table with columns: `id` (integer primary key, auto-increment), `time` (integer, Unix timestamp, unique), `open` (real), `high` (real), `low` (real), `close` (real). The `time` column MUST have a unique constraint.
#### Scenario: Schema structure
- **WHEN** the database is initialized
- **THEN** the `candles` table exists with all required columns and constraints

View file

@ -0,0 +1,44 @@
## ADDED Requirements
### Requirement: Dark mode layout
The application SHALL use a dark theme based on Tailwind's Slate-900 color palette. The root layout MUST set a dark background. All UI components (sidebar, buttons, text) SHALL use colors consistent with the dark theme.
#### Scenario: Dark theme renders
- **WHEN** the application loads
- **THEN** the page background is Slate-900 (dark), text is light, and all UI elements follow the dark color scheme
### Requirement: Sidebar toolbox
The application SHALL display a fixed sidebar on the left side of the viewport. The sidebar SHALL contain tool buttons: "Label: Break Up", "Label: Break Down", "Draw Line", "Delete". Each button SHALL use a lucide-react icon and a text label. The currently active tool button SHALL be visually highlighted.
#### Scenario: Sidebar renders with tools
- **WHEN** the application loads
- **THEN** the sidebar displays all four tool buttons with icons and labels
#### Scenario: Active tool highlight
- **WHEN** user selects a tool
- **THEN** that tool's button is visually highlighted (distinct background/border color) and other buttons return to default state
### Requirement: Main chart area
The main content area SHALL occupy the full viewport width minus the sidebar width. The chart component MUST fill this area. The layout SHALL use CSS flexbox or grid to ensure the sidebar and chart area are side by side.
#### Scenario: Layout structure
- **WHEN** the application loads with candle data
- **THEN** the sidebar appears on the left and the chart fills the remaining space
### Requirement: File upload interface
The application SHALL provide a CSV file upload interface accessible from the UI. This MAY be a button in the sidebar or a dropzone overlay. The upload component SHALL trigger the POST /api/upload endpoint and refresh the chart data on success.
#### Scenario: Upload via UI
- **WHEN** user selects a CSV file through the upload interface
- **THEN** the file is sent to the upload API and the chart refreshes with the new data on success
#### Scenario: Upload error display
- **WHEN** the upload API returns an error
- **THEN** the UI displays the error message to the user
### Requirement: Export button
The application SHALL provide an "Export" button that triggers a download of the annotations CSV. Clicking the button SHALL navigate to or fetch from the `GET /api/export` endpoint, resulting in a CSV file download.
#### Scenario: Export annotations
- **WHEN** user clicks the "Export" button
- **THEN** a CSV file named "annotations.csv" downloads containing all annotation data