Skip to content

fix: defer sentinel check until masonry container has a measured width#736

Merged
shaoster merged 2 commits into
mainfrom
issue/734-sentinel-only
May 29, 2026
Merged

fix: defer sentinel check until masonry container has a measured width#736
shaoster merged 2 commits into
mainfrom
issue/734-sentinel-only

Conversation

@shaoster
Copy link
Copy Markdown
Owner

Problem

Fixes #734 (Bug 1). On the PieceList page with 17+ pieces, a second-page fetch fires immediately on mount — before the user has scrolled and before any cards are visible — causing a dark flash as the masonry re-lays out.

Root cause: The scroll sentinel useEffect called check() immediately when hasMore became true. At that moment masonryWidth=0 (the useContainerPosition ResizeObserver hasn't fired yet), so the sentinel sits at top≈0 in the unmeasured document. check() sees top ≤ window.innerHeight + 300 → calls onLoadMore → page 2 fetch starts before page 1 is painted.

Fix

Move the masonryRef / columnWidth / useContainerPosition declarations above the sentinel effect so masonryWidth is in scope, then add it to the effect's dependency list and return early when masonryWidth === 0:

if (!hasMore || masonryWidth === 0) return;

The sentinel now first runs once the masonry container has a real width — at which point the sentinel is at its true document position and check() correctly reflects whether the user actually needs more content.

The positioner useMemo and all other masonry logic are unchanged.

Regression Test

PieceList > scroll sentinel > does not call onLoadMore while the masonry container width is unmeasured

Renders 16 pieces with hasMore=true and masonryWidth=0, then waits 50 ms (giving React's MessageChannel-based scheduler time to fire mount effects) and asserts onLoadMore was never called.

  • Fails on main before this fix — onLoadMore is called 1 time
  • Passes with this fix — effect returns early, onLoadMore never called

Verification

rtk bazel test //web:web_piece_list_test  # 52 tests pass
rtk bazel test //web:web_test             # 37 test targets pass

Closes #734

shaoster and others added 2 commits May 28, 2026 18:58
When hasMore=true, the scroll sentinel effect called check() immediately on
mount. At that point masonryWidth=0 (the ResizeObserver hasn't fired yet) so
the sentinel sits at top≈0, check() returns true, and onLoadMore fires before
any cards are visible. This triggers an immediate second-page fetch that
produces the pagination flash.

Fix: move the masonryRef/columnWidth/useContainerPosition declarations above
the sentinel effect so masonryWidth is in scope, then add masonryWidth to the
effect deps and return early when it is 0. The sentinel first runs once the
masonry container has a real width, at which point the sentinel is at its true
position and check() correctly reflects whether the user needs more content.

The positioner useMemo and all other masonry logic are unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verifies that onLoadMore is not called while masonryWidth=0 (the
ResizeObserver hasn't fired yet). Uses a real setTimeout to give
React's MessageChannel-based scheduler time to fire mount effects
before asserting. Fails on the pre-fix code, passes with the guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@shaoster shaoster merged commit f790ece into main May 29, 2026
5 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.

fix: PieceList flashes on pagination as positioner is recreated from scratch

1 participant