## ADDED Requirements ### Requirement: Span annotation JSON export for ML pipeline The system SHALL provide a `GET /api/span-annotations/export` endpoint that exports all span annotations for a given chart as JSON in the format expected by the ML pipeline. The output SHALL be a JSON object with an `annotations` array where each entry has: `id`, `start_time` (Unix timestamp), `end_time` (Unix timestamp), `label`, `confidence` (nullable), `outcome` (nullable), and `sub_spans` (nullable). The endpoint SHALL accept an optional `chartId` query parameter. #### Scenario: Export span annotations as JSON - **WHEN** GET /api/span-annotations/export?chartId=3 is called - **THEN** the system returns a JSON object with all span annotations for chart 3 in the ML pipeline format #### Scenario: Export without chartId - **WHEN** GET /api/span-annotations/export is called without chartId - **THEN** the system exports span annotations for the most recently created chart ### Requirement: Prediction-sourced span annotation creation The system SHALL support creating span annotations with a `source` field indicating whether the annotation was created by a human ("human"), confirmed from a model prediction ("model_confirmed"), or corrected from a model prediction ("model_corrected"). The existing POST endpoint for span annotations SHALL accept an optional `source` field (default: "human") and optional `model_prediction` field (object with `label` and `confidence` from the original prediction). #### Scenario: Create human annotation - **WHEN** a span annotation is created without a source field - **THEN** the source defaults to "human" #### Scenario: Confirm model prediction - **WHEN** a user confirms a model prediction as an annotation - **THEN** the span annotation is created with source "model_confirmed" and model_prediction containing the original predicted label and confidence #### Scenario: Correct model prediction - **WHEN** a user changes the label of a model prediction before saving - **THEN** the span annotation is created with source "model_corrected" and model_prediction containing the original predicted label and confidence ### Requirement: Negative annotation for dismissed predictions The system SHALL support saving negative annotations when a user dismisses a model prediction as "not a pattern". A negative annotation SHALL have label "O", source "human_correction", and a `model_prediction` field recording what the model originally predicted. #### Scenario: Save negative annotation - **WHEN** user dismisses a "bull_flag" prediction with confidence 0.72 - **THEN** the system creates a span annotation with label "O", source "human_correction", and model_prediction `{ "label": "bull_flag", "confidence": 0.72 }` ### Requirement: Keyboard handler uses stable selectedSpanId The SpanAnnotationManager keyboard handler SHALL use a `useRef` for `selectedSpanId` to prevent stale closure reads. The `handleDeleteSpan` function SHALL be wrapped in `useCallback` with proper dependencies. #### Scenario: Delete correct span via keyboard - **WHEN** user selects span A, then selects span B, then presses Delete - **THEN** span B is deleted (not span A from a stale closure) ### Requirement: Preview primitive uses ref instead of state The SpanAnnotationManager preview primitive (shown during span drawing on mouse move) SHALL use a `useRef` instead of `useState` to avoid a state/effect feedback loop and unnecessary re-renders. #### Scenario: Mouse move during drawing does not cause re-render cascade - **WHEN** the user moves the mouse while drawing a span annotation - **THEN** the preview primitive updates via ref mutation without triggering a React re-render ### Requirement: Preview primitive cleanup on unmount The SpanAnnotationManager SHALL detach the preview primitive in the `useEffect` cleanup function when the component unmounts, preventing a leaked canvas primitive. #### Scenario: Component unmounts during drawing - **WHEN** the user navigates away while mid-draw - **THEN** the preview primitive is detached from the chart ### Requirement: fitContent not called on every span change The SpanAnnotationManager reconciliation effect SHALL NOT call `chart.timeScale().fitContent()` on every span annotation change. `fitContent()` SHALL only be called on initial chart load. #### Scenario: Span annotation added preserves zoom - **WHEN** the user adds a new span annotation - **THEN** the chart maintains the current scroll position and zoom level (no fitContent reset) ### Requirement: Incremental primitive updates The SpanAnnotationManager SHALL update only the selection state of existing primitives on selection change instead of detaching all and re-attaching all. Full recreation SHALL only occur when the annotation list itself changes (add/remove/edit). #### Scenario: Selection change is O(1) - **WHEN** the user clicks a different span annotation - **THEN** only the previously selected and newly selected primitives are updated (not all N primitives) #### Scenario: Annotation add triggers full reconciliation - **WHEN** a new span annotation is added - **THEN** the primitive list is reconciled (new primitive added, existing ones kept)