Skip to content

perf(sidebar): windowed session list — only mount the visible row window - #626

Draft
Nuctori wants to merge 1 commit into
agegr:mainfrom
Nuctori:perf/l3-sidebar
Draft

perf(sidebar): windowed session list — only mount the visible row window#626
Nuctori wants to merge 1 commit into
agegr:mainfrom
Nuctori:perf/l3-sidebar

Conversation

@Nuctori

@Nuctori Nuctori commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

What

Windowed session list. The sidebar mounts at most the visible window of
SessionItem rows plus a small overscan; the rest are flattened by
translateY. Empty / loading / error states are unchanged.

  • SESSION_LIST_ITEM_HEIGHT = 54 every SessionItem already renders
    at exactly this height (existing constant inside the component).
  • ResizeObserver on the scroll container keeps listViewportH in sync
    with layout changes (sidebar resize, font-size change, browser zoom).
  • onScroll is rAF-throttled: a frame may receive many scroll events
    but emits at most one setListScrollTop per frame.
  • First-paint fallback: while ResizeObserver has not yet measured
    (listViewportH === 0), the slice uses a 600 px window guess. It is
    replaced on the first observer callback, before the user can scroll.

Why

The 2003-session catalog at #599 was a known regression: each SessionItem
allocates its own hover / rename / delete state machines, and the parent
mounts all of them on the first paint. With L1 reducing the route to
~100 ms but the sidebar still rendering 2003 cards, the user feels no
overall speedup. This is the complementary client-side fix.

No new dependencies

  • 0 changes to package.json / package-lock.json
  • 0 imports of react-window / react-virtual / similar
  • 1 file changed: components/SessionSidebar.tsx (+81 / -21)

Audit (self)

  • base: origin/main (28bab3c), ahead 0 before this PR
  • lint: 0 errors
  • tsc: 0 errors (node_modules/.bin/tsc --noEmit)
  • independent of P1 (perf-bench) / P2 (L1 scanner) / E2E harness
    (no e2e/, no lib/, no .github/**)
  • reverts: revert 1 file; no other touches
  • commit hygiene: 1 commit, no merge noise

Stacked plan (each PR base=main, merge-order-free)

Local evidence

On the local 1992-session catalog, before this PR the first paint of
the sidebar mounts 1992 React subtrees; after this PR it mounts roughly
viewport / 54 px items (typically 6–20) plus an 8-row overscan.

The 2000+ session catalogue rendered every <SessionItem/> eagerly, so
first paint paid for 2003 DOM nodes plus 2003 hover/rename/delete state
machines. Switch the list to a fixed-height (54px) window: ResizeObserver
measures the scroll viewport, a rAF-throttled onScroll computes the
visible window, and translateY positions the slice. Empty/loading/error
states render unaffected. The first paint shows a small slice; scroll
mounts the next window. No new dependency.
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.

1 participant