Phase 2 follow-on: a single self-contained TimelineDebugOverlay over the main
timeline so behavior can be watched live while the virtualizer is young.
Read-only by design: it only READS state the hook and virtualizer already
expose — rendered-rows vs total, msg/divider split, overscan, visible index
range, measured row-height min/max, totalSize, scrollTop, clientHeight,
isAtBottom, newMessageCount, and the active jump target (deep-link / find /
highlight). It never touches, wraps, or perturbs the scroll path, so there is
zero risk to sticky-bottom / prepend-retention / deep-link.
Ship-then-rip: removal is delete this one file plus its single import + single
render line in MessageTimeline.tsx. No flag-gate hook, no localStorage toggle —
nothing else references it, so it leaves zero residue. Main timeline only;
thread pane untouched.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Phase 2: render only the viewport (+overscan) of the main timeline instead of
all ~200+ rows, lifting the ~2,000-row ceiling. Main timeline ONLY — the thread
pane (and its useTimelineScrollManager) is untouched (Phase 3).
What changed:
- MessageTimeline now drives @tanstack/react-virtual off the same deferred
snapshot the rows render from, flattening day-grouped entries into
index-addressable rows via buildVirtualTimelineRows. Rows are built from the
FILTERED main-timeline entries (buildMainTimelineEntries), so dropped thread
replies never desync the row->entry mapping.
- useVirtualTimelineScroll REPLACES the bespoke 427-line useTimelineScrollManager
for the main timeline. The virtualizer owns the scroll container and all
measurement/anchoring; this hook is a thin wrapper keeping sticky-bottom
autoscroll, accent smooth-scroll, the newMessageCount pill, isAtBottom, and
deep-link + find-in-page jumps (via findVirtualRowIndexForMessage ->
scrollToIndex). No scrollTop locking, no ResizeObserver re-pinning.
- useLoadOlderOnScroll is gutted to a trigger: the double-requestAnimationFrame
scrollTop correction and restoreScrollPosition plumbing are DELETED. Stable
per-row keys (getItemKey) let the virtualizer hold scroll position on prepend
natively — the band-aid is gone at the root.
- cmd+F find drives scroll-to-row through the same scrollToIndex bridge; a
render-all-while-searching escape hatch exists as an off-by-default fallback
(the in-app path is the default, keeping the perf win).
- The per-entry render (3 row variants + video-review context) is extracted to
renderTimelineEntry + useVideoReviewContextById, a single source of truth
injected into VirtualizedTimelineList. The old nested TimelineMessageList is
deleted (orphaned by the rewire).
Must-keeps preserved: sticky-bottom autoscroll, day dividers (first-class
variable-height flat rows), jump-to-message deep links, no-tearing (same
deferredMessages snapshot drives both rows and scroll logic).
Coverage: 8 jsdom/testing-library DOM tests cover row dispatch (divider vs
message, flat-index->entry mapping), init autoscroll, accent smooth-scroll, and
deep-link scrollToIndex. jsdom has no layout engine, so react-virtual's pixel
measurement/scroll math stays on a manual/visual verification pass.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Phase 2 (b)-lite, slice 1: establish a thin DOM test lane so the upcoming
virtualized scroll rewrite ships with automated coverage, not just manual checks.
The repo's test runner is `node --test` over `*.test.mjs` with no jsdom and no
JSX transform, so component rendering wasn't testable. This adds a second lane
that coexists with the existing pure-logic lane:
- esbuild `load` hook transforms .ts/.tsx (TS + JSX, automatic React runtime)
- global-jsdom installs DOM globals via `--import`
- @testing-library/react + /dom for render/query
- `pnpm test` now runs both lanes (test:unit + test:dom)
- DOM tests are `*.dom.test.tsx`
Deliberately thin: jsdom has no layout engine, so it does not cover
react-virtual's real measurement/scroll math — those stay on the
manual/visual verification pass (see FEASIBILITY.md). One smoke test proves
the net is wired.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Read-only Phase 2 feasibility spike (NOT ship-ready). Assesses whether
@tanstack/react-virtual can cleanly own the main timeline, replacing the
bespoke scrollTop-locking scroll manager and the ~2,000-row render ceiling.
Artifacts:
- buildVirtualTimelineRows.ts: pure helper flattening day-grouped entries
into a flat indexed row list (day-divider + message kinds) the virtualizer
can measure/key; includes findVirtualRowIndexForMessage for deep-link/find.
- buildVirtualTimelineRows.test.mjs: 9 tests covering dividers, renderKey
keying, prepend key stability (native position-retention contract), and
flat-index lookup.
- __spike__/VirtualizedTimelinePoc.tsx: thin non-wired PoC showing the
react-virtual integration shape (autoscroll, prepend retention via
getItemKey, scrollToIndex for find/deep-link).
- __spike__/FEASIBILITY.md: honest verdict on the three scoped points —
sticky-bottom autoscroll, scroll-up pagination with native position
retention, and cmd+F find-in-page plan.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>