<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR adds drag-range drilldown to all four chart components (`ChartContainer`, `IPChart`, `ProtocolChart`, `SpectrumStatsChart`), allowing users to click-and-drag across chart x-axes to select a time range and drill into it with adaptive granularity. A new `rangeSelectionStore` synchronizes the selection highlight across charts in real-time. The PR also polishes chart legend labels (shorter metric names, reordered to `router · metric`), adds a skeleton loader for the Spectrum router selector, renames UI headings, and adds new shared utilities (`groupByBucketDurationMs`, `chooseAdaptiveGranularity`, `MIN_DRAG_PIXELS`) in `chart-utils.ts`.
- **Drag-range drilldown**: Mouse handlers on each chart's wrapper div track drag start/end positions, resolve them to label indices, compute an adaptive granularity based on the selected duration, and trigger a drilldown navigation.
- **Cross-chart mirroring**: During a drag, the selected label range is published to `rangeSelectionStore`, and other charts render a translucent overlay highlighting the same time range on their own axes.
- **Bug in SpectrumStatsChart**: The chart update path does not refresh `chart.data.labels` after data reloads, causing the drag-range functions to use stale labels and potentially navigate to incorrect time ranges.
- **Label polish**: Chart legends now show `router · metric` instead of `metric – router`, and metric labels are shortened (e.g., "IPv4" instead of "Unique Protocols (IPv4)").
<h3>Confidence Score: 3/5</h3>
- Mostly safe to merge, but SpectrumStatsChart has a stale-labels bug that will cause incorrect drilldown targets after data reloads.
- The core drag-range mechanism is well-implemented across ChartContainer, IPChart, and ProtocolChart. However, SpectrumStatsChart's chart update path omits the `chart.data.labels` update, which all drag-range functions depend on. This will silently produce wrong navigation after any data reload on that chart. The rest of the changes (label polish, skeleton loader, UI text) are low-risk.
- SpectrumStatsChart.svelte — its chart update path at line 663 does not refresh `chart.data.labels`, causing stale label lookups in the new drag-range code.
<h3>Important Files Changed</h3>
| Filename | Overview |
|----------|----------|
| netflow-webapp/src/lib/components/charts/ChartContainer.svelte | Adds drag-range selection with mirrored highlighting via rangeSelectionStore, adaptive granularity drilldown, and click suppression after drag. Implementation is correct; the chart-container's `position: relative` style ensures overlay positioning works. |
| netflow-webapp/src/lib/components/charts/IPChart.svelte | Adds drag-range selection logic (duplicated from ChartContainer), shorter chart legend labels (`router · metric`), and click suppression. Labels are updated on re-render so range selection stays accurate. |
| netflow-webapp/src/lib/components/charts/ProtocolChart.svelte | Adds drag-range selection logic (duplicated from ChartContainer), shorter metric labels (IPv4/IPv6), and click suppression. Labels are updated on re-render so range selection stays accurate. |
| netflow-webapp/src/lib/components/charts/SpectrumStatsChart.svelte | Adds drag-range selection and skeleton loader for routers. Bug: chart update path does not update `chart.data.labels`, causing stale labels after data reloads—drag-range drilldown will navigate to wrong time ranges. |
| netflow-webapp/src/lib/components/charts/chart-utils.ts | Adds `MIN_DRAG_PIXELS`, `groupByBucketDurationMs()`, and `chooseAdaptiveGranularity()` utility functions. Logic is sound with well-calibrated cutoff thresholds. |
| netflow-webapp/src/lib/stores/rangeSelection.ts | New Svelte writable store for cross-chart drag-range synchronization. Clean and minimal interface. |
| netflow-webapp/src/lib/components/filters/PrimaryFilters.svelte | Renames heading to "Global Controls" and adds drag-range hint to navigation instructions. No logic changes. |
| netflow-webapp/src/lib/components/netflow/NetflowDashboard.svelte | Renames heading from "NetFlow Visualization" to "Traffic Overview". No logic changes. |
</details>
<h3>Sequence Diagram</h3>
```mermaid
sequenceDiagram
participant User
participant SourceChart as Source Chart (mousedown/move/up)
participant Store as rangeSelectionStore
participant OtherCharts as Other Charts (mirrored overlay)
User->>SourceChart: mousedown inside chart area
SourceChart->>SourceChart: isDraggingRange = true, record dragStartX
SourceChart->>SourceChart: indexFromPixelX(dragStartX) → startIndex
SourceChart->>Store: set({ sourceChartId, startLabel, endLabel })
Store->>OtherCharts: subscribe → mirroredRange updated
OtherCharts->>OtherCharts: getMirroredSelectionStyle() → render overlay
User->>SourceChart: mousemove (dragging)
SourceChart->>SourceChart: update dragCurrentX, resolve indices
SourceChart->>Store: set({ sourceChartId, startLabel, endLabel })
Store->>OtherCharts: subscribe → update mirrored overlay
User->>SourceChart: mouseup (finish drag)
SourceChart->>SourceChart: finishRangeSelection()
SourceChart->>SourceChart: chooseAdaptiveGranularity(rangeMs)
SourceChart->>SourceChart: applyRangeDrilldown → emit drilldown event
SourceChart->>Store: set(null) — clear selection
Store->>OtherCharts: subscribe → remove mirrored overlay
```
<sub>Last reviewed commit: 02fdc51</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->10 files changedLine totals unavailable