Skip to content

Add virtual scrolling to StreamsTable for large stream lists.#557

Merged
ritik4ever merged 1 commit into
ritik4ever:mainfrom
testersweb0-bug:feat/streams-table-virtual-scroll-448
Jun 1, 2026
Merged

Add virtual scrolling to StreamsTable for large stream lists.#557
ritik4ever merged 1 commit into
ritik4ever:mainfrom
testersweb0-bug:feat/streams-table-virtual-scroll-448

Conversation

@testersweb0-bug
Copy link
Copy Markdown
Contributor

@testersweb0-bug testersweb0-bug commented May 31, 2026

Summary

Fixes #448 by adding virtual scrolling to StreamsTable using @tanstack/react-virtual. When 50+ streams are loaded, only rows in the visible viewport plus 5 overscan rows are mounted in the DOM, eliminating scroll jank and DOM bloat with 500+ streams.

  • Integrate @tanstack/react-virtual with a bounded scroll container and sticky table header
  • Window tbody rendering with top/bottom spacer rows to preserve scroll position
  • Measure row height dynamically so expanded timeline rows are accounted for
  • Keep lists under 50 streams on the existing full-render path (no regression for typical usage)
  • Export STREAMS_TABLE_VIRTUAL_OVERSCAN = 5 for testability

Acceptance criteria

  • Only visible + 5 overscan rows rendered for large lists (500 streams → ~15 DOM rows)
  • Smooth scrolling — DOM size stays bounded regardless of total stream count
  • Keyboard navigation preserved — Tab/focus works on checkboxes, timeline toggles, and action buttons in rendered rows
  • React DevTools shows dramatically fewer StreamRow instances with 500 streams

Test plan

  • npm test -- --run src/components/StreamsTable.test.tsx — all 6 tests pass
  • Load dashboard with 500+ streams; confirm smooth scroll with no visible jank
  • Open React DevTools → Components; verify ~15 StreamRow instances instead of 500
  • Tab through row checkboxes and Cancel/Pause/Resume buttons — focus order works
  • Expand a stream timeline mid-scroll — row resizes correctly, scroll position stable
  • Toggle optional columns while scrolled — layout remains correct
  • Verify lists under 50 streams behave identically to before (all rows visible, no fixed viewport height)
  • Bulk select-all and bulk cancel still operate on the full filtered set, not just visible rows

Summary by CodeRabbit

  • New Features

    • Implemented virtual scrolling for the streams table to improve performance when displaying large lists by rendering only visible rows.
  • Tests

    • Added comprehensive test coverage for virtual scrolling functionality, including rendering behavior for large datasets, container height handling, and focus preservation.

Window row rendering with @tanstack/react-virtual keeps DOM size bounded when 500+ streams are loaded, preserving keyboard focus and expandable timeline rows.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 2026

@testersweb0-bug is attempting to deploy a commit to the ritik4ever's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 31, 2026

@testersweb0-bug Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements virtual scrolling for StreamsTable using @tanstack/react-virtual to efficiently render large stream lists. It adds library dependency, configurable virtualization constants, custom row measurement logic, conditional row rendering, StreamRow prop extensions, CSS styling for layout, and comprehensive test coverage validating scroll behavior and focus preservation.

Changes

Virtual Scrolling Implementation

Layer / File(s) Summary
Dependencies and Virtualization Configuration
frontend/package.json, frontend/src/components/StreamsTable.tsx (imports and constants)
Adds @tanstack/react-virtual ^3.13.26 dependency and defines virtualization constants: exported STREAMS_TABLE_VIRTUAL_OVERSCAN = 5, row height 48px, virtualization threshold of 50 streams, and scroll viewport heights.
Core Virtualization Logic
frontend/src/components/StreamsTable.tsx (virtualization state, hooks, rendering)
Implements useVirtualizer with custom row measurement (supporting timeline row height), scroll element state binding, computation of whether to virtualize based on list size, derivation of virtual rows, fallback viewport-based row set, and measurement trigger via useLayoutEffect. Scroll container applies streams-table-scroll class and dynamic max-height styling when virtualization is active.
StreamRow Virtualization Props and Rendering
frontend/src/components/StreamsTable.tsx (StreamRowProps, StreamRow, memo)
Extends StreamRowProps with dataIndex and optional measureRef function. Updates StreamRow destructuring to accept these props, attaches measureRef and data-index to the main stream <tr>, adds data-timeline-row="true" to timeline rows for height measurement, and includes dataIndex in memo comparator logic.
Styles for Virtual Scrolling Layout
frontend/src/index.css
Introduces .streams-table-scroll for overflow and max-height control, .streams-table-head and .streams-table-head th for sticky header positioning with white background and elevated z-index, and .streams-table-spacer td with near-zero line-height to collapse spacer row height.
Virtual Scrolling Tests and Helpers
frontend/src/components/StreamsTable.test.tsx
Adds createMockStream factory function with parameterized status for reusable fixtures, setScrollViewport helper to mock DOM layout properties, then validates bounded scroll container class/styling, row rendering limits with 500 streams (rendered rows ≤ container height / row height + overscan), STREAMS_TABLE_VIRTUAL_OVERSCAN constant value of 5, and keyboard focus order preservation in row action controls.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ritik4ever/stellar-stream#490: Modifies StreamRow memoization comparator and skeleton row rendering paths that are extended in this PR to support row virtualization.

Poem

A rabbit bounds through rows of streams so bright,
With virtual scrolling dancing left and right,
Five hundred rows? No sweat, we skip ahead!
Measuring heights and keeping focus thread—
🐰 "Efficient rendering," the bunny said!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding virtual scrolling to StreamsTable for large lists, which aligns with the core objective of the PR.
Linked Issues check ✅ Passed Code changes fully implement the acceptance criteria from #448: virtual scrolling with @tanstack/react-virtual, configurable 5-row overscan, bounded DOM rendering for 50+ streams, dynamic row measurement, and keyboard navigation preservation.
Out of Scope Changes check ✅ Passed All changes are directly related to the virtual scrolling implementation: dependency addition, virtualization logic in StreamsTable, test coverage for virtual scrolling behavior, and supporting CSS. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/components/StreamsTable.test.tsx (1)

95-143: ⚡ Quick win

Add a threshold-boundary test for virtualization toggle (49 vs 50 rows).

Current tests validate the large-list path, but not the boundary where virtualization should switch on/off. A focused boundary test would protect the contract from regressions with minimal cost.

Suggested test addition
 describe("StreamsTable virtual scrolling", () => {
+  it("does not virtualize below threshold and virtualizes at threshold", () => {
+    const belowThreshold = Array.from({ length: 49 }, (_, i) =>
+      createMockStream(String(i + 1).padStart(4, "0")),
+    );
+    const atThreshold = Array.from({ length: 50 }, (_, i) =>
+      createMockStream(String(i + 1).padStart(4, "0")),
+    );
+
+    const view = render(<StreamsTable {...defaultProps} streams={belowThreshold} />);
+    expect(
+      screen.getAllByRole("checkbox", { name: /^Select stream / }).length,
+    ).toBe(49);
+
+    setScrollViewport(screen.getByTestId("streams-table-scroll"), 400);
+    view.rerender(<StreamsTable {...defaultProps} streams={atThreshold} />);
+    expect(
+      screen.getAllByRole("checkbox", { name: /^Select stream / }).length,
+    ).toBeLessThan(50);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/StreamsTable.test.tsx` around lines 95 - 143, Add a
boundary test that verifies virtualization toggles between 49 and 50 rows:
create two datasets using createMockStream (length 49 and length 50), render
StreamsTable with each, call
setScrollViewport(screen.getByTestId("streams-table-scroll"), 400) and rerender
as the large-list test does, then assert for 49 items that the number of
rendered checkbox rows (screen.getAllByRole("checkbox", { name: /^Select stream
/ })) equals 49 (no virtualization) and for 50 items that the rendered count is
less than 50 and <= Math.ceil(400 / 52) + STREAMS_TABLE_VIRTUAL_OVERSCAN + 2
(virtualized); add this as a new it block in StreamsTable.test.tsx.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/StreamsTable.tsx`:
- Around line 294-299: Clamp paddingBottom to a non-negative value to prevent
negative spacing when measured heights exceed estimates: compute the existing
paddingBottom using resolvedVirtualRows and rowVirtualizer.getTotalSize() (as
currently done) and then replace it with Math.max(0, paddingBottom) (or
equivalent) before it’s used in rendering; update the variable named
paddingBottom in StreamsTable (where resolvedVirtualRows and
rowVirtualizer.getTotalSize() are referenced) so any temporary negative result
becomes 0.

---

Nitpick comments:
In `@frontend/src/components/StreamsTable.test.tsx`:
- Around line 95-143: Add a boundary test that verifies virtualization toggles
between 49 and 50 rows: create two datasets using createMockStream (length 49
and length 50), render StreamsTable with each, call
setScrollViewport(screen.getByTestId("streams-table-scroll"), 400) and rerender
as the large-list test does, then assert for 49 items that the number of
rendered checkbox rows (screen.getAllByRole("checkbox", { name: /^Select stream
/ })) equals 49 (no virtualization) and for 50 items that the rendered count is
less than 50 and <= Math.ceil(400 / 52) + STREAMS_TABLE_VIRTUAL_OVERSCAN + 2
(virtualized); add this as a new it block in StreamsTable.test.tsx.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1aa41fcd-22fc-4ba7-8409-0338898bbc7b

📥 Commits

Reviewing files that changed from the base of the PR and between cab45cf and f865051.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • frontend/package.json
  • frontend/src/components/StreamsTable.test.tsx
  • frontend/src/components/StreamsTable.tsx
  • frontend/src/index.css

Comment on lines +294 to +299
const paddingTop =
resolvedVirtualRows.length > 0 ? resolvedVirtualRows[0].start : 0;
const paddingBottom =
resolvedVirtualRows.length > 0
? rowVirtualizer.getTotalSize() - resolvedVirtualRows[resolvedVirtualRows.length - 1].end
: 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against negative paddingBottom.

If measured row heights exceed estimates during recalculation, getTotalSize() could temporarily be smaller than the last row's end, resulting in negative padding. CSS treats negative heights as 0, but explicit clamping is clearer and avoids layout edge cases.

🛡️ Proposed fix
   const paddingBottom =
     resolvedVirtualRows.length > 0
-      ? rowVirtualizer.getTotalSize() - resolvedVirtualRows[resolvedVirtualRows.length - 1].end
+      ? Math.max(0, rowVirtualizer.getTotalSize() - resolvedVirtualRows[resolvedVirtualRows.length - 1].end)
       : 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const paddingTop =
resolvedVirtualRows.length > 0 ? resolvedVirtualRows[0].start : 0;
const paddingBottom =
resolvedVirtualRows.length > 0
? rowVirtualizer.getTotalSize() - resolvedVirtualRows[resolvedVirtualRows.length - 1].end
: 0;
const paddingTop =
resolvedVirtualRows.length > 0 ? resolvedVirtualRows[0].start : 0;
const paddingBottom =
resolvedVirtualRows.length > 0
? Math.max(0, rowVirtualizer.getTotalSize() - resolvedVirtualRows[resolvedVirtualRows.length - 1].end)
: 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/StreamsTable.tsx` around lines 294 - 299, Clamp
paddingBottom to a non-negative value to prevent negative spacing when measured
heights exceed estimates: compute the existing paddingBottom using
resolvedVirtualRows and rowVirtualizer.getTotalSize() (as currently done) and
then replace it with Math.max(0, paddingBottom) (or equivalent) before it’s used
in rendering; update the variable named paddingBottom in StreamsTable (where
resolvedVirtualRows and rowVirtualizer.getTotalSize() are referenced) so any
temporary negative result becomes 0.

@ritik4ever ritik4ever merged commit eb79b4f into ritik4ever:main Jun 1, 2026
4 of 6 checks passed
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.

Add virtual scrolling to StreamsTable for 500+ streams

2 participants