<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Implements synchronized crosshair functionality across multiple Chart.js visualizations using a centralized store pattern.
**Key Changes:**
- Created `CrosshairStore` singleton (`crosshair.ts`) to coordinate hover state across charts
- Extended crosshair plugin with `sync` callbacks (`onHover`, `getExternalLabel`) for multi-chart coordination
- Integrated crosshair synchronization in all chart components (`ChartContainer`, `IPChart`, `ProtocolChart`, `SpectrumStatsChart`)
- Each chart registers/unregisters with the store on mount/destroy
- Local hover takes precedence over external hover for better UX
- Tooltips only display on the actively hovered chart, while crosshairs mirror across all registered charts
**Architecture:**
The implementation uses a pub-sub pattern where the hovered chart notifies the store, which triggers redraws on all other charts. Charts query the store during their draw cycle to determine if they should display an external crosshair.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- Implementation is clean, well-architected, and follows established patterns. Proper lifecycle management with register/unregister prevents memory leaks. The singleton store pattern is appropriate for coordinating global UI state. No logic errors or security issues identified.
- No files require special attention
<h3>Important Files Changed</h3>
| Filename | Overview |
|----------|----------|
| netflow-webapp/src/lib/stores/crosshair.ts | New singleton store for synchronized crosshair state management across charts |
| netflow-webapp/src/lib/components/charts/crosshair-plugin.ts | Added sync callbacks and external label support for cross-chart synchronization |
| netflow-webapp/src/lib/components/charts/ChartContainer.svelte | Integrated crosshair store with registration/unregistration and sync callbacks |
| netflow-webapp/src/lib/components/charts/IPChart.svelte | Added crosshair plugin with sync support and proper chart lifecycle management |
| netflow-webapp/src/lib/components/charts/ProtocolChart.svelte | Added crosshair plugin with sync support and proper chart lifecycle management |
| netflow-webapp/src/lib/components/charts/SpectrumStatsChart.svelte | Added crosshair plugin with sync support for scatter chart type |
</details>
<h3>Sequence Diagram</h3>
```mermaid
sequenceDiagram
participant User
participant ChartA as Chart A (e.g., IPChart)
participant Plugin as Crosshair Plugin
participant Store as Crosshair Store
participant ChartB as Chart B (e.g., ProtocolChart)
Note over ChartA,ChartB: Initialization Phase
ChartA->>Store: register('ip', chartInstance)
ChartB->>Store: register('protocol', chartInstance)
Note over ChartA,ChartB: User Hovers Over Chart A
User->>ChartA: mousemove event
ChartA->>Plugin: afterEvent (mousemove)
Plugin->>Plugin: calculateHoveredDate(mouseX)
Plugin->>Store: setHover(label, 'ip')
Store->>Store: _hoveredLabel = label<br/>_sourceChartId = 'ip'
Store->>ChartB: chart.draw()
Note over ChartB: Chart B Redraw
ChartB->>Plugin: afterDraw
Plugin->>Store: getExternalLabel('protocol')
Store-->>Plugin: return label (sourceId != 'protocol')
Plugin->>Plugin: Find label index<br/>Convert to pixel position
Plugin->>ChartB: Draw crosshair at synchronized position
Note over ChartA: Chart A Continues
Plugin->>ChartA: afterDraw
Plugin->>Store: getExternalLabel('ip')
Store-->>Plugin: return null (sourceId == 'ip')
Plugin->>ChartA: Draw crosshair at local mouse position<br/>Show tooltip after delay
Note over ChartA,ChartB: User Leaves Chart A
User->>ChartA: mouseout event
ChartA->>Plugin: afterEvent (mouseout)
Plugin->>Store: setHover(null, 'ip')
Store->>Store: _hoveredLabel = null<br/>_sourceChartId = null
Store->>ChartB: chart.draw()
ChartB->>Plugin: afterDraw
Plugin->>Store: getExternalLabel('protocol')
Store-->>Plugin: return null
Plugin->>ChartB: No crosshair drawn
Note over ChartA,ChartB: Cleanup Phase
ChartA->>Store: unregister('ip')
ChartB->>Store: unregister('protocol')
```
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->6 files changedLine totals unavailable