diff --git a/openspec/specs/backend-api/spec.md b/openspec/specs/backend-api/spec.md index f3cb2be..6d90e49 100644 --- a/openspec/specs/backend-api/spec.md +++ b/openspec/specs/backend-api/spec.md @@ -1,46 +1,58 @@ ## 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. +The system SHALL provide a `POST /api/upload` endpoint that accepts a CSV file via multipart form data. The endpoint SHALL create a new chart (named from the uploaded filename without extension), parse the CSV using papaparse, validate the format, and insert all candle records into the `candles` table with the new chart's `chart_id` within a single database transaction. On success, the endpoint SHALL return a JSON response with the chart `id`, chart `name`, and the count of inserted records. On failure, it SHALL return an appropriate error status and message without creating a chart. #### Scenario: Successful upload -- **WHEN** a valid CSV file is sent to POST /api/upload -- **THEN** endpoint returns `{ "success": true, "count": }` with HTTP 200 +- **WHEN** a valid CSV file named "BTC-1H.csv" is sent to POST /api/upload +- **THEN** endpoint creates a chart named "BTC-1H", inserts candles with that chart_id, and returns `{ "success": true, "count": , "chart": { "id": , "name": "BTC-1H" } }` 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": "" }` with HTTP 400 +- **THEN** endpoint returns `{ "error": "" }` with HTTP 400 and does not create a chart #### Scenario: No file provided - **WHEN** POST /api/upload is called without a file - **THEN** endpoint returns `{ "error": "No file provided" }` with HTTP 400 +#### Scenario: Duplicate filename +- **WHEN** a CSV named "BTC-1H.csv" is uploaded and a chart named "BTC-1H" already exists +- **THEN** endpoint creates a chart named "BTC-1H-2" (or next available suffix) and inserts candles under that chart + ### 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`. +The system SHALL provide a `GET /api/annotations` endpoint that accepts an optional `chartId` query parameter. When `chartId` is provided, the endpoint SHALL return only annotations belonging to that chart. When `chartId` is omitted, the endpoint SHALL return annotations for the most recently created chart. Each annotation object SHALL include: `id`, `chart_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: Fetch annotations for specific chart +- **WHEN** GET /api/annotations?chartId=3 is called +- **THEN** endpoint returns a JSON array of annotations where chart_id equals 3, with HTTP 200 -#### Scenario: No annotations exist -- **WHEN** GET /api/annotations is called and no annotations are in the database +#### Scenario: Fetch annotations without chartId +- **WHEN** GET /api/annotations is called without a chartId parameter +- **THEN** endpoint returns annotations for the most recently created chart with HTTP 200 + +#### Scenario: No annotations exist for chart +- **WHEN** GET /api/annotations?chartId=3 is called and no annotations exist for chart 3 - **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`. +The system SHALL provide a `POST /api/annotations` endpoint that accepts a JSON body with fields: `timestamp` (required, integer), `label_type` (required, string), `chart_id` (required, integer), and `geometry` (optional, object). The endpoint SHALL validate the input, verify the chart exists, 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 +- **WHEN** POST /api/annotations is called with `{ "timestamp": 1700000000, "label_type": "break_up", "chart_id": 3 }` +- **THEN** endpoint saves the annotation with chart_id 3 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 +- **WHEN** POST /api/annotations is called with `{ "timestamp": 1700000000, "label_type": "line", "chart_id": 3, "geometry": { "startTime": 1700000000, "startPrice": 1.05, "endTime": 1700100000, "endPrice": 1.06 } }` +- **THEN** endpoint saves the annotation with chart_id 3 and serialized geometry JSON, returns the created object with HTTP 201 #### Scenario: Invalid annotation data -- **WHEN** POST /api/annotations is called with missing required fields +- **WHEN** POST /api/annotations is called with missing required fields (timestamp, label_type, or chart_id) - **THEN** endpoint returns `{ "error": "" }` with HTTP 400 +#### Scenario: Annotation for non-existent chart +- **WHEN** POST /api/annotations is called with a chart_id that does not exist +- **THEN** endpoint returns `{ "error": "Chart not found" }` with HTTP 404 + ### 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. @@ -53,23 +65,27 @@ The system SHALL provide a `DELETE /api/annotations/[id]` endpoint that removes - **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. +The system SHALL provide a `GET /api/export` endpoint that accepts an optional `chartId` query parameter. When `chartId` is provided, the endpoint SHALL export only annotations for that chart. When `chartId` is omitted, the endpoint SHALL export annotations for the most recently created chart. The CSV SHALL have columns: `timestamp`, `label_type`, `price`. 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 for specific chart +- **WHEN** GET /api/export?chartId=3 is called and annotations exist for chart 3 +- **THEN** endpoint returns a CSV file download with only annotations belonging to chart 3 -#### 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 +#### Scenario: Export without chartId +- **WHEN** GET /api/export is called without a chartId parameter +- **THEN** endpoint exports annotations for the most recently created chart ### 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`. +The system SHALL provide a `GET /api/candles` endpoint that accepts an optional `chartId` query parameter. When `chartId` is provided, the endpoint SHALL return only candles belonging to that chart. When `chartId` is omitted, the endpoint SHALL return candles for the most recently created chart. Results SHALL be 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: Fetch candles for specific chart +- **WHEN** GET /api/candles?chartId=3 is called +- **THEN** endpoint returns a JSON array of candle objects where chart_id equals 3, 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 +#### Scenario: Fetch candles without chartId +- **WHEN** GET /api/candles is called without a chartId parameter +- **THEN** endpoint returns candles for the most recently created chart with HTTP 200 + +#### Scenario: No candles exist for chart +- **WHEN** GET /api/candles?chartId=3 is called and no candles exist for chart 3 - **THEN** endpoint returns an empty JSON array `[]` with HTTP 200 diff --git a/openspec/specs/chart-canvas/spec.md b/openspec/specs/chart-canvas/spec.md index 850b874..56fb318 100644 --- a/openspec/specs/chart-canvas/spec.md +++ b/openspec/specs/chart-canvas/spec.md @@ -1,14 +1,14 @@ ## 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 candlestick visuals using a black and white color scheme. Bullish candles SHALL have a white interior with black outline and black wicks. Bearish candles SHALL be completely black (black fill and black wicks). The chart SHALL be a client-side React component. +The system SHALL render candle data as a candlestick chart using the `lightweight-charts` library (v4). The chart MUST display OHLC data with candlestick visuals using a black and white color scheme. Bullish candles SHALL have a white interior with black outline and black wicks. Bearish candles SHALL be completely black (black fill and black wicks). The chart SHALL be a client-side React component. The chart SHALL fetch and display candles scoped to the active chart by passing `chartId` to the `GET /api/candles` endpoint. -#### 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: Chart renders candle data for active chart +- **WHEN** an active chart is selected and candle data exists for that chart +- **THEN** the chart fetches candles via `GET /api/candles?chartId=` and renders only that chart's candles #### Scenario: Empty state -- **WHEN** no candle data exists in the database +- **WHEN** no charts exist or the active chart has no candle data - **THEN** the chart area displays an empty chart with a prompt to upload CSV data #### Scenario: Bullish candle appearance @@ -19,6 +19,10 @@ The system SHALL render candle data as a candlestick chart using the `lightweigh - **WHEN** a candle's close price is lower than its open price (bearish) - **THEN** the candle displays as completely black (black fill and black wick) +#### Scenario: Chart switches when active chart changes +- **WHEN** user selects a different chart from the chart selector +- **THEN** the chart clears existing data, fetches candles for the newly selected chart, and renders the new dataset + ### 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. @@ -45,16 +49,20 @@ The chart SHALL use a dark color scheme consistent with the application's Slate- - **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. +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. Markers SHALL be scoped to the active chart by fetching annotations via `GET /api/annotations?chartId=`. #### Scenario: Break Up marker display -- **WHEN** a Break Up annotation exists for a candle timestamp +- **WHEN** a Break Up annotation exists for a candle timestamp in the active chart - **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 +- **WHEN** a Break Down annotation exists for a candle timestamp in the active chart - **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 +- **WHEN** user adds or deletes an annotation on the active chart - **THEN** chart markers update immediately without requiring a page reload + +#### Scenario: Markers refresh on chart switch +- **WHEN** user switches to a different chart +- **THEN** markers from the previous chart are cleared and markers for the new chart's annotations are loaded diff --git a/openspec/specs/chart-management/spec.md b/openspec/specs/chart-management/spec.md new file mode 100644 index 0000000..af64b94 --- /dev/null +++ b/openspec/specs/chart-management/spec.md @@ -0,0 +1,85 @@ +# chart-management + +## Purpose + +Managing multiple named charts — creating, selecting, listing, and deleting chart datasets. Includes the chart selector UI component and the charts data model. + +## Requirements + +### Requirement: Charts database table +The system SHALL store chart datasets in a `charts` table with columns: `id` (integer primary key, auto-increment), `name` (text, unique), `created_at` (integer, Unix timestamp). Each chart represents a distinct uploaded CSV dataset. + +#### Scenario: Schema structure +- **WHEN** the database is initialized +- **THEN** the `charts` table exists with all required columns and constraints + +#### Scenario: Unique chart names +- **WHEN** a chart is created with a name that already exists +- **THEN** the system appends a numeric suffix (e.g., "btc-daily-2") to ensure uniqueness + +### Requirement: Chart selector in sidebar +The system SHALL display a chart selector component in the sidebar, positioned between the app title/header section and the file upload section. The selector SHALL show the name of the currently active chart and allow the user to switch between all available charts. + +#### Scenario: Chart selector renders with charts +- **WHEN** one or more charts exist in the database +- **THEN** the sidebar displays a selector showing the active chart name, with a dropdown listing all available charts sorted by creation date (newest first) + +#### Scenario: Chart selector with no charts +- **WHEN** no charts exist in the database +- **THEN** the selector displays a placeholder message "No charts — upload a CSV to get started" + +#### Scenario: Switch active chart +- **WHEN** user selects a different chart from the selector dropdown +- **THEN** the system updates `activeChartId` state, fetches candles and annotations for the selected chart, and re-renders the chart and sidebar label list + +#### Scenario: Active chart persistence on page load +- **WHEN** the page loads and charts exist +- **THEN** the system selects the most recently created chart as the active chart + +### Requirement: Delete chart +The system SHALL allow users to delete a chart and all its associated candles and annotations. + +#### Scenario: Delete chart with confirmation +- **WHEN** user clicks the delete button next to a chart in the selector +- **THEN** the system displays a confirmation dialog: "Delete chart '{name}' and all its candles and annotations? This cannot be undone." + +#### Scenario: Confirm chart deletion +- **WHEN** user confirms chart deletion +- **THEN** the system deletes the chart, all its candles, and all its annotations from the database, removes the chart from the selector, and switches to the next available chart (or shows empty state if none remain) + +#### Scenario: Cancel chart deletion +- **WHEN** user cancels chart deletion +- **THEN** no data is deleted and the dialog closes + +#### Scenario: Delete last chart +- **WHEN** user deletes the only remaining chart +- **THEN** the system shows the empty state with no chart selected and prompts the user to upload a CSV + +### Requirement: List charts API +The system SHALL provide a `GET /api/charts` endpoint that returns all charts as a JSON array, ordered by `created_at` descending. Each chart object SHALL include: `id`, `name`, `created_at`. + +#### Scenario: Fetch all charts +- **WHEN** GET /api/charts is called +- **THEN** endpoint returns a JSON array of chart objects ordered by created_at descending with HTTP 200 + +#### Scenario: No charts exist +- **WHEN** GET /api/charts is called and no charts exist +- **THEN** endpoint returns an empty JSON array `[]` with HTTP 200 + +### Requirement: Delete chart API +The system SHALL provide a `DELETE /api/charts/[id]` endpoint that deletes a chart and all its associated candles and annotations within a single transaction. + +#### Scenario: Delete existing chart +- **WHEN** DELETE /api/charts/5 is called and chart with id 5 exists +- **THEN** endpoint deletes the chart, all candles with chart_id 5, and all annotations with chart_id 5, then returns `{ "success": true }` with HTTP 200 + +#### Scenario: Delete non-existent chart +- **WHEN** DELETE /api/charts/999 is called and no chart with that id exists +- **THEN** endpoint returns `{ "error": "Chart not found" }` with HTTP 404 + +### Requirement: Auto-select new chart after upload +The system SHALL automatically switch to the newly created chart after a successful CSV upload. + +#### Scenario: Upload creates and selects chart +- **WHEN** user uploads a CSV and the upload succeeds +- **THEN** the system creates a new chart, inserts candle data for that chart, adds the chart to the selector, and sets it as the active chart diff --git a/openspec/specs/data-ingestion/spec.md b/openspec/specs/data-ingestion/spec.md index 777e9cf..eaca602 100644 --- a/openspec/specs/data-ingestion/spec.md +++ b/openspec/specs/data-ingestion/spec.md @@ -1,27 +1,27 @@ ## 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). +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). Uploading a CSV SHALL create a new chart (named from the filename without extension) and insert all candle rows associated with that chart, rather than replacing existing data. #### 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 +- **THEN** system creates a new chart named from the filename (without .csv extension), parses all rows, and stores them in the `candles` table with the new chart's `chart_id` #### 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 +- **THEN** system stores the timestamps as integers in the database with the correct `chart_id` 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 +- **THEN** system converts dates to Unix timestamps and stores them in the database with the correct `chart_id` #### 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 +- **THEN** system displays an error message describing the issue, does not create a chart, 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) +#### Scenario: Duplicate filename upload +- **WHEN** user uploads a CSV whose filename (without extension) matches an existing chart name +- **THEN** system appends a numeric suffix to the chart name (e.g., "btc-daily-2") and creates a new chart with the suffixed name ### 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. @@ -35,8 +35,12 @@ The system SHALL use the `papaparse` library for CSV parsing. Parsing SHALL hand - **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. +The system SHALL store candle data in a `candles` table with columns: `id` (integer primary key, auto-increment), `chart_id` (integer, foreign key to `charts.id`, NOT NULL), `time` (integer, Unix timestamp), `open` (real), `high` (real), `low` (real), `close` (real). The table MUST have a composite unique constraint on `(chart_id, time)`. #### Scenario: Schema structure - **WHEN** the database is initialized -- **THEN** the `candles` table exists with all required columns and constraints +- **THEN** the `candles` table exists with all required columns including `chart_id` and the composite unique constraint on `(chart_id, time)` + +#### Scenario: Same timestamp across different charts +- **WHEN** two different charts have candles with the same Unix timestamp +- **THEN** both records are stored successfully because the unique constraint is per-chart diff --git a/openspec/specs/label-management/spec.md b/openspec/specs/label-management/spec.md index a246b5b..428b82a 100644 --- a/openspec/specs/label-management/spec.md +++ b/openspec/specs/label-management/spec.md @@ -39,28 +39,32 @@ The system SHALL allow users to delete the currently selected label annotation u - **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. +The Toolbox SHALL display a collapsible section showing all label annotations for the active chart 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 +- **WHEN** Toolbox renders with an active chart selected +- **THEN** system displays "Label Annotations" section below the annotation tools with collapse/expand toggle button, showing only annotations belonging to the active chart #### 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 +- **THEN** system displays scrollable list of label annotations for the active chart 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" +- **THEN** system hides the label list but shows count summary "Labels: X break_up, Y break_down" for the active chart #### Scenario: Empty label list -- **WHEN** no label annotations exist in database +- **WHEN** no label annotations exist for the active chart - **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 +#### Scenario: Label list updates on chart switch +- **WHEN** user switches to a different chart via the chart selector +- **THEN** the label list clears and reloads with annotations belonging to the newly selected chart + ### 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. @@ -111,30 +115,34 @@ The Toolbox label section SHALL provide search and filter controls. - **THEN** system shows labels that match both criteria (AND logic) ### Requirement: Label count display -The Toolbox SHALL display counts of each label type. +The Toolbox SHALL display counts of each label type for the active chart. #### 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 +- **WHEN** Toolbox renders and labels exist for the active chart +- **THEN** system displays count summary "Break Up: X | Break Down: Y" at top of label section, reflecting only the active chart's annotations #### Scenario: Zero labels -- **WHEN** no labels exist in database +- **WHEN** no labels exist for the active chart - **THEN** count summary displays "Break Up: 0 | Break Down: 0" #### Scenario: Count updates after delete -- **WHEN** user deletes a label +- **WHEN** user deletes a label from the active chart - **THEN** count summary updates immediately to reflect the new totals +#### Scenario: Counts update on chart switch +- **WHEN** user switches to a different chart +- **THEN** count summary updates to reflect the new chart's label counts + ### Requirement: Delete all labels with confirmation -The system SHALL provide a "Delete All Labels" button with confirmation dialog. +The system SHALL provide a "Delete All Labels" button with confirmation dialog. The deletion SHALL only affect labels belonging to the active chart. #### 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 +- **THEN** system displays confirmation dialog with message "Delete all label annotations for this chart (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 +- **THEN** system deletes only label annotations belonging to the active chart, 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 diff --git a/openspec/specs/ui-shell/spec.md b/openspec/specs/ui-shell/spec.md index 9049e02..418c37e 100644 --- a/openspec/specs/ui-shell/spec.md +++ b/openspec/specs/ui-shell/spec.md @@ -26,15 +26,15 @@ The main content area SHALL occupy the full viewport width minus the sidebar wid - **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. +The application SHALL provide a CSV file upload interface accessible from the sidebar. The upload component SHALL trigger the POST /api/upload endpoint. On success, the component SHALL add the newly created chart to the chart selector and set it as the active chart, triggering the chart and annotation data to refresh for the new chart. #### 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 +- **THEN** the file is sent to the upload API, a new chart is created, the chart selector updates to include the new chart, and the new chart becomes active with its candles displayed #### Scenario: Upload error display - **WHEN** the upload API returns an error -- **THEN** the UI displays the error message to the user +- **THEN** the UI displays the error message to the user and no chart is created ### 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. @@ -42,3 +42,18 @@ The application SHALL provide an "Export" button that triggers a download of the #### Scenario: Export annotations - **WHEN** user clicks the "Export" button - **THEN** a CSV file named "annotations.csv" downloads containing all annotation data + +### Requirement: Theme-aware Manage Annotation Types link +The "Manage Annotation Types" link in the sidebar header SHALL use theme-aware styling consistent with the rest of the application. The link SHALL NOT use hardcoded color values. It SHALL use `text-muted-foreground` and `hover:text-foreground` Tailwind classes to respect both light and dark themes. + +#### Scenario: Link styling in dark mode +- **WHEN** the application is in dark mode +- **THEN** the "Manage Annotation Types" link uses muted foreground color and brightens on hover, consistent with the dark theme + +#### Scenario: Link styling in light mode +- **WHEN** the application is in light mode +- **THEN** the "Manage Annotation Types" link uses muted foreground color and darkens on hover, consistent with the light theme + +#### Scenario: Link visual consistency +- **WHEN** the sidebar renders +- **THEN** the "Manage Annotation Types" link does not use underline styling and visually matches other sidebar text elements