Skip to content

tech-debt batch 5b: decompose IntervalHistory.tsx into hooks (#156)#169

Merged
delabrcd merged 1 commit into
mainfrom
tech-debt/156-intervalhistory
Jun 22, 2026
Merged

tech-debt batch 5b: decompose IntervalHistory.tsx into hooks (#156)#169
delabrcd merged 1 commit into
mainfrom
tech-debt/156-intervalhistory

Conversation

@delabrcd

Copy link
Copy Markdown
Owner

Tech-debt batch 5b (epic #161). Behavior-preserving structural decomposition of the biggest component.

Closes #156.

What

IntervalHistory.tsx 1424 → 899 LOC; extracted three colocated impure-shell hooks (pure window math stays in lib/intervalZoom.ts):

  • lib/hooks/useIntervalFetch.ts — SWR lifecycle (warm-cache hydrate, no-cold-blank revalidate, alive-guard).
  • lib/hooks/useOverscanLoad.ts — the WS8 reconcile trio + load state + debounce/cleanup.
  • lib/hooks/useChartNavigation.ts — the two native non-passive listeners (wheel zoom/shift-pan, ctrl+drag) + focus/modifier tracking.

Regression-safety (this is the sibling of the widget that crashed prod in v0.40.2)

Explicitly guarded every payload deref against the loading/null state — the #150 crash class. data is [] until a payload exists (if (!state || 'error' in state) return []), so state.rows is never read eagerly; the chart subtree is only built in the final non-loading render branch; all payload reads guard with state && !('error' in state). No state!/payload! in JSX. Preserved invariants: fresh curWindowRef reads (no stale-zoom closure), horizontal-wheel pan + stopPropagation, and the one justified exhaustive-deps disable (kept, not broadened).

Verification

  • Docker test 68 files / 983 tests; production build (lint+typecheck) green.
  • ⚠ Gesture-sensitive — lead will mount the widget on staging (force-mount via cleared layout) and verify the loading state + zoom/pan/drag-select + no crash + 0 console errors before release.

)

Extract the three impure shells out of the 1424-LOC IntervalHistory widget into
colocated custom hooks under lib/hooks/, leaving a presentational shell (-525 LOC):

- useIntervalFetch(key, url): the WS2 SWR fetch lifecycle — warm-cache hydrate,
  no-cold-blank revalidate, alive-guard, stale-beats-blank on error. Owns the
  IntervalApiRow/Loaded/LoadState types + toLoaded. Keeps the justified
  exhaustive-deps disable (reads state only for the shimmer flag at fetch start).
- useOverscanLoad(bounds): the WS8 reconcile trio (loadWindowForView /
  reconcileLoad / scheduleReconcile) + the load state, loadRef mirror, debounce
  timer, and a resetLoad() for the context-change effect.
- useChartNavigation(bodyRef, curWindowRef, applyNavWindow, bounds, dataLength):
  the two native non-passive listeners (wheel zoom/shift-pan + Ctrl+drag pan) and
  focus/modifier tracking; returns the focus state, chart cursor, and the refs the
  Recharts drag-select handlers read.

Behaviour-preserving: the pure window math stays in lib/intervalZoom +
lib/intervalOverscan. Preserves the fresh-curWindowRef read (no stale zoom
closure), horizontal-wheel pan gate, the wheel stopPropagation, and the loading
guards (the populated chart subtree stays behind the loading/empty/errored
ternary; no payload deref runs with null state). Hooks satisfy no-floating-promises
via void + .catch. Docker test 983/983; production build (lint+typecheck) green.
@delabrcd delabrcd merged commit e357e59 into main Jun 22, 2026
6 checks passed
@delabrcd delabrcd deleted the tech-debt/156-intervalhistory branch June 22, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decompose IntervalHistory.tsx (1467 LOC) into fetch/overscan/navigation hooks

1 participant