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

6.7 KiB

1. Database Schema & Migrations

  • 1.1 Add span_label_types table to Drizzle schema (id, name, display_name, color, hotkey, is_active, sort_order, created_at)
  • 1.2 Add span_annotations table to Drizzle schema (id, chart_id FK, start_time, end_time, label, confidence, outcome, notes, sub_spans JSON, color, created_at)
  • 1.3 Run migration to create both tables
  • 1.4 Seed span_label_types with default labels: bull_flag, bear_flag, head_and_shoulders, double_bottom, wedge_up, wedge_down, custom (with distinct colors and hotkeys 1-7)

2. Span Label Types API

  • 2.1 Create GET /api/span-label-types endpoint — return all active label types sorted by sort_order
  • 2.2 Create POST /api/span-label-types endpoint — create a new label type
  • 2.3 Create PATCH /api/span-label-types/[id] endpoint — update label type fields
  • 2.4 Create DELETE /api/span-label-types/[id] endpoint — delete a label type

3. Span Annotations CRUD API

  • 3.1 Create GET /api/span-annotations?chartId=X endpoint — return all span annotations for a chart, sorted by start_time desc
  • 3.2 Create POST /api/span-annotations endpoint — create span annotation with start/end time swap validation (start <= end)
  • 3.3 Create PATCH /api/span-annotations/[id] endpoint — update label, confidence, outcome, notes, sub_spans
  • 3.4 Create DELETE /api/span-annotations/[id] endpoint — delete a span annotation

4. SpanRectanglePrimitive (Chart Rendering)

  • 4.1 Create SpanRectanglePrimitive.ts implementing ISeriesPrimitive with data-space rectangle coordinates (start_time, end_time, max_high, min_low)
  • 4.2 Implement updateAllViews() with a pane renderer that converts time/price to pixel coordinates and draws a filled semi-transparent rectangle using useBitmapCoordinateSpace
  • 4.3 Implement label tag text rendering above the rectangle (pattern name)
  • 4.4 Implement hitTest() to detect clicks within the rectangle bounds
  • 4.5 Add highlight state (thicker border / increased opacity) for selected spans
  • 4.6 Set zOrder: 'bottom' so rectangles render behind candlesticks

5. Span Tool State & Integration

  • 5.1 Extend activeTool type in page.tsx to include 'span' tool mode
  • 5.2 Add span-related state to page.tsx: spanAnnotations[], selectedSpanId, spanLabelTypes[]
  • 5.3 Add fetchSpanAnnotations(chartId) and fetchSpanLabelTypes() data fetching functions
  • 5.4 Load span annotations and label types when chart changes (alongside existing annotation loading)
  • 5.5 Add "Span" tool button to Toolbox component alongside existing tools

6. Two-Click Span Selection & Preview

  • 6.1 Create SpanAnnotationManager.tsx component that manages span interaction state (idle / first-click-done / popover-open)
  • 6.2 Implement first-click handler: snap to nearest candle, store start candle, render start marker via a preview primitive
  • 6.3 Implement mouse-move preview: stretch preview rectangle from start candle to cursor candle, computing price range (min low to max high) of candles in range
  • 6.4 Implement second-click handler: snap to nearest candle, finalize span range, swap if end < start, trigger popover
  • 6.5 Implement Escape key to cancel span selection and clear preview

7. Label Assignment Popover

  • 7.1 Create SpanPopover.tsx with shadcn Popover/Dialog: label dropdown (from span_label_types), confidence slider (1-5), outcome select (win/loss/breakeven/none), notes textarea, Save/Cancel buttons
  • 7.2 Position popover near the end-click position with collision avoidance
  • 7.3 Wire Save button: POST to API, attach SpanRectanglePrimitive to chart series, update spanAnnotations state, close popover
  • 7.4 Wire Cancel button / Escape: discard span, clear preview, close popover
  • 7.5 Disable Save when no label is selected (validation)

8. Span Selection, Editing & Deletion

  • 8.1 Implement span click-to-select using hitTest: set selectedSpanId, highlight rectangle, scroll sidebar list to selected item
  • 8.2 Implement click-to-deselect (click selected span again or click outside any span)
  • 8.3 Implement double-click / Enter to open edit popover pre-populated with current span data
  • 8.4 Wire edit Save: PATCH to API, update primitive color/label, update state
  • 8.5 Implement Delete/Backspace keyboard shortcut for selected span: DELETE API call, remove primitive, clear selection, update state
  • 8.6 Implement delete-tool click on span rectangle: same DELETE flow as keyboard shortcut

9. Span Annotation Sidebar List

  • 9.1 Create SpanAnnotationList.tsx component: scrollable list of span annotations sorted by start_time desc, showing time range, label with colored badge, delete button
  • 9.2 Add count summary grouped by label type (e.g., "Bull Flag: 3 | Bear Flag: 2")
  • 9.3 Implement click-to-select in list: set selectedSpanId, highlight chart rectangle, scroll chart to center on span's time range
  • 9.4 Highlight selected span entry in list (background/border)
  • 9.5 Wire delete button per list item: DELETE API call, remove primitive, update state
  • 9.6 Show empty state message when no span annotations exist
  • 9.7 Clear and reload span list when active chart changes

10. Hotkey Label Assignment

  • 10.1 Add keydown listener for span label hotkeys (active only when span tool is active and span range is selected)
  • 10.2 On hotkey press: save span with mapped label (default confidence/outcome/notes), render rectangle, update sidebar — skip popover
  • 10.3 Ignore hotkeys when span tool is inactive or no span range selected

11. Export Endpoints

  • 11.1 Create GET /api/export/spans?chartId=X&format=json — Raw Annotations JSON export with full metadata
  • 11.2 Create GET /api/export/spans?chartId=X&format=windowed — Windowed Classification CSV with flattened OHLCV columns and configurable context_padding (default 10)
  • 11.3 Create GET /api/export/spans?chartId=X&format=bio — BIO-tagged CSV with one row per candle, B-{label}/I-{label}/O tagging, multi-label columns for overlapping spans

12. Integration Testing & Polish

  • 12.1 Verify full create flow: activate span tool → two clicks → popover → save → rectangle renders → sidebar updates
  • 12.2 Verify edit flow: select span → double-click → edit popover → save → updates reflect
  • 12.3 Verify delete flow: select span → Delete key / trash icon → removed from chart and sidebar
  • 12.4 Verify chart switch: span annotations and sidebar list reload for new chart
  • 12.5 Verify all three export formats produce correct output
  • 12.6 Verify hotkey label assignment works end-to-end