perf: split SpanAnnotationManager reconciliation into two effects

Separate the single reconciliation effect into:
1. Full reconciliation effect — runs only when spanAnnotations list,
   series, chart, or candles change. Rebuilds all primitives.
2. Selection-only effect — runs only when selectedSpanId changes.
   Calls setSelected() on existing primitives instead of detaching
   and reattaching all of them.

This avoids tearing down and rebuilding every primitive on each
selection change, which was the main unnecessary overhead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marko Djordjevic 2026-02-18 15:21:46 +01:00
parent aa27fe93f6
commit acf31d3edf
2 changed files with 11 additions and 4 deletions

View file

@ -73,7 +73,7 @@
- [x] 8.1 `[opus]` Fix SpanAnnotationManager preview primitive memory leak: replace `useState` with `useRef` for preview primitive, add cleanup on unmount (`src/components/SpanAnnotationManager.tsx:265-324`)
- [x] 8.2 `[sonnet]` Use `chart.applyOptions()` for theme changes instead of re-creating chart (`src/components/CandleChart.tsx:333`)
- [x] 8.3 `[sonnet]` Remove `fitContent()` from reconciliation effect, only call on initial load (`src/components/SpanAnnotationManager.tsx:160`)
- [ ] 8.4 `[opus]` Implement incremental primitive updates in SpanAnnotationManager: update only selection state on selection change, full reconciliation only on annotation list changes (`src/components/SpanAnnotationManager.tsx:104-161`)
- [x] 8.4 `[opus]` Implement incremental primitive updates in SpanAnnotationManager: update only selection state on selection change, full reconciliation only on annotation list changes (`src/components/SpanAnnotationManager.tsx:104-161`)
- [ ] 8.5 `[sonnet]` Add bounded prediction cache (max 100 entries, FIFO eviction) to `predictionCacheRef` (`src/app/page.tsx:195-199`)
- [ ] 8.6 `[sonnet]` Fix hardcoded 1-minute candle interval: detect interval from data, use for span iteration (`src/components/CandleChart.tsx:452`)
- [ ] 8.7 `[haiku]` Extract magic numbers (8px, 60s, colors) to named constants in `CandleChart.tsx`

View file

@ -121,7 +121,7 @@ export default function SpanAnnotationManager({
return { max_high, min_low };
};
// Render span annotations as primitives
// Full reconciliation: rebuild all primitives when annotation list changes
useEffect(() => {
if (!series || !chart) return;
@ -170,13 +170,20 @@ export default function SpanAnnotationManager({
const primitive = new SpanRectanglePrimitive({
data: spanData,
isSelected: span.id === selectedSpanId,
isSelected: span.id === selectedSpanIdRef.current,
});
series.attachPrimitive(primitive);
primitivesRef.current.set(span.id, primitive);
});
}, [spanAnnotations, selectedSpanId, series, chart, candles]);
}, [spanAnnotations, series, chart, candles]);
// Selection-only effect: update visual state without rebuilding primitives
useEffect(() => {
primitivesRef.current.forEach((primitive, spanId) => {
primitive.setSelected(spanId === selectedSpanId);
});
}, [selectedSpanId]);
// Fit chart content only on initial load when chart and series become available
useEffect(() => {