chore: archive both OpenSpec changes and sync specs to main
This commit is contained in:
parent
a6e763c153
commit
50229e2ccf
25 changed files with 927 additions and 1 deletions
|
|
@ -0,0 +1,2 @@
|
|||
schema: spec-driven
|
||||
created: 2026-02-12
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
## 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.
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
## Why
|
||||
|
||||
Building a custom machine-learning model for trading requires labeled training data. Currently there is no tool to manually annotate EUR/USD candlestick charts with pattern labels (break up, break down, trend lines) and export those annotations as structured data. This app provides a TradingView-like charting interface with an interactive labeling layer, enabling a trader to visually mark patterns on historical candle data and export the results for ML pipelines.
|
||||
|
||||
## What Changes
|
||||
|
||||
- New full-stack Next.js application (App Router, TypeScript, Tailwind CSS)
|
||||
- CSV upload for OHLC candle data (time, open, high, low, close) with parsing and SQLite persistence
|
||||
- Interactive candlestick chart powered by `lightweight-charts`
|
||||
- Annotation toolbox: point labels (Break Up, Break Down) and two-click line drawing
|
||||
- Visual markers on chart for existing annotations (arrows, lines)
|
||||
- Backend API for data ingestion, annotation CRUD, and CSV export
|
||||
- Dark mode UI with sidebar toolbox and responsive chart area
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `data-ingestion`: CSV file upload, parsing with papaparse, and storage of OHLC candle records in SQLite via Drizzle ORM
|
||||
- `chart-canvas`: Candlestick chart rendering using lightweight-charts with responsive layout and dark theme
|
||||
- `annotation-tools`: Interactive labeling (Break Up, Break Down markers) and two-click line drawing with coordinate mapping between screen and price/time
|
||||
- `backend-api`: REST endpoints for CSV upload (POST /api/upload), annotation read/write (GET/POST /api/annotations), and annotation export as CSV
|
||||
- `ui-shell`: Dark mode layout with sidebar toolbox, main chart area, and export button
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
(none — greenfield project)
|
||||
|
||||
## Impact
|
||||
|
||||
- **New dependencies**: next, react, typescript, tailwindcss, lightweight-charts, lucide-react, papaparse, drizzle-orm, better-sqlite3, shadcn-ui
|
||||
- **New database**: Local SQLite file with `candles` and `annotations` tables
|
||||
- **New API surface**: Three REST endpoints under /api/
|
||||
- **File structure**: /components, /lib/db, /app/api modular layout
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
## 1. Project Setup & Configuration
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
||||
- [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
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
schema: spec-driven
|
||||
created: 2026-02-12
|
||||
|
|
@ -0,0 +1,439 @@
|
|||
# Design: Annotation Features and Deployment Enhancement
|
||||
|
||||
## Context
|
||||
|
||||
### Current State
|
||||
The candle annotator has a working annotation system with:
|
||||
- Line drawing with color selection, visual feedback, selection, and endpoint dragging (Phase 1-4 complete)
|
||||
- Label markers (break_up/break_down) added by clicking candles
|
||||
- Delete tool that removes annotations by clicking near them
|
||||
- SQLite database storing all annotations with geometry and color
|
||||
- Next.js 16 app with lightweight-charts for visualization
|
||||
- shadcn/ui components with dark slate theme
|
||||
- Local development workflow only
|
||||
|
||||
### Constraints
|
||||
- **No breaking changes**: Existing functionality must remain intact
|
||||
- **No new dependencies**: Use existing Next.js, React, Tailwind, shadcn/ui stack
|
||||
- **Single-user application**: No authentication or multi-user considerations
|
||||
- **SQLite database**: Must maintain compatibility with existing schema
|
||||
- **Lightweight-charts integration**: Chart library API is fixed, work within SVG overlay pattern
|
||||
- **Performance**: Chart rendering must maintain 60fps during annotation interactions
|
||||
|
||||
### Stakeholders
|
||||
- **Primary user**: Marko (developer/trader creating training data)
|
||||
- **Deployment target**: Server environment with Docker support
|
||||
- **Implementation**: Haiku model must be able to follow the implementation plan
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
1. Enable efficient bulk annotation management (delete all labels, delete all lines)
|
||||
2. Provide visibility into all annotations via sidebar list with search/filter
|
||||
3. Enable one-click production deployment via Docker
|
||||
4. Transform UI to distinctive hacker/terminal aesthetic while maintaining usability
|
||||
5. Create implementation plan detailed enough for Haiku model to execute
|
||||
|
||||
**Non-Goals:**
|
||||
1. Multi-user support or authentication
|
||||
2. Undo/redo functionality (can be added later)
|
||||
3. Import/export of individual annotations (CSV export already exists)
|
||||
4. Real-time collaboration features
|
||||
5. Mobile responsive design (desktop-focused tool)
|
||||
6. Internationalization (English only)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: Label Selection Architecture
|
||||
|
||||
**Choice**: Use separate state management for label selection parallel to existing line selection
|
||||
|
||||
**Why?**
|
||||
- Lines and labels are fundamentally different (geometry vs point markers)
|
||||
- Existing line selection in `SvgOverlay.tsx` works well, don't disturb it
|
||||
- Labels rendered by `CandleChart.tsx` using lightweight-charts markers API
|
||||
- Parallel state (`selectedLabelId` alongside `selectedLineId`) keeps concerns separated
|
||||
|
||||
**Alternatives Considered:**
|
||||
- **Unified selection system**: Single `selectedAnnotationId` for both lines and labels
|
||||
- ❌ Rejected: Would require refactoring working line selection code, higher risk
|
||||
- ❌ Different selection behaviors (lines: click geometry, labels: click marker)
|
||||
- **Event bus pattern**: Pub/sub for selection events
|
||||
- ❌ Rejected: Over-engineering for single-user app, adds complexity
|
||||
|
||||
**Implementation:**
|
||||
- Add `selectedLabelId` state in `page.tsx`
|
||||
- Pass `selectedLabelId` and `setSelectedLabelId` to `CandleChart.tsx`
|
||||
- Make markers clickable via `onClick` handler in marker creation
|
||||
- Highlight selected marker with custom scale and color properties
|
||||
- Deselect on Escape key (global keyboard handler in page component)
|
||||
|
||||
### Decision 2: Label List UI Structure
|
||||
|
||||
**Choice**: Collapsible section within existing Toolbox sidebar with virtualized scrolling
|
||||
|
||||
**Why?**
|
||||
- Keeps all controls in one place (consistency)
|
||||
- Toolbox already has vertical space for expansion
|
||||
- Users may have hundreds/thousands of labels, need efficient rendering
|
||||
- No need for new layout areas or floating panels
|
||||
|
||||
**Alternatives Considered:**
|
||||
- **Separate right sidebar**: New panel on right side of chart
|
||||
- ❌ Rejected: Takes screen real estate from chart, awkward horizontal layout
|
||||
- **Modal/overlay**: Floating panel over chart
|
||||
- ❌ Rejected: Blocks chart view, poor UX for browsing annotations while viewing data
|
||||
- **Bottom panel**: Horizontal list below chart
|
||||
- ❌ Rejected: Vertical list more natural for timestamped items, less vertical space for chart
|
||||
|
||||
**Implementation:**
|
||||
- Add `<Collapsible>` section in Toolbox below tool buttons, above Export
|
||||
- Section header: "Label Annotations (X)" with expand/collapse icon
|
||||
- When expanded: search input, filter dropdown, scrollable list (max-height: 400px)
|
||||
- Each list item: formatted timestamp, colored badge, delete button
|
||||
- Click item to select/highlight on chart
|
||||
- Use CSS `overflow-y: auto` with custom scrollbar styling (no virtualization needed initially)
|
||||
- If performance issues with 1000+ items, add `react-window` in future iteration
|
||||
|
||||
### Decision 3: API Design for Bulk Operations
|
||||
|
||||
**Choice**: Extend existing DELETE `/api/annotations` with query parameters
|
||||
|
||||
**Why?**
|
||||
- RESTful approach: DELETE resource with filters
|
||||
- No new endpoints to maintain
|
||||
- Backwards compatible (no query params = existing single-delete behavior still works via `/api/annotations/[id]`)
|
||||
- Simple to implement and test
|
||||
|
||||
**Query Parameter Schema:**
|
||||
```
|
||||
DELETE /api/annotations?type=line // Delete all lines
|
||||
DELETE /api/annotations?type=break_up // Delete all break_up labels
|
||||
DELETE /api/annotations?type=break_down // Delete all break_down labels
|
||||
DELETE /api/annotations?type=break_up,break_down // Delete all labels
|
||||
DELETE /api/annotations?all=true // Delete everything (nuclear option)
|
||||
```
|
||||
|
||||
**Alternatives Considered:**
|
||||
- **POST /api/annotations/bulk-delete**: Separate endpoint
|
||||
- ❌ Rejected: Not RESTful (POST for delete), adds complexity
|
||||
- **DELETE with request body**: `{ types: ['break_up', 'break_down'] }`
|
||||
- ❌ Rejected: DELETE with body is controversial in HTTP semantics, some proxies strip bodies
|
||||
- **Separate endpoints per type**: `/api/annotations/lines`, `/api/annotations/labels`
|
||||
- ❌ Rejected: More endpoints to maintain, inconsistent with current design
|
||||
|
||||
**Implementation:**
|
||||
- Modify `src/app/api/annotations/route.ts` DELETE handler
|
||||
- Parse query params: `const { type, all } = request.nextUrl.searchParams`
|
||||
- Use Drizzle ORM with conditional WHERE clause
|
||||
- Return `{ success: true, deleted: <count> }` for confirmation UI
|
||||
|
||||
### Decision 4: Docker Strategy - Standalone Output
|
||||
|
||||
**Choice**: Next.js standalone output mode with multi-stage build
|
||||
|
||||
**Why?**
|
||||
- Minimal production bundle (~50MB for Next.js runtime vs ~500MB with full node_modules)
|
||||
- Fast container builds (cached layers for dependencies)
|
||||
- Official Next.js recommendation for containerization
|
||||
- Better-sqlite3 works in standalone mode (native module properly bundled)
|
||||
|
||||
**Dockerfile Structure:**
|
||||
```dockerfile
|
||||
# Stage 1: Build
|
||||
FROM node:18-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Production
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
**Alternatives Considered:**
|
||||
- **Full node_modules copy**: Copy entire node_modules to production
|
||||
- ❌ Rejected: 5-10x larger images, slower deployments, unnecessary dev dependencies
|
||||
- **Distroless base images**: Google distroless/nodejs
|
||||
- ❌ Rejected: No shell for debugging, alpine is small enough, better-sqlite3 compatibility unclear
|
||||
- **Docker layer caching with pnpm/yarn**: Different package manager
|
||||
- ❌ Rejected: Project uses npm, no reason to change, npm v7+ has similar caching
|
||||
|
||||
**Database Persistence:**
|
||||
- Mount `/app/data` as Docker volume
|
||||
- SQLite file at `/app/data/candles.db`
|
||||
- Named volume in docker-compose: `candle-data:/app/data`
|
||||
|
||||
### Decision 5: Theme Implementation - CSS Variables + Tailwind Extension
|
||||
|
||||
**Choice**: Replace existing CSS variables in `globals.css` with hacker theme values, extend Tailwind config with custom colors and animations
|
||||
|
||||
**Why?**
|
||||
- Minimal refactoring: existing shadcn/ui components already use CSS variables
|
||||
- Tailwind extension provides utility classes for new theme features (glow effects, animations)
|
||||
- No component rewrites needed, just style overrides
|
||||
- Easy to toggle/revert if needed (swap CSS variable values)
|
||||
|
||||
**Color Mapping Strategy:**
|
||||
```css
|
||||
/* globals.css - override existing variables */
|
||||
:root {
|
||||
--background: 120 100% 4%; /* #0a0e0a - very dark green */
|
||||
--foreground: 120 100% 50%; /* #00ff41 - matrix green */
|
||||
--primary: 120 100% 50%; /* #00ff41 */
|
||||
--destructive: 348 100% 50%; /* #ff0040 - neon red */
|
||||
--border: 120 100% 10%; /* #003311 - dim green */
|
||||
/* ... map all existing variables to hacker theme equivalents */
|
||||
}
|
||||
```
|
||||
|
||||
**Tailwind Extension:**
|
||||
```typescript
|
||||
// tailwind.config.ts - extend existing config
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
matrix: '#00ff41',
|
||||
matrixDim: '#00cc33',
|
||||
matrixDark: '#003311',
|
||||
neonRed: '#ff0040',
|
||||
neonCyan: '#00d4ff',
|
||||
// ... keep existing shadcn colors
|
||||
},
|
||||
fontFamily: {
|
||||
mono: ['JetBrains Mono', 'Fira Code', 'Courier New', 'monospace'],
|
||||
},
|
||||
boxShadow: {
|
||||
'glow-sm': '0 0 8px #00ff41',
|
||||
'glow': '0 0 15px #00ff41, 0 0 30px rgba(0,255,65,0.5)',
|
||||
'glow-lg': '0 0 20px #00ff41, 0 0 40px rgba(0,255,65,0.5)',
|
||||
},
|
||||
keyframes: {
|
||||
'glow-pulse': {
|
||||
'0%, 100%': { boxShadow: '0 0 15px #00ff41' },
|
||||
'50%': { boxShadow: '0 0 30px #00ff41, 0 0 50px rgba(0,255,65,0.5)' },
|
||||
},
|
||||
flicker: {
|
||||
'0%, 100%': { opacity: '1' },
|
||||
'50%': { opacity: '0.8' },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'glow-pulse': 'glow-pulse 2s ease-in-out infinite',
|
||||
'flicker': 'flicker 0.1s ease-in-out',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Alternatives Considered:**
|
||||
- **Complete rewrite with different UI library**: Styled-components, Emotion
|
||||
- ❌ Rejected: Massive effort, no benefit, Tailwind + shadcn works well
|
||||
- **Theme toggle system**: Support multiple themes with switcher
|
||||
- ❌ Rejected: Not in requirements, adds complexity, hacker theme is the goal
|
||||
- **CSS-in-JS for glow effects**: Inline styles or styled-jsx
|
||||
- ❌ Rejected: Inconsistent with existing Tailwind approach, harder to maintain
|
||||
|
||||
**Font Loading:**
|
||||
- Add JetBrains Mono via Google Fonts in `layout.tsx` `<head>`
|
||||
- Fallback chain: JetBrains Mono → Fira Code → Courier New → system monospace
|
||||
- Set `font-family: var(--font-mono)` on body element
|
||||
|
||||
### Decision 6: Feedback Messages - Toast Component
|
||||
|
||||
**Choice**: Create reusable `Toast` component using shadcn/ui toast primitive, styled for terminal aesthetic
|
||||
|
||||
**Why?**
|
||||
- Consistent with existing shadcn/ui architecture
|
||||
- Already have toast primitive in `components/ui/` (likely from shadcn init)
|
||||
- Position: top-center, auto-dismiss after 4s
|
||||
- Terminal-style formatting: `> SUCCESS: Operation completed [3 items]`
|
||||
|
||||
**Implementation:**
|
||||
```typescript
|
||||
// src/components/ui/toast.tsx - style overrides
|
||||
// Add terminal-style formatting
|
||||
className="font-mono border-matrix bg-terminal text-matrix"
|
||||
|
||||
// Usage in components:
|
||||
import { useToast } from '@/hooks/use-toast'
|
||||
const { toast } = useToast()
|
||||
|
||||
toast({
|
||||
title: "> SUCCESS",
|
||||
description: "Deleted all labels [15 items]",
|
||||
variant: "default", // green
|
||||
})
|
||||
|
||||
toast({
|
||||
title: "> ERROR",
|
||||
description: "Failed to delete annotations [code: 500]",
|
||||
variant: "destructive", // red
|
||||
})
|
||||
```
|
||||
|
||||
**Alternatives Considered:**
|
||||
- **Custom notification system**: Build from scratch
|
||||
- ❌ Rejected: Reinventing the wheel, shadcn toast is battle-tested
|
||||
- **Browser alert()**: Native dialogs
|
||||
- ❌ Rejected: Ugly, blocks UI, no styling, poor UX
|
||||
- **Console.log only**: No visual feedback
|
||||
- ❌ Rejected: User won't see feedback, poor UX
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### Risk 1: Theme Accessibility
|
||||
**Risk**: Neon green on black may strain eyes during extended use, contrast issues for colorblind users
|
||||
|
||||
**Mitigation:**
|
||||
- Ensure WCAG AA contrast ratios (4.5:1 for text, 3:1 for UI elements)
|
||||
- Use #00ff41 which has sufficient contrast on #0a0e0a background
|
||||
- Provide option to revert theme in future (environment variable: `THEME=classic`)
|
||||
- Include prefers-reduced-motion support to disable animations
|
||||
|
||||
**Trade-off**: Distinctive aesthetic vs universal accessibility (accepting some users may prefer different theme)
|
||||
|
||||
### Risk 2: Label List Performance with Large Datasets
|
||||
**Risk**: 10,000+ labels may cause slow rendering and sluggish scrolling in sidebar list
|
||||
|
||||
**Mitigation:**
|
||||
- Set reasonable max-height (400px) for scrollable area
|
||||
- If performance issues arise, add pagination or react-window virtualization in follow-up
|
||||
- Monitor performance during development with test data (1000, 5000, 10000 labels)
|
||||
|
||||
**Trade-off**: Simple initial implementation vs premature optimization (YAGNI principle - add virtualization only if needed)
|
||||
|
||||
### Risk 3: Docker Image Size for Better-SQLite3
|
||||
**Risk**: Native SQLite module may require additional build dependencies in alpine, increasing image size
|
||||
|
||||
**Mitigation:**
|
||||
- Node:18-alpine includes necessary build tools (python, make, g++)
|
||||
- Better-sqlite3 compiles during npm install in build stage
|
||||
- Final image only includes compiled binary, not build tools
|
||||
- Test build locally: `docker build --progress=plain .` to verify size (<200MB target)
|
||||
|
||||
**Trade-off**: Alpine convenience vs potential build complexity (alpine is still best choice despite native modules)
|
||||
|
||||
### Risk 4: Breaking Existing Line Selection
|
||||
**Risk**: Adding label selection state and keyboard handlers may interfere with existing line selection logic
|
||||
|
||||
**Mitigation:**
|
||||
- Keep line and label selection completely separate (different state variables)
|
||||
- Keyboard handler checks both states: if label selected, delete label; if line selected, delete line
|
||||
- Thoroughly test all existing line operations (draw, select, drag, delete) after changes
|
||||
- Use TypeScript strict mode to catch state management issues
|
||||
|
||||
**Trade-off**: Duplicate code for selection logic vs refactoring working code (choose stability)
|
||||
|
||||
### Risk 5: Next.js Standalone Mode Compatibility
|
||||
**Risk**: Standalone output may not include all necessary files (static assets, environment configs)
|
||||
|
||||
**Mitigation:**
|
||||
- Follow official Next.js documentation for standalone mode
|
||||
- Explicitly copy `.next/static/` and `public/` directories in Dockerfile
|
||||
- Test docker build locally before committing
|
||||
- Verify all pages, API routes, and static assets work in container
|
||||
|
||||
**Trade-off**: Smaller image size vs potential missing files (standalone is well-tested by community)
|
||||
|
||||
### Risk 6: Confirmation Dialog UX
|
||||
**Risk**: Users may accidentally confirm bulk delete operations
|
||||
|
||||
**Mitigation:**
|
||||
- Use clear, explicit confirmation messages: "Delete all X annotations? This cannot be undone."
|
||||
- Require deliberate click on "Confirm" button (not Enter key, no auto-focus)
|
||||
- Show count of items to be deleted in confirmation message
|
||||
- Use destructive button styling (red) to signal danger
|
||||
- No undo system (out of scope), but users can re-annotate from CSV data
|
||||
|
||||
**Trade-off**: Safety vs speed (prioritize safety for destructive operations)
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Label Management (No Deployment Impact)
|
||||
1. Add `selectedLabelId` state and selection handlers in `CandleChart.tsx`
|
||||
2. Build label list UI in `Toolbox.tsx` (collapsible section)
|
||||
3. Implement search/filter functionality
|
||||
4. Add keyboard delete handler for labels
|
||||
5. Test with existing database, no schema changes
|
||||
6. **Deployment**: None (dev only), commit after testing
|
||||
|
||||
### Phase 2: API Extensions (Backwards Compatible)
|
||||
1. Modify `DELETE /api/annotations` to accept query params
|
||||
2. Add bulk delete logic with Drizzle ORM
|
||||
3. Create `GET /api/health` endpoint
|
||||
4. Test bulk operations with Postman/curl
|
||||
5. **Deployment**: None (API changes backwards compatible), commit after testing
|
||||
|
||||
### Phase 3: Hacker Theme (Visual Only)
|
||||
1. Load JetBrains Mono font in `layout.tsx`
|
||||
2. Update `globals.css` with new CSS variables
|
||||
3. Extend `tailwind.config.ts` with custom colors, shadows, animations
|
||||
4. Apply theme classes to existing components (Toolbox, buttons, inputs)
|
||||
5. Test visual appearance and contrast ratios
|
||||
6. **Deployment**: None (CSS/styling only), commit after visual review
|
||||
|
||||
### Phase 4: Docker Setup (New Deployment Path)
|
||||
1. Update `next.config.js` to enable standalone output
|
||||
2. Create `Dockerfile` with multi-stage build
|
||||
3. Create `docker-compose.yml` with volume configuration
|
||||
4. Create `.dockerignore` and `.env.example`
|
||||
5. Test local build: `docker-compose up --build`
|
||||
6. Verify database persistence across container restarts
|
||||
7. Update `DEPLOYMENT.md` with Docker instructions
|
||||
8. **Deployment**: Push image to registry, deploy to production server
|
||||
|
||||
### Rollback Strategy
|
||||
- **Label Management**: No rollback needed (additive feature, disable by not using)
|
||||
- **API Extensions**: Backwards compatible, no rollback needed
|
||||
- **Hacker Theme**: Revert `globals.css` and `tailwind.config.ts` commits
|
||||
- **Docker**: Rollback to direct Node.js deployment, use existing dev workflow
|
||||
|
||||
### Testing Checklist
|
||||
- [ ] All existing line drawing features work unchanged
|
||||
- [ ] Label selection highlights correct marker on chart
|
||||
- [ ] Label list displays all annotations with correct formatting
|
||||
- [ ] Search/filter correctly narrows label list
|
||||
- [ ] Delete individual label removes from chart and list
|
||||
- [ ] Delete all labels removes all markers after confirmation
|
||||
- [ ] Bulk delete API endpoints return correct counts
|
||||
- [ ] Health check endpoint returns 200
|
||||
- [ ] Theme maintains WCAG AA contrast ratios
|
||||
- [ ] All text uses monospace font
|
||||
- [ ] Glow effects appear on hover/active states
|
||||
- [ ] Docker container starts successfully
|
||||
- [ ] Database persists across container restarts
|
||||
- [ ] Application accessible on http://localhost:3000 in container
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Should we add label editing (change type)?**
|
||||
- Not in current scope, but consider for future
|
||||
- Would require modal dialog to change break_up ↔ break_down
|
||||
|
||||
2. **Should label list show price information?**
|
||||
- Specs don't mention it, but could be useful
|
||||
- Decision: Show timestamp and type only initially, add price in follow-up if requested
|
||||
|
||||
3. **Should we limit the number of labels displayed in sidebar?**
|
||||
- Pagination vs infinite scroll vs show all
|
||||
- Decision: Show all initially, add pagination only if performance issues arise
|
||||
|
||||
4. **Should Docker image include sample CSV data?**
|
||||
- Useful for testing, but increases image size
|
||||
- Decision: No, keep image minimal, provide sample CSV in repo documentation
|
||||
|
||||
5. **Should we add keyboard shortcuts for tool switching (e.g., 'L' for line tool)?**
|
||||
- Not in requirements, but could enhance UX
|
||||
- Decision: Out of scope for this change, consider in future UX iteration
|
||||
|
||||
These questions do not block implementation - proceed with spec-defined behavior and defer enhancements.
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
## Why
|
||||
|
||||
The candle annotator currently has basic line drawing functionality (Phase 1-4 complete: color support, visual feedback, selection, and endpoint dragging). Users need more comprehensive annotation management including bulk deletion, label CRUD operations, production deployment capability, and an improved UI theme. These features are essential for efficient annotation workflows and real-world deployment scenarios.
|
||||
|
||||
## What Changes
|
||||
|
||||
### Label Management System
|
||||
- **Delete All Labels**: Add button to remove all break_up/break_down markers with confirmation
|
||||
- **Select Label**: Make label markers (arrows) clickable to select/highlight them
|
||||
- **Delete Specific Label**: Delete individual selected label marker (keyboard or UI button)
|
||||
- **Label CRUD UI**: Add collapsible section in Toolbox sidebar showing:
|
||||
- List of all label annotations (timestamp, type, color-coded)
|
||||
- Click to select/highlight on chart
|
||||
- Delete button per label
|
||||
- Count of each label type
|
||||
- Search/filter by type
|
||||
|
||||
### Docker Deployment
|
||||
- **Dockerization**: Create production-ready Docker setup:
|
||||
- Multi-stage Dockerfile (build + runtime)
|
||||
- docker-compose.yml for easy deployment
|
||||
- Volume mounting for persistent SQLite database
|
||||
- Environment variable configuration
|
||||
- Health check endpoint
|
||||
- Production build optimization
|
||||
- Documentation in DEPLOYMENT.md
|
||||
|
||||
### UI Theme Enhancement
|
||||
- **Hacker-Style Theme**: Transform interface to minimal, terminal-inspired design:
|
||||
- Monospace fonts throughout
|
||||
- Matrix-style green (#00ff41) accents on dark background
|
||||
- Neon glow effects on active elements
|
||||
- Simplified borders with ASCII-style corners
|
||||
- Terminal-like command feedback messages
|
||||
- Minimalist icons or ASCII art alternatives
|
||||
- High contrast for readability
|
||||
- Cyber/retro aesthetic without sacrificing usability
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `label-management`: Complete CRUD interface for label annotations in sidebar
|
||||
- `docker-deployment`: Containerized deployment with docker-compose
|
||||
- `hacker-theme`: Terminal-inspired minimal dark theme with neon accents
|
||||
|
||||
### Modified Capabilities
|
||||
- (none - only adding new features, not changing existing requirements)
|
||||
|
||||
## Impact
|
||||
|
||||
### Code Changes
|
||||
- **Database**: No schema changes needed (existing tables support all operations)
|
||||
- **Components**:
|
||||
- `Toolbox.tsx`: Add label list UI, delete buttons, collapsible sections, theme styling
|
||||
- `CandleChart.tsx`: Add label selection click handlers, highlight logic
|
||||
- `page.tsx`: Add state for selected label, pass callbacks
|
||||
- **API Routes**:
|
||||
- `DELETE /api/annotations`: Add query param for bulk delete (by type or all)
|
||||
- `GET /api/health`: New endpoint for Docker health checks
|
||||
- **Styling**: Update `globals.css` and Tailwind config for hacker theme
|
||||
- **New Files**:
|
||||
- `Dockerfile`
|
||||
- `docker-compose.yml`
|
||||
- `.dockerignore`
|
||||
- `.env.example`
|
||||
|
||||
### Dependencies
|
||||
- No new npm packages required (existing stack covers all needs)
|
||||
- Docker runtime required for deployment
|
||||
|
||||
### Deployment
|
||||
- Current dev workflow unchanged
|
||||
- New production deployment path via Docker
|
||||
- Database persistence via volume mounts
|
||||
- Environment-based configuration
|
||||
|
||||
### User Experience
|
||||
- More efficient annotation workflows (bulk operations)
|
||||
- Better annotation visibility and organization (sidebar list)
|
||||
- Easier deployment to servers (docker-compose up)
|
||||
- More visually distinctive theme (hacker aesthetic)
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Multi-stage Dockerfile
|
||||
The project SHALL include a Dockerfile with multi-stage build for optimized production images.
|
||||
|
||||
#### Scenario: Build stage setup
|
||||
- **WHEN** Dockerfile build stage executes
|
||||
- **THEN** uses Node.js 18-alpine base image, copies package files, installs ALL dependencies including devDependencies, copies source code, and runs `npm run build`
|
||||
|
||||
#### Scenario: Runtime stage setup
|
||||
- **WHEN** Dockerfile runtime stage executes
|
||||
- **THEN** uses Node.js 18-alpine base image, creates non-root user 'appuser', copies only production dependencies and built files from build stage, and sets USER to appuser
|
||||
|
||||
#### Scenario: Working directory structure
|
||||
- **WHEN** container runs
|
||||
- **THEN** application files are in /app directory, database volume mounts to /app/data, and permissions allow appuser to write to /app/data
|
||||
|
||||
#### Scenario: Environment variables in Dockerfile
|
||||
- **WHEN** Dockerfile defines environment
|
||||
- **THEN** sets NODE_ENV=production, PORT=3000, and HOSTNAME=0.0.0.0 for Next.js standalone server
|
||||
|
||||
#### Scenario: Exposed ports
|
||||
- **WHEN** container is built
|
||||
- **THEN** Dockerfile exposes port 3000 for HTTP traffic
|
||||
|
||||
#### Scenario: Container startup
|
||||
- **WHEN** container starts
|
||||
- **THEN** executes `node server.js` (Next.js standalone output) as the CMD
|
||||
|
||||
### Requirement: Docker Compose configuration
|
||||
The project SHALL include docker-compose.yml for simplified deployment orchestration.
|
||||
|
||||
#### Scenario: Service definition
|
||||
- **WHEN** docker-compose.yml is parsed
|
||||
- **THEN** defines single service named 'candle-annotator' using Dockerfile from current directory
|
||||
|
||||
#### Scenario: Port mapping
|
||||
- **WHEN** docker-compose up runs
|
||||
- **THEN** maps host port 3000 to container port 3000 (configurable via PORT environment variable)
|
||||
|
||||
#### Scenario: Volume mounting for database
|
||||
- **WHEN** docker-compose up runs
|
||||
- **THEN** mounts named volume 'candle-data' to /app/data in container for SQLite database persistence
|
||||
|
||||
#### Scenario: Environment variable configuration
|
||||
- **WHEN** docker-compose.yml is used
|
||||
- **THEN** supports loading environment variables from .env file for NODE_ENV, PORT, and other configs
|
||||
|
||||
#### Scenario: Restart policy
|
||||
- **WHEN** container crashes or stops
|
||||
- **THEN** docker-compose automatically restarts container unless explicitly stopped (restart: unless-stopped)
|
||||
|
||||
#### Scenario: Volume declaration
|
||||
- **WHEN** docker-compose.yml is parsed
|
||||
- **THEN** declares 'candle-data' as named volume in volumes section
|
||||
|
||||
### Requirement: Environment variable configuration
|
||||
The project SHALL use environment variables for runtime configuration.
|
||||
|
||||
#### Scenario: .env.example file
|
||||
- **WHEN** repository is cloned
|
||||
- **THEN** includes .env.example file documenting all configurable environment variables with example values
|
||||
|
||||
#### Scenario: PORT configuration
|
||||
- **WHEN** PORT environment variable is set
|
||||
- **THEN** Next.js server listens on specified port (default: 3000)
|
||||
|
||||
#### Scenario: NODE_ENV configuration
|
||||
- **WHEN** NODE_ENV environment variable is set to 'production'
|
||||
- **THEN** Next.js runs in production mode with optimizations enabled
|
||||
|
||||
#### Scenario: Database path configuration
|
||||
- **WHEN** DATABASE_PATH environment variable is set (optional)
|
||||
- **THEN** SQLite database file is created at specified path (default: ./data/candles.db)
|
||||
|
||||
#### Scenario: HOSTNAME configuration
|
||||
- **WHEN** HOSTNAME environment variable is set
|
||||
- **THEN** Next.js server binds to specified hostname (default: 0.0.0.0 for containers)
|
||||
|
||||
### Requirement: Health check endpoint
|
||||
The API SHALL provide a health check endpoint for container orchestration.
|
||||
|
||||
#### Scenario: Health check endpoint responds
|
||||
- **WHEN** GET request sent to `/api/health`
|
||||
- **THEN** system returns 200 status with JSON `{ status: 'ok', timestamp: <unix_timestamp> }`
|
||||
|
||||
#### Scenario: Database connection check
|
||||
- **WHEN** GET request sent to `/api/health?check=db`
|
||||
- **THEN** system attempts simple database query and returns 200 if successful, 503 if database unavailable
|
||||
|
||||
#### Scenario: Health check in Dockerfile
|
||||
- **WHEN** Dockerfile defines HEALTHCHECK
|
||||
- **THEN** runs `curl -f http://localhost:3000/api/health || exit 1` every 30 seconds with 3 retries
|
||||
|
||||
### Requirement: .dockerignore file
|
||||
The project SHALL include .dockerignore to exclude unnecessary files from Docker context.
|
||||
|
||||
#### Scenario: Excluded files
|
||||
- **WHEN** Docker build context is created
|
||||
- **THEN** .dockerignore excludes node_modules, .next, .git, data/, *.md, .env*, and test files
|
||||
|
||||
#### Scenario: Included files
|
||||
- **WHEN** Docker build context is created
|
||||
- **THEN** includes package.json, package-lock.json, source code in src/, and required config files
|
||||
|
||||
### Requirement: Next.js standalone output
|
||||
The build SHALL use Next.js standalone output mode for minimal production bundle.
|
||||
|
||||
#### Scenario: next.config.js standalone setting
|
||||
- **WHEN** next.config.js is read
|
||||
- **THEN** output property is set to 'standalone'
|
||||
|
||||
#### Scenario: Standalone build output
|
||||
- **WHEN** npm run build executes
|
||||
- **THEN** Next.js creates .next/standalone directory with minimal runtime files and dependencies
|
||||
|
||||
#### Scenario: Copy standalone files to image
|
||||
- **WHEN** Dockerfile runtime stage executes
|
||||
- **THEN** copies .next/standalone/ contents to /app, copies .next/static to /app/.next/static, and copies public/ to /app/public
|
||||
|
||||
### Requirement: Production build optimization
|
||||
The Docker image SHALL be optimized for production use with minimal size.
|
||||
|
||||
#### Scenario: Use alpine base images
|
||||
- **WHEN** Dockerfile specifies base images
|
||||
- **THEN** uses node:18-alpine for both build and runtime stages
|
||||
|
||||
#### Scenario: Multi-stage build cleanup
|
||||
- **WHEN** Docker image is built
|
||||
- **THEN** build artifacts, devDependencies, and source files are not included in final image
|
||||
|
||||
#### Scenario: Layer caching optimization
|
||||
- **WHEN** Dockerfile is structured
|
||||
- **THEN** package.json and package-lock.json are copied and dependencies installed before source code copy for better layer caching
|
||||
|
||||
#### Scenario: Final image size
|
||||
- **WHEN** Docker image build completes
|
||||
- **THEN** final image size is under 200MB (excluding data volume)
|
||||
|
||||
### Requirement: Database persistence
|
||||
The deployment SHALL ensure SQLite database persists across container restarts.
|
||||
|
||||
#### Scenario: Volume mounting
|
||||
- **WHEN** container runs with volume mount
|
||||
- **THEN** /app/data directory is mounted from host or Docker volume
|
||||
|
||||
#### Scenario: Database file location
|
||||
- **WHEN** application initializes database
|
||||
- **THEN** SQLite file is created at /app/data/candles.db (inside mounted volume)
|
||||
|
||||
#### Scenario: Container restart preserves data
|
||||
- **WHEN** container is stopped and restarted
|
||||
- **THEN** existing database file is reused and all annotations and candles remain intact
|
||||
|
||||
#### Scenario: File permissions
|
||||
- **WHEN** container creates database file
|
||||
- **THEN** appuser has read/write permissions to /app/data directory
|
||||
|
||||
### Requirement: Deployment documentation
|
||||
DEPLOYMENT.md SHALL include comprehensive Docker deployment instructions.
|
||||
|
||||
#### Scenario: Docker deployment section
|
||||
- **WHEN** DEPLOYMENT.md is read
|
||||
- **THEN** includes dedicated "Docker Deployment" section with prerequisites, build steps, and run commands
|
||||
|
||||
#### Scenario: Quick start commands
|
||||
- **WHEN** following deployment docs
|
||||
- **THEN** provides complete commands: `docker-compose up -d` for production and `docker-compose up --build` for rebuilding
|
||||
|
||||
#### Scenario: Environment setup instructions
|
||||
- **WHEN** following deployment docs
|
||||
- **THEN** explains how to copy .env.example to .env and configure required variables
|
||||
|
||||
#### Scenario: Volume backup instructions
|
||||
- **WHEN** following deployment docs
|
||||
- **THEN** provides commands to backup database: `docker cp candle-annotator:/app/data/candles.db ./backup.db`
|
||||
|
||||
#### Scenario: Troubleshooting section
|
||||
- **WHEN** deployment issues occur
|
||||
- **THEN** DEPLOYMENT.md includes troubleshooting for common Docker issues: port conflicts, permission errors, build failures
|
||||
|
||||
#### Scenario: Update and maintenance
|
||||
- **WHEN** updating deployed application
|
||||
- **THEN** documentation provides steps: pull new code, rebuild image, restart containers with data preservation
|
||||
|
||||
### Requirement: Container security
|
||||
The Docker setup SHALL follow security best practices.
|
||||
|
||||
#### Scenario: Non-root user
|
||||
- **WHEN** container runs
|
||||
- **THEN** application process runs as non-root user 'appuser' (UID 1000)
|
||||
|
||||
#### Scenario: Read-only filesystem where possible
|
||||
- **WHEN** container runs
|
||||
- **THEN** only /app/data directory requires write permissions, all other files are read-only to appuser
|
||||
|
||||
#### Scenario: No sensitive data in image
|
||||
- **WHEN** Docker image is built
|
||||
- **THEN** .env files, secrets, and database files are not included in image layers
|
||||
|
||||
#### Scenario: Minimal attack surface
|
||||
- **WHEN** container runs
|
||||
- **THEN** only port 3000 is exposed, no SSH, no unnecessary services, alpine base reduces package vulnerabilities
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Monospace typography
|
||||
The application SHALL use monospace fonts throughout the entire interface.
|
||||
|
||||
#### Scenario: Primary font family
|
||||
- **WHEN** CSS font-family is applied
|
||||
- **THEN** system uses font stack: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace
|
||||
|
||||
#### Scenario: Font loading
|
||||
- **WHEN** application loads
|
||||
- **THEN** system loads JetBrains Mono font from Google Fonts or local files with weights: 400, 500, 700
|
||||
|
||||
#### Scenario: All text elements
|
||||
- **WHEN** rendering any text (buttons, labels, inputs, tooltips, headings)
|
||||
- **THEN** all text uses monospace font family without exception
|
||||
|
||||
#### Scenario: Number formatting
|
||||
- **WHEN** displaying numbers (prices, timestamps, counts)
|
||||
- **THEN** uses tabular-nums CSS property for aligned columns
|
||||
|
||||
### Requirement: Matrix-style color scheme
|
||||
The application SHALL use a dark background with neon green (#00ff41) accent color scheme.
|
||||
|
||||
#### Scenario: Background colors
|
||||
- **WHEN** rendering main layout
|
||||
- **THEN** page background is #0a0e0a (near black), sidebar background is #0d110d, and chart background is #000000
|
||||
|
||||
#### Scenario: Primary accent color
|
||||
- **WHEN** highlighting interactive elements, active states, or success messages
|
||||
- **THEN** uses #00ff41 (matrix green) as the primary accent color
|
||||
|
||||
#### Scenario: Secondary accent colors
|
||||
- **WHEN** rendering labels or status indicators
|
||||
- **THEN** uses #ff0040 (neon red) for break_down/destructive, #00ff41 (neon green) for break_up/success, #00d4ff (neon cyan) for informational, #ffff00 (neon yellow) for warnings
|
||||
|
||||
#### Scenario: Text colors
|
||||
- **WHEN** rendering text
|
||||
- **THEN** primary text is #00ff41, secondary text is #00cc33 (dimmer green), and disabled text is #003311 (very dim green)
|
||||
|
||||
#### Scenario: Border colors
|
||||
- **WHEN** rendering borders
|
||||
- **THEN** uses #00ff41 with 1px solid for active elements, #003311 for inactive borders
|
||||
|
||||
### Requirement: Neon glow effects
|
||||
The application SHALL apply glow effects to active and interactive elements.
|
||||
|
||||
#### Scenario: Button hover glow
|
||||
- **WHEN** user hovers over any button
|
||||
- **THEN** button shows box-shadow: 0 0 10px #00ff41, 0 0 20px #00ff4180
|
||||
|
||||
#### Scenario: Active tool glow
|
||||
- **WHEN** a tool is active (selected)
|
||||
- **THEN** button shows pulsing glow animation with box-shadow: 0 0 15px #00ff41, 0 0 30px #00ff4180
|
||||
|
||||
#### Scenario: Selected element glow
|
||||
- **WHEN** a line or label is selected
|
||||
- **THEN** element shows glow effect with filter: drop-shadow(0 0 8px #00ff41)
|
||||
|
||||
#### Scenario: Input focus glow
|
||||
- **WHEN** user focuses on text input or search field
|
||||
- **THEN** input shows border glow: box-shadow: 0 0 8px #00ff41
|
||||
|
||||
#### Scenario: Cursor circle glow
|
||||
- **WHEN** drawing line with cursor circle visible
|
||||
- **THEN** cursor circle has stroke glow with filter: drop-shadow(0 0 4px currentColor)
|
||||
|
||||
### Requirement: ASCII-style borders
|
||||
The application SHALL use terminal-inspired border styling with corner characters.
|
||||
|
||||
#### Scenario: Container borders
|
||||
- **WHEN** rendering bordered containers (Toolbox, label list sections)
|
||||
- **THEN** uses 1px solid borders with #00ff41 color
|
||||
|
||||
#### Scenario: Border corners with pseudo-elements
|
||||
- **WHEN** rendering section headers or important containers
|
||||
- **THEN** uses CSS ::before and ::after pseudo-elements to display ASCII corner characters: ┌ ┐ └ ┘
|
||||
|
||||
#### Scenario: Separator lines
|
||||
- **WHEN** rendering section separators
|
||||
- **THEN** uses border-top: 1px solid #003311 with optional ASCII characters (─) via pseudo-elements
|
||||
|
||||
#### Scenario: Card-style containers
|
||||
- **WHEN** rendering label list entries or modal dialogs
|
||||
- **THEN** containers have 1px solid border with subtle inset shadow for depth
|
||||
|
||||
### Requirement: Terminal-like feedback messages
|
||||
The application SHALL display command-style feedback for user actions.
|
||||
|
||||
#### Scenario: Success message format
|
||||
- **WHEN** user completes an action successfully (delete, create, export)
|
||||
- **THEN** displays message in format: `> SUCCESS: <action> completed [<count> items]` in green text
|
||||
|
||||
#### Scenario: Error message format
|
||||
- **WHEN** an error occurs
|
||||
- **THEN** displays message in format: `> ERROR: <description> [code: <error_code>]` in red text
|
||||
|
||||
#### Scenario: Info message format
|
||||
- **WHEN** displaying informational message
|
||||
- **THEN** displays message in format: `> INFO: <message>` in cyan text
|
||||
|
||||
#### Scenario: Message container styling
|
||||
- **WHEN** displaying any feedback message
|
||||
- **THEN** container has monospace font, left-aligned text, subtle border, and auto-dismiss after 4 seconds
|
||||
|
||||
#### Scenario: Message animation
|
||||
- **WHEN** feedback message appears
|
||||
- **THEN** slides in from top with fade-in animation (0.3s ease-out)
|
||||
|
||||
### Requirement: Minimalist icon treatment
|
||||
The application SHALL use simplified, outline-style icons or ASCII alternatives.
|
||||
|
||||
#### Scenario: Lucide icon styling
|
||||
- **WHEN** rendering Lucide React icons
|
||||
- **THEN** icons use stroke-width: 1.5, size: 20px, and color matches text color (#00ff41)
|
||||
|
||||
#### Scenario: Icon hover effect
|
||||
- **WHEN** user hovers over icon button
|
||||
- **THEN** icon glows with filter: drop-shadow(0 0 4px currentColor)
|
||||
|
||||
#### Scenario: ASCII alternative icons (optional)
|
||||
- **WHEN** rendering simple actions (delete, expand, collapse)
|
||||
- **THEN** optionally uses ASCII characters: [×] for delete, [▼] for expand, [▲] for collapse, [>] for actions
|
||||
|
||||
#### Scenario: Icon color consistency
|
||||
- **WHEN** icon is in active/selected state
|
||||
- **THEN** icon color is #00ff41
|
||||
- **WHEN** icon is in disabled state
|
||||
- **THEN** icon color is #003311
|
||||
|
||||
### Requirement: High contrast for readability
|
||||
The application SHALL maintain WCAG AA contrast ratios for all text.
|
||||
|
||||
#### Scenario: Primary text contrast
|
||||
- **WHEN** displaying primary text (#00ff41) on dark background (#0a0e0a)
|
||||
- **THEN** contrast ratio is at least 4.5:1 (WCAG AA standard)
|
||||
|
||||
#### Scenario: Secondary text contrast
|
||||
- **WHEN** displaying secondary text (#00cc33) on dark background
|
||||
- **THEN** contrast ratio is at least 4.5:1
|
||||
|
||||
#### Scenario: Button text contrast
|
||||
- **WHEN** button is in any state (default, hover, active, disabled)
|
||||
- **THEN** text maintains minimum 4.5:1 contrast with background
|
||||
|
||||
#### Scenario: Chart element visibility
|
||||
- **WHEN** rendering lines and markers on chart
|
||||
- **THEN** all annotations have sufficient contrast against black background and candles
|
||||
|
||||
### Requirement: Simplified button styling
|
||||
The application SHALL use minimal, flat button design with terminal aesthetic.
|
||||
|
||||
#### Scenario: Default button appearance
|
||||
- **WHEN** rendering buttons in default state
|
||||
- **THEN** buttons have transparent background, 1px solid #00ff41 border, #00ff41 text, 4px padding, and no rounded corners (border-radius: 0)
|
||||
|
||||
#### Scenario: Hover button appearance
|
||||
- **WHEN** user hovers over button
|
||||
- **THEN** background changes to #00ff4110 (10% opacity), border glows, and text remains #00ff41
|
||||
|
||||
#### Scenario: Active button appearance
|
||||
- **WHEN** button is active/pressed
|
||||
- **THEN** background is #00ff4120 (20% opacity), border is 2px solid, and includes pulsing glow animation
|
||||
|
||||
#### Scenario: Disabled button appearance
|
||||
- **WHEN** button is disabled
|
||||
- **THEN** background is transparent, border is #003311, text is #003311, and no hover effects
|
||||
|
||||
#### Scenario: Destructive button styling
|
||||
- **WHEN** button represents destructive action (delete)
|
||||
- **THEN** uses #ff0040 (neon red) for border and text instead of green, with matching red glow on hover
|
||||
|
||||
### Requirement: Cyber-retro aesthetic elements
|
||||
The application SHALL include subtle design elements that enhance the hacker theme.
|
||||
|
||||
#### Scenario: Scanline effect (optional)
|
||||
- **WHEN** rendering main container
|
||||
- **THEN** applies subtle CSS repeating-linear-gradient overlay simulating CRT scanlines with 2px spacing at 5% opacity
|
||||
|
||||
#### Scenario: Noise texture (optional)
|
||||
- **WHEN** rendering background
|
||||
- **THEN** applies subtle noise texture using CSS filter or background-image at 3% opacity for grain effect
|
||||
|
||||
#### Scenario: Typewriter animation for messages
|
||||
- **WHEN** displaying new feedback message
|
||||
- **THEN** text appears with brief letter-by-letter typing animation (0.02s per character, max 0.5s total)
|
||||
|
||||
#### Scenario: Flicker animation on focus
|
||||
- **WHEN** user focuses on input field
|
||||
- **THEN** border shows brief flicker animation (0.1s) simulating electrical surge
|
||||
|
||||
#### Scenario: ASCII art branding (optional)
|
||||
- **WHEN** application loads or in header
|
||||
- **THEN** displays application name in ASCII art style using monospace font
|
||||
|
||||
### Requirement: Responsive dark theme
|
||||
The application SHALL maintain theme consistency across all components and states.
|
||||
|
||||
#### Scenario: Modal dialogs
|
||||
- **WHEN** displaying confirmation dialogs
|
||||
- **THEN** modal has dark background (#0d110d), neon green border, monospace font, and terminal-style title (e.g., "> CONFIRM ACTION")
|
||||
|
||||
#### Scenario: Input fields
|
||||
- **WHEN** rendering text inputs or search fields
|
||||
- **THEN** inputs have transparent background, #00ff41 border, #00ff41 text, #00ff4120 placeholder, and cursor color #00ff41
|
||||
|
||||
#### Scenario: Dropdown menus
|
||||
- **WHEN** displaying filter or select dropdowns
|
||||
- **THEN** dropdown has dark background (#0d110d), neon green border, #00ff41 option text, hover state with #00ff4120 background
|
||||
|
||||
#### Scenario: Scrollbars
|
||||
- **WHEN** content requires scrolling
|
||||
- **THEN** custom scrollbar with #003311 track, #00ff41 thumb, and 8px width
|
||||
|
||||
#### Scenario: Tooltips
|
||||
- **WHEN** displaying tooltips on hover
|
||||
- **THEN** tooltip has dark background (#0d110d), 1px #00ff41 border, monospace font, and terminal-style text
|
||||
|
||||
### Requirement: Animation performance
|
||||
The application SHALL use performant CSS animations that don't impact chart rendering.
|
||||
|
||||
#### Scenario: GPU acceleration
|
||||
- **WHEN** applying animations (glow, pulse, fade)
|
||||
- **THEN** uses CSS transform and opacity properties with will-change hints for GPU acceleration
|
||||
|
||||
#### Scenario: Animation duration
|
||||
- **WHEN** defining animations
|
||||
- **THEN** all animations complete within 0.5s, with most hover effects at 0.2s
|
||||
|
||||
#### Scenario: Reduced motion support
|
||||
- **WHEN** user has prefers-reduced-motion enabled
|
||||
- **THEN** system disables glow animations, typewriter effects, and pulsing, keeping only instant transitions
|
||||
|
||||
#### Scenario: Chart performance
|
||||
- **WHEN** theme is active and chart is rendering
|
||||
- **THEN** SVG overlay and chart rendering maintains 60fps without jank from theme effects
|
||||
|
||||
### Requirement: Tailwind CSS configuration
|
||||
The Tailwind config SHALL be updated to support hacker theme tokens.
|
||||
|
||||
#### Scenario: Custom colors in tailwind.config
|
||||
- **WHEN** tailwind.config.js is parsed
|
||||
- **THEN** defines custom colors: matrix: '#00ff41', matrixDim: '#00cc33', matrixDark: '#003311', neonRed: '#ff0040', neonCyan: '#00d4ff', neonYellow: '#ffff00', terminal: '#0a0e0a', terminalLight: '#0d110d'
|
||||
|
||||
#### Scenario: Custom font families
|
||||
- **WHEN** tailwind.config.js is parsed
|
||||
- **THEN** defines fontFamily: { mono: ['JetBrains Mono', 'Fira Code', 'Courier New', 'monospace'] }
|
||||
|
||||
#### Scenario: Custom animations
|
||||
- **WHEN** tailwind.config.js is parsed
|
||||
- **THEN** defines keyframes for 'glow-pulse', 'flicker', 'scanline-move' and corresponding animation utilities
|
||||
|
||||
#### Scenario: Custom shadows
|
||||
- **WHEN** tailwind.config.js is parsed
|
||||
- **THEN** defines boxShadow: { 'glow-sm': '0 0 8px #00ff41', 'glow': '0 0 15px #00ff41, 0 0 30px #00ff4180', 'glow-lg': '0 0 20px #00ff41, 0 0 40px #00ff4180' }
|
||||
|
||||
### Requirement: globals.css updates
|
||||
The globals.css file SHALL define base styles and CSS variables for the hacker theme.
|
||||
|
||||
#### Scenario: CSS custom properties
|
||||
- **WHEN** globals.css is loaded
|
||||
- **THEN** defines CSS variables: --color-matrix, --color-matrix-dim, --color-matrix-dark, --color-neon-red, --color-neon-cyan, --color-terminal, --font-mono
|
||||
|
||||
#### Scenario: Base body styles
|
||||
- **WHEN** page loads
|
||||
- **THEN** body element has background: --color-terminal, color: --color-matrix, font-family: --font-mono, and -webkit-font-smoothing: antialiased
|
||||
|
||||
#### Scenario: Selection styling
|
||||
- **WHEN** user selects text
|
||||
- **THEN** selection background is #00ff4140 (40% opacity) and text is #00ff41
|
||||
|
||||
#### Scenario: Scrollbar styling
|
||||
- **WHEN** globals.css is loaded
|
||||
- **THEN** defines custom scrollbar styles using ::-webkit-scrollbar pseudo-elements with theme colors
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Select label by clicking marker
|
||||
The system SHALL allow users to select label annotations by clicking on their marker symbols on the chart.
|
||||
|
||||
#### Scenario: Click on break_up marker
|
||||
- **WHEN** user clicks on a green arrow (break_up) marker on the chart
|
||||
- **THEN** system sets selectedLabelId state to the annotation ID, highlights the marker with increased size and glow effect, and scrolls to the label in the sidebar list
|
||||
|
||||
#### Scenario: Click on break_down marker
|
||||
- **WHEN** user clicks on a red arrow (break_down) marker on the chart
|
||||
- **THEN** system sets selectedLabelId state to the annotation ID, highlights the marker with increased size and glow effect, and scrolls to the label in the sidebar list
|
||||
|
||||
#### Scenario: Click already selected marker
|
||||
- **WHEN** user clicks on a marker that is already selected
|
||||
- **THEN** system deselects the marker by setting selectedLabelId to null and removes highlight effects
|
||||
|
||||
#### Scenario: Click different marker while one is selected
|
||||
- **WHEN** user clicks on a different marker while another marker is already selected
|
||||
- **THEN** system deselects the previous marker, selects the new marker, and updates the highlight to the new marker
|
||||
|
||||
#### Scenario: Tool mode during label selection
|
||||
- **WHEN** user selects a label marker
|
||||
- **THEN** selection works regardless of active tool (works in all modes: none, line, delete, break_up, break_down)
|
||||
|
||||
### Requirement: Delete selected label with keyboard
|
||||
The system SHALL allow users to delete the currently selected label annotation using keyboard shortcuts.
|
||||
|
||||
#### Scenario: Delete selected label with Delete key
|
||||
- **WHEN** a label is selected (selectedLabelId is set) and user presses Delete key
|
||||
- **THEN** system sends DELETE request to `/api/annotations/{id}`, removes marker from display, clears selection state, and triggers annotation refresh
|
||||
|
||||
#### Scenario: Delete selected label with Backspace key
|
||||
- **WHEN** a label is selected and user presses Backspace key
|
||||
- **THEN** system sends DELETE request to `/api/annotations/{id}`, removes marker from display, clears selection state, and triggers annotation refresh
|
||||
|
||||
#### Scenario: No label selected when pressing delete
|
||||
- **WHEN** no label is selected (selectedLabelId is null) and user presses Delete or Backspace key
|
||||
- **THEN** system takes no action on labels (may still delete selected line if line is selected)
|
||||
|
||||
### Requirement: Label list in sidebar
|
||||
The Toolbox SHALL display a collapsible section showing all label annotations with interactive controls.
|
||||
|
||||
#### Scenario: Display label list section
|
||||
- **WHEN** Toolbox renders
|
||||
- **THEN** system displays "Label Annotations" section below the annotation tools with collapse/expand toggle button
|
||||
|
||||
#### Scenario: Label list expanded
|
||||
- **WHEN** "Label Annotations" section is expanded
|
||||
- **THEN** system displays scrollable list of all label annotations sorted by timestamp (newest first), with each entry showing timestamp, label type badge, and delete button
|
||||
|
||||
#### Scenario: Label list collapsed
|
||||
- **WHEN** user clicks collapse button on "Label Annotations" section
|
||||
- **THEN** system hides the label list but shows count summary "Labels: X break_up, Y break_down"
|
||||
|
||||
#### Scenario: Empty label list
|
||||
- **WHEN** no label annotations exist in database
|
||||
- **THEN** section displays message "No labels yet. Click Break Up or Break Down tools to add labels."
|
||||
|
||||
#### Scenario: Label entry format
|
||||
- **WHEN** displaying a label in the list
|
||||
- **THEN** each entry shows formatted timestamp (e.g., "Feb 12, 14:30"), colored badge ("BREAK UP" in green or "BREAK DOWN" in red), and trash icon delete button
|
||||
|
||||
### Requirement: Click label in list to select on chart
|
||||
The system SHALL allow users to click a label in the sidebar list to select and highlight it on the chart.
|
||||
|
||||
#### Scenario: Click label entry in sidebar
|
||||
- **WHEN** user clicks on a label entry in the sidebar list
|
||||
- **THEN** system sets selectedLabelId to that annotation ID, highlights the corresponding marker on chart, and centers the chart view on that timestamp
|
||||
|
||||
#### Scenario: Click already selected label in list
|
||||
- **WHEN** user clicks on a label entry that is already selected
|
||||
- **THEN** system deselects the label by setting selectedLabelId to null and removes highlights
|
||||
|
||||
#### Scenario: Selected label visual in list
|
||||
- **WHEN** a label is selected
|
||||
- **THEN** the corresponding entry in the sidebar list has a highlighted background and border
|
||||
|
||||
### Requirement: Delete label from sidebar
|
||||
The system SHALL provide delete buttons for each label in the sidebar list.
|
||||
|
||||
#### Scenario: Click delete button for label
|
||||
- **WHEN** user clicks trash icon button next to a label in the sidebar
|
||||
- **THEN** system sends DELETE request to `/api/annotations/{id}`, removes label from list and chart, clears selection if that label was selected, and triggers annotation refresh
|
||||
|
||||
#### Scenario: Delete button styling
|
||||
- **WHEN** displaying delete buttons in label list
|
||||
- **THEN** buttons use red/destructive color on hover and show tooltip "Delete this label"
|
||||
|
||||
### Requirement: Search and filter labels
|
||||
The Toolbox label section SHALL provide search and filter controls.
|
||||
|
||||
#### Scenario: Filter by label type
|
||||
- **WHEN** user selects filter dropdown and chooses "Break Up"
|
||||
- **THEN** system displays only break_up annotations in the list
|
||||
|
||||
#### Scenario: Filter by label type - Break Down
|
||||
- **WHEN** user selects filter dropdown and chooses "Break Down"
|
||||
- **THEN** system displays only break_down annotations in the list
|
||||
|
||||
#### Scenario: Show all labels
|
||||
- **WHEN** user selects filter dropdown and chooses "All"
|
||||
- **THEN** system displays all label annotations regardless of type
|
||||
|
||||
#### Scenario: Search by timestamp
|
||||
- **WHEN** user types text into search input field
|
||||
- **THEN** system filters list to show only labels whose formatted timestamp contains the search text (case-insensitive)
|
||||
|
||||
#### Scenario: Combined search and filter
|
||||
- **WHEN** user has both search text and type filter active
|
||||
- **THEN** system shows labels that match both criteria (AND logic)
|
||||
|
||||
### Requirement: Label count display
|
||||
The Toolbox SHALL display counts of each label type.
|
||||
|
||||
#### Scenario: Display label counts
|
||||
- **WHEN** Toolbox renders and labels exist
|
||||
- **THEN** system displays count summary "Break Up: X | Break Down: Y" at top of label section
|
||||
|
||||
#### Scenario: Zero labels
|
||||
- **WHEN** no labels exist in database
|
||||
- **THEN** count summary displays "Break Up: 0 | Break Down: 0"
|
||||
|
||||
#### Scenario: Count updates after delete
|
||||
- **WHEN** user deletes a label
|
||||
- **THEN** count summary updates immediately to reflect the new totals
|
||||
|
||||
### Requirement: Delete all labels with confirmation
|
||||
The system SHALL provide a "Delete All Labels" button with confirmation dialog.
|
||||
|
||||
#### Scenario: Click Delete All Labels button
|
||||
- **WHEN** user clicks "Delete All Labels" button in Toolbox
|
||||
- **THEN** system displays confirmation dialog with message "Delete all label annotations (Break Up and Break Down)? This cannot be undone." and Cancel/Confirm buttons
|
||||
|
||||
#### Scenario: User confirms delete all labels
|
||||
- **WHEN** confirmation dialog is open and user clicks Confirm button
|
||||
- **THEN** system sends DELETE request to `/api/annotations?type=break_up,break_down`, removes all label markers from chart, clears label list, clears selection state, triggers annotation refresh, and closes dialog
|
||||
|
||||
#### Scenario: User cancels delete all labels
|
||||
- **WHEN** confirmation dialog is open and user clicks Cancel button
|
||||
- **THEN** system closes dialog without making any API calls or removing any labels
|
||||
|
||||
#### Scenario: Delete All Labels button visibility
|
||||
- **WHEN** one or more label annotations exist
|
||||
- **THEN** "Delete All Labels" button is enabled with destructive styling
|
||||
- **WHEN** no label annotations exist
|
||||
- **THEN** "Delete All Labels" button is disabled with reduced opacity
|
||||
|
||||
### Requirement: API support for label operations
|
||||
The API SHALL support filtering and bulk deletion of label annotations.
|
||||
|
||||
#### Scenario: Delete all break_up labels
|
||||
- **WHEN** DELETE request sent to `/api/annotations?type=break_up`
|
||||
- **THEN** system deletes all annotations where label_type equals 'break_up' and returns `{ success: true, deleted: <count> }`
|
||||
|
||||
#### Scenario: Delete all break_down labels
|
||||
- **WHEN** DELETE request sent to `/api/annotations?type=break_down`
|
||||
- **THEN** system deletes all annotations where label_type equals 'break_down' and returns `{ success: true, deleted: <count> }`
|
||||
|
||||
#### Scenario: Delete multiple types at once
|
||||
- **WHEN** DELETE request sent to `/api/annotations?type=break_up,break_down`
|
||||
- **THEN** system deletes all annotations where label_type is either 'break_up' or 'break_down' and returns `{ success: true, deleted: <count> }`
|
||||
|
||||
#### Scenario: Get labels with type filter
|
||||
- **WHEN** GET request sent to `/api/annotations?type=break_up`
|
||||
- **THEN** system returns only annotations where label_type equals 'break_up'
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
# Implementation Tasks
|
||||
|
||||
## 1. Setup and Configuration
|
||||
|
||||
- [ ] 1.1 Load JetBrains Mono font from Google Fonts in `src/app/layout.tsx` (add to head with weights 400, 500, 700)
|
||||
- [ ] 1.2 Update `next.config.js` to enable standalone output mode (`output: 'standalone'`)
|
||||
- [ ] 1.3 Verify all existing features work (run dev server, test line drawing, label placement, delete tool)
|
||||
|
||||
## 2. API Extensions for Bulk Operations
|
||||
|
||||
- [ ] 2.1 Read existing `src/app/api/annotations/route.ts` DELETE handler
|
||||
- [ ] 2.2 Add query parameter parsing in DELETE handler (`type` and `all` params from `request.nextUrl.searchParams`)
|
||||
- [ ] 2.3 Add conditional WHERE clause logic: if `type` param exists, filter by `label_type IN (comma-separated types)`, if `all=true` delete everything, else return 400 error
|
||||
- [ ] 2.4 Update return value to include count: `{ success: true, deleted: <count> }` using `result.length` from Drizzle delete operation
|
||||
- [ ] 2.5 Test bulk delete via curl: `curl -X DELETE "http://localhost:3000/api/annotations?type=break_up"` (verify count returned)
|
||||
- [ ] 2.6 Create new file `src/app/api/health/route.ts` with GET handler returning `{ status: 'ok', timestamp: Date.now() }`
|
||||
- [ ] 2.7 Add optional database check in health endpoint: if `?check=db` query param exists, attempt simple `SELECT 1` query and return 503 on failure
|
||||
- [ ] 2.8 Test health endpoint: `curl http://localhost:3000/api/health` (verify 200 response)
|
||||
|
||||
## 3. Hacker Theme - CSS Variables and Tailwind
|
||||
|
||||
- [ ] 3.1 Read current `src/app/globals.css` and note existing CSS variable names
|
||||
- [ ] 3.2 Replace CSS variable values in `:root` with hacker theme colors: `--background: 120 100% 4%` (#0a0e0a), `--foreground: 120 100% 50%` (#00ff41), `--primary: 120 100% 50%`, `--destructive: 348 100% 50%` (#ff0040), `--border: 120 100% 10%` (#003311), map all other variables to theme equivalents
|
||||
- [ ] 3.3 Update body font-family in `globals.css` to: `font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;`
|
||||
- [ ] 3.4 Add custom selection styling in `globals.css`: `::selection { background: rgba(0, 255, 65, 0.4); color: #00ff41; }`
|
||||
- [ ] 3.5 Add custom scrollbar styles in `globals.css`: `::-webkit-scrollbar { width: 8px; }`, `::-webkit-scrollbar-track { background: #003311; }`, `::-webkit-scrollbar-thumb { background: #00ff41; border-radius: 4px; }`
|
||||
- [ ] 3.6 Read `tailwind.config.ts` and locate `theme.extend.colors` object
|
||||
- [ ] 3.7 Add custom colors to Tailwind config: `matrix: '#00ff41'`, `matrixDim: '#00cc33'`, `matrixDark: '#003311'`, `neonRed: '#ff0040'`, `neonCyan: '#00d4ff'`, `neonYellow: '#ffff00'`, `terminal: '#0a0e0a'`, `terminalLight: '#0d110d'`
|
||||
- [ ] 3.8 Add custom fontFamily to Tailwind config: `mono: ['JetBrains Mono', 'Fira Code', 'Courier New', 'monospace']`
|
||||
- [ ] 3.9 Add custom boxShadow to Tailwind config: `'glow-sm': '0 0 8px #00ff41'`, `'glow': '0 0 15px #00ff41, 0 0 30px rgba(0,255,65,0.5)'`, `'glow-lg': '0 0 20px #00ff41, 0 0 40px rgba(0,255,65,0.5)'`
|
||||
- [ ] 3.10 Add keyframes to Tailwind config: `'glow-pulse': { '0%, 100%': { boxShadow: '0 0 15px #00ff41' }, '50%': { boxShadow: '0 0 30px #00ff41, 0 0 50px rgba(0,255,65,0.5)' } }`, `'flicker': { '0%, 100%': { opacity: '1' }, '50%': { opacity: '0.8' } }`
|
||||
- [ ] 3.11 Add animations to Tailwind config: `'glow-pulse': 'glow-pulse 2s ease-in-out infinite'`, `'flicker': 'flicker 0.1s ease-in-out'`
|
||||
- [ ] 3.12 Test theme in browser: verify green text, black background, monospace font on all elements
|
||||
|
||||
## 4. Hacker Theme - Component Styling
|
||||
|
||||
- [ ] 4.1 Read `src/components/Toolbox.tsx` and identify button elements
|
||||
- [ ] 4.2 Update button hover states in Toolbox to add glow effect: add `hover:shadow-glow` class to all Button components
|
||||
- [ ] 4.3 Update active tool buttons in Toolbox: when `activeTool` matches, add `animate-glow-pulse` class
|
||||
- [ ] 4.4 Update input field styling (search input in label list, if exists): add `focus:shadow-glow-sm focus:border-matrix` classes
|
||||
- [ ] 4.5 Read `src/components/ui/button.tsx` and add destructive variant glow: in destructive variant, change hover shadow to red: `hover:shadow-[0_0_15px_#ff0040]`
|
||||
- [ ] 4.6 Test button styling: hover over buttons (should glow green), click to activate tool (should pulse), hover delete button (should glow red)
|
||||
|
||||
## 5. Label Management - State and Selection
|
||||
|
||||
- [ ] 5.1 Read `src/app/page.tsx` and locate existing state declarations (activeTool, selectedColor, etc.)
|
||||
- [ ] 5.2 Add new state in page.tsx: `const [selectedLabelId, setSelectedLabelId] = useState<number | null>(null)`
|
||||
- [ ] 5.3 Pass selectedLabelId and setSelectedLabelId as props to CandleChart component
|
||||
- [ ] 5.4 Read `src/components/CandleChart.tsx` and locate marker creation code (where break_up/break_down markers are added)
|
||||
- [ ] 5.5 Add onClick handler to marker options: in marker creation config, add `onClick: () => props.onLabelSelect?.(annotation.id)`
|
||||
- [ ] 5.6 Update CandleChart props interface to accept `selectedLabelId: number | null`, `onLabelSelect: (id: number) => void`
|
||||
- [ ] 5.7 Wire up onLabelSelect in page.tsx: `onLabelSelect={(id) => setSelectedLabelId(id === selectedLabelId ? null : id)}`
|
||||
- [ ] 5.8 Add visual highlight for selected marker: when rendering markers, if `annotation.id === selectedLabelId`, set marker size to 1.5x and add glow effect (modify marker shape/color options)
|
||||
- [ ] 5.9 Test label selection: click on break_up arrow (should highlight), click again (should deselect), click different marker (should switch selection)
|
||||
|
||||
## 6. Label Management - Keyboard Delete
|
||||
|
||||
- [ ] 6.1 Read `src/app/page.tsx` and locate any existing keyboard event handlers
|
||||
- [ ] 6.2 Add global keyboard handler in page.tsx useEffect: `window.addEventListener('keydown', handleKeyDown)`
|
||||
- [ ] 6.3 Implement handleKeyDown function: if `e.key === 'Delete' || e.key === 'Backspace'`, check if `selectedLabelId !== null`
|
||||
- [ ] 6.4 In handleKeyDown, when Delete pressed with selected label: call `fetch('/api/annotations/' + selectedLabelId, { method: 'DELETE' })`
|
||||
- [ ] 6.5 After successful delete, call chart refresh method and reset `setSelectedLabelId(null)`
|
||||
- [ ] 6.6 Add cleanup in useEffect: `return () => window.removeEventListener('keydown', handleKeyDown)`
|
||||
- [ ] 6.7 Test keyboard delete: select a label marker, press Delete key (marker should disappear), verify database updated
|
||||
|
||||
## 7. Label Management - Sidebar List UI Structure
|
||||
|
||||
- [ ] 7.1 Read `src/components/Toolbox.tsx` and locate the export button section (bottom of component)
|
||||
- [ ] 7.2 Import Collapsible components from shadcn/ui: check if `src/components/ui/collapsible.tsx` exists, if not install via `npx shadcn@latest add collapsible`
|
||||
- [ ] 7.3 Add state in Toolbox for collapsed state: `const [labelsExpanded, setLabelsExpanded] = useState(true)`
|
||||
- [ ] 7.4 Add new section above Export button: wrap in Collapsible component with `open={labelsExpanded}` and `onOpenChange={setLabelsExpanded}`
|
||||
- [ ] 7.5 Create section header button: "Label Annotations (X)" with ChevronDown/ChevronUp icon from lucide-react, clicking toggles `labelsExpanded`
|
||||
- [ ] 7.6 Inside Collapsible.Content, add container div with `className="max-h-96 overflow-y-auto space-y-2 p-2"`
|
||||
- [ ] 7.7 Add count display in header: calculate `breakUpCount` and `breakDownCount` from props.annotations filtered by `label_type`, display as "Break Up: X | Break Down: Y"
|
||||
- [ ] 7.8 Test collapsible: click header (should expand/collapse), verify smooth animation
|
||||
|
||||
## 8. Label Management - Sidebar List Content
|
||||
|
||||
- [ ] 8.1 Update Toolbox props to accept `annotations: Annotation[]`, `selectedLabelId: number | null`, `onLabelSelect: (id: number) => void`, `onLabelDelete: (id: number) => void`
|
||||
- [ ] 8.2 Pass annotations from page.tsx to Toolbox (retrieve from CandleChart or fetch directly in page.tsx)
|
||||
- [ ] 8.3 Filter annotations in Toolbox: `const labelAnnotations = annotations.filter(a => a.label_type === 'break_up' || a.label_type === 'break_down')`
|
||||
- [ ] 8.4 Sort labelAnnotations by timestamp descending: `labelAnnotations.sort((a, b) => b.timestamp - a.timestamp)`
|
||||
- [ ] 8.5 Map over labelAnnotations to render list items: each item in a div with border, padding, cursor-pointer, onClick calls onLabelSelect
|
||||
- [ ] 8.6 Format timestamp in each item: `new Date(timestamp * 1000).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })`
|
||||
- [ ] 8.7 Add colored badge for label type: break_up shows "BREAK UP" in green bg, break_down shows "BREAK DOWN" in red bg
|
||||
- [ ] 8.8 Add delete button (trash icon) to each item: onClick calls `onLabelDelete(annotation.id)` with stopPropagation to prevent selection
|
||||
- [ ] 8.9 Highlight selected item: if `annotation.id === selectedLabelId`, add border-matrix and bg-matrix/10 classes
|
||||
- [ ] 8.10 Handle empty state: if labelAnnotations.length === 0, show message "No labels yet. Click Break Up or Break Down tools to add labels."
|
||||
- [ ] 8.11 Test list rendering: add several labels, verify they appear sorted by time, click to select, click delete button (should remove)
|
||||
|
||||
## 9. Label Management - Search and Filter
|
||||
|
||||
- [ ] 9.1 Add state for search in Toolbox: `const [searchText, setSearchText] = useState('')`
|
||||
- [ ] 9.2 Add state for filter in Toolbox: `const [filterType, setFilterType] = useState<'all' | 'break_up' | 'break_down'>('all')`
|
||||
- [ ] 9.3 Add search input above label list: Input component with placeholder "Search by timestamp...", value={searchText}, onChange={e => setSearchText(e.target.value)}
|
||||
- [ ] 9.4 Add filter dropdown above label list: Select or dropdown with options "All", "Break Up", "Break Down", value={filterType}, onChange={setFilterType}
|
||||
- [ ] 9.5 Apply filter to labelAnnotations: if filterType !== 'all', filter array by `a.label_type === filterType`
|
||||
- [ ] 9.6 Apply search to labelAnnotations: filter by formatted timestamp includes searchText (case-insensitive): `formattedTimestamp.toLowerCase().includes(searchText.toLowerCase())`
|
||||
- [ ] 9.7 Update count display to show filtered count: "Showing X of Y labels"
|
||||
- [ ] 9.8 Test search: type "Feb" in search (should filter to February dates), test filter: select "Break Up" (should show only break_up labels)
|
||||
|
||||
## 10. Label Management - Delete All Labels Button
|
||||
|
||||
- [ ] 10.1 Import Dialog components from shadcn/ui: check if `src/components/ui/dialog.tsx` exists, if not install via `npx shadcn@latest add dialog`
|
||||
- [ ] 10.2 Add state for confirmation dialog: `const [deleteAllDialogOpen, setDeleteAllDialogOpen] = useState(false)`
|
||||
- [ ] 10.3 Add "Delete All Labels" button below search/filter controls: Button with destructive variant, onClick opens dialog, disabled if labelAnnotations.length === 0
|
||||
- [ ] 10.4 Create Dialog component with AlertDialog pattern: AlertDialogTitle "Delete all label annotations?", AlertDialogDescription "This will remove all Break Up and Break Down markers. This cannot be undone."
|
||||
- [ ] 10.5 Add Cancel button in dialog: onClick closes dialog without action
|
||||
- [ ] 10.6 Add Confirm button in dialog: destructive variant, onClick calls API DELETE with type=break_up,break_down
|
||||
- [ ] 10.7 Implement delete all handler: `fetch('/api/annotations?type=break_up,break_down', { method: 'DELETE' })`, on success refresh annotations and close dialog
|
||||
- [ ] 10.8 Show toast notification after delete: use shadcn toast to display "> SUCCESS: Deleted all labels [X items]"
|
||||
- [ ] 10.9 Test delete all: add multiple labels, click Delete All Labels, confirm dialog (all should disappear), verify database emptied
|
||||
|
||||
## 11. Toast Feedback System
|
||||
|
||||
- [ ] 11.1 Install shadcn toast if not present: `npx shadcn@latest add toast`
|
||||
- [ ] 11.2 Read `src/components/ui/toast.tsx` and `src/hooks/use-toast.ts`
|
||||
- [ ] 11.3 Update toast styling in `toast.tsx`: add `font-mono` class, update border color to use matrix theme colors
|
||||
- [ ] 11.4 Import and add Toaster component in `src/app/layout.tsx` (add `<Toaster />` in body)
|
||||
- [ ] 11.5 Create useToast hook wrapper in components that need feedback: import `{ useToast } from '@/hooks/use-toast'`
|
||||
- [ ] 11.6 Add toast calls after successful operations: delete label → `toast({ title: "> SUCCESS", description: "Deleted label [1 item]" })`
|
||||
- [ ] 11.7 Add toast calls for errors: catch API errors → `toast({ title: "> ERROR", description: "Failed to delete [code: 500]", variant: "destructive" })`
|
||||
- [ ] 11.8 Test toast: perform delete operation (should see green success toast), cause error by disconnecting network (should see red error toast)
|
||||
|
||||
## 12. Docker - Dockerfile Creation
|
||||
|
||||
- [ ] 12.1 Create `.dockerignore` file in project root with contents: `node_modules`, `.next`, `.git`, `data/`, `*.md`, `.env*`, `*.log`, `coverage/`, `.DS_Store`
|
||||
- [ ] 12.2 Create `Dockerfile` in project root: start with `FROM node:18-alpine AS builder`
|
||||
- [ ] 12.3 Add build stage instructions: `WORKDIR /app`, `COPY package*.json ./`, `RUN npm ci`, `COPY . .`, `RUN npm run build`
|
||||
- [ ] 12.4 Add production stage: `FROM node:18-alpine`, `WORKDIR /app`
|
||||
- [ ] 12.5 Create non-root user: `RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001`
|
||||
- [ ] 12.6 Copy standalone files: `COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./`
|
||||
- [ ] 12.7 Copy static files: `COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static`
|
||||
- [ ] 12.8 Copy public files: `COPY --from=builder --chown=nextjs:nodejs /app/public ./public`
|
||||
- [ ] 12.9 Create data directory: `RUN mkdir -p /app/data && chown nextjs:nodejs /app/data`
|
||||
- [ ] 12.10 Set environment variables: `ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0`
|
||||
- [ ] 12.11 Set user and expose port: `USER nextjs`, `EXPOSE 3000`
|
||||
- [ ] 12.12 Add healthcheck: `HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1`
|
||||
- [ ] 12.13 Set CMD: `CMD ["node", "server.js"]`
|
||||
- [ ] 12.14 Test Dockerfile: run `docker build -t candle-annotator .` (should build successfully, check image size with `docker images`)
|
||||
|
||||
## 13. Docker - Compose Configuration
|
||||
|
||||
- [ ] 13.1 Create `docker-compose.yml` in project root
|
||||
- [ ] 13.2 Set compose version: `version: '3.8'` (or use no version for latest)
|
||||
- [ ] 13.3 Define service: `services: candle-annotator:`
|
||||
- [ ] 13.4 Add build context: `build: .`
|
||||
- [ ] 13.5 Add port mapping: `ports: - "3000:3000"`
|
||||
- [ ] 13.6 Add volume mount: `volumes: - candle-data:/app/data`
|
||||
- [ ] 13.7 Add environment variables: `environment: - NODE_ENV=production` (optionally use `env_file: .env`)
|
||||
- [ ] 13.8 Add restart policy: `restart: unless-stopped`
|
||||
- [ ] 13.9 Define named volume: `volumes: candle-data:`
|
||||
- [ ] 13.10 Create `.env.example` file with template variables: `NODE_ENV=production`, `PORT=3000`, `DATABASE_PATH=/app/data/candles.db`
|
||||
- [ ] 13.11 Test compose: run `docker-compose up --build` (should start container and be accessible at http://localhost:3000)
|
||||
- [ ] 13.12 Test persistence: upload CSV, add annotations, stop container (`docker-compose down`), restart (`docker-compose up`), verify data persists
|
||||
|
||||
## 14. Documentation Updates
|
||||
|
||||
- [ ] 14.1 Read existing `DEPLOYMENT.md` file
|
||||
- [ ] 14.2 Add "Docker Deployment" section with prerequisites (Docker, docker-compose installed)
|
||||
- [ ] 14.3 Add build instructions: `docker-compose build` or `docker build -t candle-annotator .`
|
||||
- [ ] 14.4 Add run instructions: `docker-compose up -d` for detached mode, `docker-compose logs -f` to view logs
|
||||
- [ ] 14.5 Add environment setup: copy `.env.example` to `.env`, edit variables as needed
|
||||
- [ ] 14.6 Add volume management: explain data persists in `candle-data` volume, show backup command `docker cp candle-annotator:/app/data/candles.db ./backup.db`
|
||||
- [ ] 14.7 Add troubleshooting section: port conflicts (change PORT in .env), permission errors (check volume ownership), build failures (clear cache with `docker-compose build --no-cache`)
|
||||
- [ ] 14.8 Add update procedure: `git pull`, `docker-compose down`, `docker-compose up --build -d`
|
||||
- [ ] 14.9 Update `README.md` with Docker quickstart: add section "Docker Deployment" linking to DEPLOYMENT.md
|
||||
- [ ] 14.10 Update `CLAUDE_DESCRIPTION.md` with new features: label management sidebar, hacker theme, Docker support
|
||||
|
||||
## 15. Integration Testing and Validation
|
||||
|
||||
- [ ] 15.1 Test full label workflow: create 10 break_up and 10 break_down labels using chart tools
|
||||
- [ ] 15.2 Test label selection: click markers on chart (should highlight), verify selection state reflected in sidebar list
|
||||
- [ ] 15.3 Test sidebar list: verify all 20 labels appear sorted by timestamp, click list item (should highlight marker on chart)
|
||||
- [ ] 15.4 Test search: type partial timestamp (should filter list), clear search (should show all)
|
||||
- [ ] 15.5 Test filter: select "Break Up" filter (should show only 10 items), select "Break Down" (should show other 10), select "All" (should show 20)
|
||||
- [ ] 15.6 Test delete individual label: click trash icon on list item (should remove from list and chart), verify database updated
|
||||
- [ ] 15.7 Test delete all labels: click "Delete All Labels" button, confirm dialog (all 20 should disappear), verify success toast appears
|
||||
- [ ] 15.8 Test keyboard delete: create label, click to select, press Delete key (should remove), create another, press Backspace (should remove)
|
||||
- [ ] 15.9 Test existing line features: draw lines (should still work), select lines (should still work), drag endpoints (should still work), delete lines (should still work)
|
||||
- [ ] 15.10 Test theme visual appearance: verify monospace font on all text, green color scheme throughout, glow effects on hover, borders and shadows match specs
|
||||
- [ ] 15.11 Test health endpoint: `curl http://localhost:3000/api/health` (should return 200 with { status: 'ok' }), test with db check `curl http://localhost:3000/api/health?check=db`
|
||||
- [ ] 15.12 Test Docker deployment: build image, start container, access http://localhost:3000, upload CSV, create annotations, verify persistence after restart
|
||||
- [ ] 15.13 Test contrast and accessibility: use browser dev tools to check contrast ratios (should meet WCAG AA 4.5:1), test with prefers-reduced-motion (animations should disable)
|
||||
- [ ] 15.14 Verify no breaking changes: compare current functionality with pre-change state, confirm all original features work identically
|
||||
- [ ] 15.15 Final smoke test: clean database, fresh Docker build, complete annotation workflow from CSV upload to export, verify output CSV includes all annotations
|
||||
|
||||
## 16. Commit and Cleanup
|
||||
|
||||
- [ ] 16.1 Review all changed files with `git status` and `git diff`
|
||||
- [ ] 16.2 Commit label management changes: `git add src/app/page.tsx src/components/CandleChart.tsx src/components/Toolbox.tsx` and commit with message "feat: implement label management with sidebar list and search"
|
||||
- [ ] 16.3 Commit API changes: `git add src/app/api/` and commit with message "feat: add bulk delete API and health endpoint"
|
||||
- [ ] 16.4 Commit theme changes: `git add src/app/globals.css tailwind.config.ts src/app/layout.tsx src/components/ui/` and commit with message "feat: implement hacker theme with matrix colors and monospace fonts"
|
||||
- [ ] 16.5 Commit Docker files: `git add Dockerfile docker-compose.yml .dockerignore .env.example next.config.js` and commit with message "feat: add Docker deployment with multi-stage build"
|
||||
- [ ] 16.6 Commit documentation: `git add README.md DEPLOYMENT.md CLAUDE_DESCRIPTION.md` and commit with message "docs: update deployment and feature documentation"
|
||||
- [ ] 16.7 Tag release: `git tag v2.0.0 -m "Release: Label management, Docker deployment, hacker theme"`
|
||||
- [ ] 16.8 Push to remote: `git push origin master --tags`
|
||||
Loading…
Add table
Add a link
Reference in a new issue