From 92abab53163f3d9ebe34312b6884f7025f43b9cd Mon Sep 17 00:00:00 2001 From: Marko Djordjevic Date: Sun, 15 Feb 2026 10:16:05 +0100 Subject: [PATCH] span annotations are working --- .../.openspec.yaml | 0 .../2026-02-15-span-annotation}/design.md | 0 .../2026-02-15-span-annotation}/proposal.md | 0 .../specs/span-annotation/spec.md | 0 .../2026-02-15-span-annotation}/tasks.md | 0 openspec/specs/span-annotation/spec.md | 182 ++++++++++++++++++ 6 files changed, 182 insertions(+) rename openspec/changes/{span-annotation => archive/2026-02-15-span-annotation}/.openspec.yaml (100%) rename openspec/changes/{span-annotation => archive/2026-02-15-span-annotation}/design.md (100%) rename openspec/changes/{span-annotation => archive/2026-02-15-span-annotation}/proposal.md (100%) rename openspec/changes/{span-annotation => archive/2026-02-15-span-annotation}/specs/span-annotation/spec.md (100%) rename openspec/changes/{span-annotation => archive/2026-02-15-span-annotation}/tasks.md (100%) create mode 100644 openspec/specs/span-annotation/spec.md diff --git a/openspec/changes/span-annotation/.openspec.yaml b/openspec/changes/archive/2026-02-15-span-annotation/.openspec.yaml similarity index 100% rename from openspec/changes/span-annotation/.openspec.yaml rename to openspec/changes/archive/2026-02-15-span-annotation/.openspec.yaml diff --git a/openspec/changes/span-annotation/design.md b/openspec/changes/archive/2026-02-15-span-annotation/design.md similarity index 100% rename from openspec/changes/span-annotation/design.md rename to openspec/changes/archive/2026-02-15-span-annotation/design.md diff --git a/openspec/changes/span-annotation/proposal.md b/openspec/changes/archive/2026-02-15-span-annotation/proposal.md similarity index 100% rename from openspec/changes/span-annotation/proposal.md rename to openspec/changes/archive/2026-02-15-span-annotation/proposal.md diff --git a/openspec/changes/span-annotation/specs/span-annotation/spec.md b/openspec/changes/archive/2026-02-15-span-annotation/specs/span-annotation/spec.md similarity index 100% rename from openspec/changes/span-annotation/specs/span-annotation/spec.md rename to openspec/changes/archive/2026-02-15-span-annotation/specs/span-annotation/spec.md diff --git a/openspec/changes/span-annotation/tasks.md b/openspec/changes/archive/2026-02-15-span-annotation/tasks.md similarity index 100% rename from openspec/changes/span-annotation/tasks.md rename to openspec/changes/archive/2026-02-15-span-annotation/tasks.md diff --git a/openspec/specs/span-annotation/spec.md b/openspec/specs/span-annotation/spec.md new file mode 100644 index 0000000..5349825 --- /dev/null +++ b/openspec/specs/span-annotation/spec.md @@ -0,0 +1,182 @@ +## ADDED Requirements + +### Requirement: Span annotation data model +The system SHALL store span annotations in a `span_annotations` database table with columns: `id` (integer primary key, auto-increment), `chart_id` (integer, FK to charts.id), `start_time` (integer, Unix timestamp of first candle), `end_time` (integer, Unix timestamp of last candle), `label` (text, pattern name referencing span_label_types.name), `confidence` (integer, nullable, 1-5 scale), `outcome` (text, nullable, one of 'win'|'loss'|'breakeven'), `notes` (text, nullable), `sub_spans` (text, nullable, JSON array), `color` (text, default '#2196F3'), `created_at` (integer, Unix timestamp). + +#### Scenario: Schema structure +- **WHEN** the database is initialized or migrated +- **THEN** the `span_annotations` table exists with all required columns and the foreign key constraint on `chart_id` + +#### Scenario: start_time before end_time +- **WHEN** a span annotation is created where the user clicked the later candle first +- **THEN** the system SHALL swap the values so `start_time` is always less than or equal to `end_time` + +### Requirement: Two-click span creation +When the "span" tool is active, the system SHALL implement a two-click interaction to define a span. The first click sets the start candle. The second click sets the end candle. Both clicks SHALL snap to the nearest candle timestamp from the loaded candle data using `chart.timeScale().coordinateToTime()` followed by finding the nearest candle in the data array. + +#### Scenario: First click sets start candle +- **WHEN** "span" tool is active and user clicks on the chart +- **THEN** the system identifies the nearest candle to the click position and highlights it as the span start point + +#### Scenario: Second click completes span selection +- **WHEN** "span" tool is active and user has already clicked a start candle, then clicks a second position +- **THEN** the system identifies the nearest candle to the second click and defines the span range from start to end candle + +#### Scenario: Clicking same candle twice +- **WHEN** user clicks the same candle for both start and end +- **THEN** the system creates a single-candle span (start_time equals end_time) + +#### Scenario: Clicking end before start +- **WHEN** user clicks a candle that is earlier in time than the start candle +- **THEN** the system SHALL swap start and end so the span is always in chronological order + +### Requirement: Live preview rectangle during span selection +After the first click, the system SHALL display a preview rectangle that stretches from the start candle to the candle currently under the cursor. The preview rectangle height SHALL cover the price range (min low to max high) of all candles within the preview span. The preview SHALL update as the mouse moves and SHALL snap to candle positions. + +#### Scenario: Preview follows cursor +- **WHEN** user has clicked the first candle and moves the mouse +- **THEN** a semi-transparent preview rectangle renders from the start candle to the candle nearest to the cursor, with height spanning the high/low range of candles in that range + +#### Scenario: Preview updates on mouse move +- **WHEN** cursor moves to a different candle position +- **THEN** the preview rectangle resizes to include the new candle range with updated price bounds + +#### Scenario: Preview disappears on cancel +- **WHEN** user presses Escape during span selection (after first click) +- **THEN** the preview rectangle disappears and the span selection is cancelled without saving + +### Requirement: Label assignment popover +After the second click completes the span selection, the system SHALL display a popover near the click position containing: a label dropdown populated from `span_label_types`, an optional confidence slider (1-5), an optional outcome select (win/loss/breakeven/none), an optional notes textarea, and Save/Cancel buttons. + +#### Scenario: Popover appears after span selection +- **WHEN** user completes the two-click span selection +- **THEN** a popover appears near the end-click position with label dropdown, confidence, outcome, notes fields, and Save/Cancel buttons + +#### Scenario: Save span annotation +- **WHEN** user selects a label and clicks Save in the popover +- **THEN** the system sends a POST request to create the span annotation, renders the span rectangle on the chart, updates the sidebar list, and closes the popover + +#### Scenario: Cancel span annotation +- **WHEN** user clicks Cancel in the popover or presses Escape +- **THEN** the popover closes, the preview rectangle disappears, and no annotation is saved + +#### Scenario: Label is required +- **WHEN** user clicks Save without selecting a label +- **THEN** the Save button SHALL be disabled or the system SHALL show a validation message requiring a label + +### Requirement: Span rectangle rendering via ISeriesPrimitive +The system SHALL render saved span annotations as semi-transparent colored rectangles on the chart using the lightweight-charts `ISeriesPrimitive` API. Each span annotation SHALL have one `SpanRectanglePrimitive` instance attached to the candlestick series via `series.attachPrimitive()`. The rectangle SHALL be defined by data coordinates: top-left `{time: start_time, price: max_high}` and bottom-right `{time: end_time, price: min_low}` where max_high and min_low are the highest high and lowest low of candles within the span range. + +#### Scenario: Rectangle renders for saved span +- **WHEN** a span annotation exists for the active chart +- **THEN** a semi-transparent colored rectangle renders behind the candles in the span range, with color from the span's label type configuration + +#### Scenario: Rectangle z-order +- **WHEN** span rectangles render on the chart +- **THEN** they SHALL render behind (below) the candlestick series so candles remain fully visible on top + +#### Scenario: Rectangle updates on zoom/pan +- **WHEN** user zooms or pans the chart +- **THEN** span rectangles automatically reposition and resize to stay aligned with their candle range (handled natively by the ISeriesPrimitive lifecycle) + +#### Scenario: Label tag display +- **WHEN** a span rectangle renders on the chart +- **THEN** a small label tag showing the pattern name SHALL be displayed above the rectangle + +#### Scenario: Rectangle color from label config +- **WHEN** a span annotation has label "bull_flag" and the bull_flag label type has color "#4CAF50" +- **THEN** the rectangle renders with fill color "#4CAF50" at reduced opacity (semi-transparent) + +### Requirement: Span selection on chart +The system SHALL allow users to select an existing span annotation by clicking within its rectangle on the chart. + +#### Scenario: Click to select span +- **WHEN** user clicks within a span rectangle on the chart (detected via `hitTest()`) +- **THEN** the system sets the selectedSpanId state, highlights the span rectangle (e.g., thicker border or increased opacity), and scrolls to it in the sidebar list + +#### Scenario: Click to deselect span +- **WHEN** user clicks on an already-selected span rectangle +- **THEN** the system deselects it by clearing selectedSpanId and removing the highlight + +#### Scenario: Click outside any span +- **WHEN** user clicks on the chart outside any span rectangle while a span is selected +- **THEN** the system deselects the currently selected span + +### Requirement: Span editing +The system SHALL allow users to edit an existing span annotation's metadata (label, confidence, outcome, notes). + +#### Scenario: Open edit popover +- **WHEN** user double-clicks on an existing span rectangle or selects it and presses Enter +- **THEN** a popover appears pre-populated with the span's current label, confidence, outcome, and notes + +#### Scenario: Save edited span +- **WHEN** user modifies fields in the edit popover and clicks Save +- **THEN** the system sends a PATCH request to update the span annotation, re-renders the rectangle with any color changes, and updates the sidebar list + +#### Scenario: Cancel edit +- **WHEN** user clicks Cancel or presses Escape in the edit popover +- **THEN** changes are discarded and the popover closes + +### Requirement: Span deletion +The system SHALL allow users to delete span annotations via keyboard shortcut, sidebar button, or the delete tool. + +#### Scenario: Delete selected span with keyboard +- **WHEN** a span is selected (selectedSpanId is set) and user presses Delete or Backspace +- **THEN** the system sends a DELETE request, removes the rectangle primitive from the chart, clears the selection, and updates the sidebar list + +#### Scenario: Delete span from sidebar +- **WHEN** user clicks the trash icon on a span entry in the sidebar list +- **THEN** the system sends a DELETE request, removes the rectangle from the chart, and updates the sidebar list + +#### Scenario: Delete span with delete tool +- **WHEN** the "delete" tool is active and user clicks within a span rectangle +- **THEN** the system deletes that span annotation via API, removes the rectangle, and updates the sidebar + +### Requirement: Span annotation sidebar list +The system SHALL display a "Span Annotations" section in the sidebar showing all span annotations for the active chart. + +#### Scenario: Display span list +- **WHEN** span annotations exist for the active chart +- **THEN** the sidebar displays a scrollable list of span annotations sorted by start_time (newest first), with each entry showing: start/end time range, label name with colored badge, and delete button + +#### Scenario: Empty span list +- **WHEN** no span annotations exist for the active chart +- **THEN** the section displays "No span annotations yet. Use the Span tool to select candle ranges." + +#### Scenario: Click span in list to select +- **WHEN** user clicks a span entry in the sidebar list +- **THEN** the system sets selectedSpanId, highlights the corresponding rectangle on the chart, and scrolls the chart to center on the span's time range + +#### Scenario: Selected span highlight in list +- **WHEN** a span is selected +- **THEN** the corresponding entry in the sidebar list has a highlighted background and border + +#### Scenario: Span list updates on chart switch +- **WHEN** user switches to a different chart +- **THEN** the span list clears and reloads with span annotations belonging to the newly selected chart + +### Requirement: Span count display +The sidebar SHALL display a count summary of span annotations grouped by label type. + +#### Scenario: Display span counts +- **WHEN** span annotations exist for the active chart +- **THEN** the sidebar displays counts per label type (e.g., "Bull Flag: 3 | Bear Flag: 2") + +#### Scenario: Counts update after changes +- **WHEN** user adds or deletes a span annotation +- **THEN** the count summary updates immediately + +### Requirement: Hotkey label assignment +When the span tool is active and the user has completed a two-click selection, pressing a configured hotkey SHALL assign the corresponding label and save the span immediately without showing the popover. + +#### Scenario: Hotkey assigns label after span selection +- **WHEN** span tool is active, user has selected start and end candles, and presses hotkey "1" (mapped to "bull_flag") +- **THEN** the system saves the span with label "bull_flag", default confidence/outcome/notes, renders the rectangle, and updates the sidebar + +#### Scenario: Hotkey ignored when no span selected +- **WHEN** span tool is active but no span range has been selected yet, and user presses a label hotkey +- **THEN** the system takes no action + +#### Scenario: Hotkey ignored when span tool is not active +- **WHEN** a different tool (e.g., break_up, line) is active and user presses a span label hotkey +- **THEN** the system takes no action (hotkeys only work when span tool is active)