# Research: `react-native-wagmi-charts` for ticker-chart migration

**Scope:** PR #70 (feature/nan-20-live-price-websocket).
**Author:** tele (coordinator, not dispatched agent).
**Date:** 2026-04-18.

## TL;DR

Wagmi-charts is a credible replacement for the ticker chart's interactive layer (line + candle) and pairs cleanly with PR #70's 1.5 s-batched `LivePriceStore`. It will **not** fix the 1D multi-session-bleed bug — that's a data-layer issue in `getStockAggs` / WS overlay, and must be addressed independently. The line↔candle switch "feels bad" problem is only partially solved structurally, but wagmi's unified design and gesture model make the swap cohesive instead of jarring. Migration scope is contained: replace `StockLineChart` + `StockCandleChart` internals, preserve the existing public props, keep financial/EPS charts on victory-native. Net add to bundle is moderate (Reanimated + gesture-handler already present; haptic-feedback is new).

## Current chart stack (baseline)

- **Line chart** — `src/features/stock-detail/presentation/components/StockLineChart.tsx` — victory-native `CartesianChart` + `Line`, wrapped in custom `ChartCrosshair`.
- **Candle chart** — `src/features/stock-detail/presentation/components/StockCandleChart.tsx` — victory-native bounds provider, but renders candles with `@shopify/react-native-skia` (`Group`, `SkiaLine`, `RoundedRect`) because victory-native has no native candlestick.
- **Crosshair/gesture** — custom, built on `useChartPressState` from victory-native + reanimated `useAnimatedReaction`.
- **Type switch** — `StockDetailScreen.tsx:110–116`, conditional on `chartType` state — full unmount/remount of the chart component on every toggle.
- **Data** — `useChartData(ticker, timeRange)` → React Query → `container.market.repository.getStockAggs` → `AggBar[]`. `5 min staleTime`. No live overlay wired today; this is what PR #70 is adding.
- **Live price infra (PR #70)** — `LivePriceStore` buffers WS ticks, flushes every 1.5 s via `useSyncExternalStore`. Prices currently surface on watchlist / movers / header / etc., NOT on the ticker chart yet. That wiring is unclaimed.

## Wagmi-charts capabilities

Source: context7 `/coinjar/react-native-wagmi-charts` + GitHub README. Latest `2.9.1` (Feb 2026). 125 doc snippets, reputation: High.

| Feature | Supported | Notes |
|---|---|---|
| Line chart | ✅ | `LineChart.Provider` + `LineChart.Path`. Native transition animation via `pathProps.isTransitionEnabled`. |
| Candlestick | ✅ | `CandlestickChart.Provider` + `CandlestickChart.Candles`. Built-in; no hand-rolled Skia needed. |
| Crosshair + tooltip | ✅ | `CursorCrosshair`, `Tooltip`, `PriceText`, `DatetimeText` — compose-able. |
| Haptics on scrub | ✅ | `onCurrentIndexChange` callback; user wires to `expo-haptics`. |
| Type switch (line↔candle) | ❌ | No unified provider; must conditional-mount the right Provider. **Same pattern as today.** |
| Real-time updates | ⚠️ | Data passed as JS array prop. On change → Provider re-renders. No `SharedValue` data input. Fine for 1.5 s flush cadence (1 render per 1.5 s); would be bad for per-tick 500 Hz. |
| Web | ⚠️ | Experimental, flickers. Not relevant — this repo is mobile-first (iOS/Android Expo). |
| TypeScript | ✅ | 96.6 % TS; first-class types. |
| Dependencies | — | `react-native-reanimated` ✅ present, `react-native-gesture-handler` ✅ present, `react-native-haptic-feedback` **❌ add**. |
| Rendering tech | Reanimated + RN SVG | No Skia. We'd have Skia (for financials) + Reanimated-SVG (for ticker) coexisting — already the de-facto state. |

## Mapping to the three user pain points

### 1. "Interaction feels unnatural"

**Root cause (today):** `useChartPressState` + custom `useAnimatedReaction` + custom Crosshair + manual haptics wiring. Every detail is hand-rolled; gesture thresholds and release behavior aren't tuned.

**With wagmi:** built-in `CursorCrosshair` + `Tooltip` are battle-tested (CoinJar wallet is the namesake). Haptic trigger via `onCurrentIndexChange` is one-line. The pan/release model is opinionated and familiar (Robinhood-style, same lineage wagmi borrows from).

**Verdict:** ✅ High-confidence improvement. Delete custom crosshair code, adopt wagmi primitives, wire haptic.

### 2. "Switch between line and candle is bad"

**Root cause (today):** conditional remount causes the chart to disappear-and-reappear. No shared animation between chart types.

**With wagmi:** same structural constraint (two Providers, conditional mount). Wagmi does **not** offer a unified line/candle Provider. However:
- Both Providers share the same visual language (padding, bounds, crosshair style) → swap looks more cohesive.
- `isTransitionEnabled: true` on the `Line` side smooths the data-change animation on line mount.
- UX lift is available: animate opacity on swap + key the parent View to `chartType` so React Native crossfade via Reanimated — a ~30-line add.

**Verdict:** ⚠️ Partial. Library alone won't fix; pairing it with a crossfade wrapper does. Scope the crossfade into the migration PR.

### 3. "1D timeframe shows multiple days of data on last session" → unreliable WS, render glitches

**Root cause:** this is a **data bug, not a chart bug.** `getStockAggs(ticker, '1D')` is returning bars that span > 1 session (likely: WS ingest isn't filtering pre-/post-session ticks into the live buffer; OR `AggBar[]` aggregation window isn't normalized to the current trading session).

**With wagmi:** unchanged. Any chart library will faithfully render whatever bars it's given.

**Fix lives in:**
- `getStockAggs` service adapter — normalize bars to current session for `1D`.
- `LivePriceStore` ingest — filter ticks by session window when feeding into chart overlay.
- Chart data integration — **decide the data model**: does the chart's last bar get mutated in place by the latest WS price (interpolating the current candle's `close`), or is a synthetic "live bar" appended? Wagmi's transition animation works best with the former — mutate last bar + re-submit the array.

**Verdict:** ❌ Orthogonal. Must be addressed at the data layer. Document as a separate sub-task on PR #70.

## Live-price wiring (new work on PR #70, chart-side)

Regardless of library choice, PR #70 needs to answer: **how does a live WS tick surface on the chart?**

Proposed pattern (works equally for victory-native or wagmi):

```
LivePriceStore (1.5 s flush)
  → useLivePrice(ticker) hook (already exists in PR #70)
  → useMemo: take static bars from useChartData, clone, set bars[last].close = livePrice
  → feed cloned array to chart
```

With wagmi this plays nicely with `isTransitionEnabled` — the last-bar close animates each flush. With victory-native it requires manual Reanimated tweening to avoid snap-jumps.

## Migration scope (if approved)

**In-scope (PR #70 agent):**
1. Add dep `react-native-haptic-feedback` (or use `expo-haptics` already in the tree).
2. Replace internals of `StockLineChart.tsx` with `LineChart.Provider` + `LineChart.Path` + `CursorCrosshair` + custom price/time text using existing tokens. Keep the exported component signature identical.
3. Replace internals of `StockCandleChart.tsx` with `CandlestickChart.Provider` + `CandlestickChart.Candles` + `CandlestickChart.Crosshair`. Delete the hand-rolled Skia candle renderer (~40 lines gone).
4. Delete custom `ChartCrosshair.tsx` + `useCrosshair` wiring once both ticker charts migrate.
5. Add crossfade wrapper in `ChartSlot` (`StockDetailScreen.tsx:110`) for line↔candle swap polish.
6. Wire `LivePriceStore` tick → mutate `bars[last].close` → feed wagmi. Validate 1.5 s cadence feels smooth.
7. **Separate commit**: fix `getStockAggs('1D')` session-window bug.

**Out-of-scope (keep victory-native):**
- Financial bar/line charts under `components/financials/*` — stay on victory-native. Wagmi has no bar or stacked-bar type.
- EPS / projection charts under `components/analyst-forecasts/*`.
- Portfolio chart (SimTrade) — if it's line-only and interactive, candidate for follow-up migration, not this PR.
- Tour's `InteractiveLineChart.tsx` — victory-native; leave for now.

## Risks & unknowns

- **Crosshair customization depth** — wagmi's Tooltip is composable but its out-of-box look won't match our `ChartCrosshair` banner exactly. ~1 day of style work to match.
- **Candlestick color theming** — wagmi accepts `positiveColor` / `negativeColor`, but wick vs body styling might be less granular than our Skia version. Spot-check OHLC in both trend cases.
- **Reanimated version** — wagmi web needs Reanimated ≥ 2.3.0-beta.2; repo is on 3.x, so fine.
- **Bundle size** — not measured; Reanimated + gesture-handler already present, so net add is just wagmi itself (~50 KB gzipped per Context7 signal).
- **Test coverage** — current ticker-chart tests mock at render boundary. Wagmi migration should preserve those mocks; no new Jest tooling needed.
- **Fabric/new arch compatibility** — wagmi uses Reanimated 2/3 APIs that work on Fabric. No known issues.

## Recommendation

1. **Migrate ticker chart (line + candle) to wagmi-charts** — scoped inside PR #70 as 1–2 separable commits after live-price wiring is stable. Expected wins: interaction feel, code reduction (~100 LOC deleted), candle rendering no longer hand-rolled.
2. **Fix the 1D session-bleed bug at the data layer** — independent sub-task on PR #70. Do NOT block the library swap on this.
3. **Keep financials/EPS/portfolio on victory-native** — wagmi only does line + candle.
4. **Spike first, then migrate** — one agent session: rebuild `StockLineChart` on wagmi in isolation, visual-verify against production, decide go/no-go. Budget 4h.

## User answers (2026-04-18)

1. **1D session-bleed reproduces on pure REST** for some tickers → bug lives in `getStockAggs` service adapter, NOT in WS overlay. Fix scope narrows: audit session-window filtering in the adapter; no live-price ingestion audit needed.
2. **Haptics → use `expo-haptics`.** Trade-off was expo-haptics vs react-native-haptic-feedback. Neither currently in tree, so fresh install. Chosen based on:
   - Fits existing dep pattern (repo is Expo-managed, ~20 `expo-*` packages already)
   - Zero native linking / prebuild config changes
   - EAS preview hook keeps working unchanged
   - Wagmi README itself uses expo-haptics in examples
   - Only need one haptic type (light impact on scrub) → granularity edge of haptic-feedback lib is moot
   - Lower maintenance surface (Expo SDK tracks RN/Expo versions)
3. **Crossfade polish IS in-scope of PR #70** — not a follow-up.

## Final dispatch brief (for PR-70 agent)

**GOAL:** migrate ticker chart to wagmi-charts + fix 1D session-bleed.

**FILE OWNERSHIP:**
- `src/features/stock-detail/presentation/components/StockLineChart.tsx` (replace internals)
- `src/features/stock-detail/presentation/components/StockCandleChart.tsx` (replace internals; delete hand-rolled Skia candle renderer)
- `src/features/stock-detail/presentation/components/ChartCrosshair.tsx` (delete after migration)
- `src/features/stock-detail/presentation/screens/StockDetailScreen.tsx` (add crossfade wrapper to `ChartSlot`, lines 110–116)
- Service adapter path for `getStockAggs` (name TBD by agent on first scan) → fix 1D session-window bug
- `package.json` → add `react-native-wagmi-charts`, `expo-haptics`

**OUT OF SCOPE:**
- Financial/EPS/portfolio charts under `components/financials/*`, `components/analyst-forecasts/*`, `features/simtrade` — stay on victory-native
- Tour's `InteractiveLineChart.tsx`
- `LivePriceStore` itself (already built on PR #70; just consume it)

**SEQUENCING (recommended):**
1. Spike on line chart only (~4h) — swap `StockLineChart` to wagmi `LineChart.Provider + Path + CursorCrosshair`, wire `expo-haptics` on `onCurrentIndexChange`, visual-verify vs production. Checkpoint ping. GO/NO-GO decision.
2. If GO: swap candle chart, delete hand-rolled Skia renderer + custom crosshair.
3. Wire live price tick → mutate `bars[last].close` → feed wagmi data prop. Validate 1.5s flush cadence.
4. Add crossfade wrapper in `ChartSlot`.
5. **Separate commit**: fix 1D session-bleed in `getStockAggs` adapter (normalize bars to current trading session window).

**VISUAL VERIFY:** Playwright screenshot or EAS preview on device, compare against current chart for both line and candle, both positive/negative trend states.

**ACCEPTANCE:**
- Scrub interaction feels natural (smooth, haptic tick, no jumps)
- Line↔candle swap crossfades instead of hard-cut
- 1D chart shows ONLY current session on tickers that previously bled
- Live price updates smoothly on last bar (no snap-jumps)
- `rtk pnpm check` clean
- Existing ticker-chart tests green
