candle-annotator/openspec/changes/archive/2026-02-15-span-annotation/specs/span-annotation/spec.md
2026-02-15 10:16:05 +01:00

11 KiB

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)