I think I was able to reproduce it in a Codespace built based on that branch.
I tried because today I noticed that sometimes the numbers in the user directory are wrong and I thought that maybe it’s because of the issue that users are not loaded.
As you can see, the number of posts @lcor read today changed after the reload. Before, it didn’t fit into the ordered list.
Actually, the 214 are the posts read this week:
This was not the only account where the numbers showed the weekly count instead of today’s when I performed these steps. There are more users that look like they aren’t at the right place, where actually the weekly numbers are shown.
Next users in the list before/after reload
The numbers are indeed different but what’s more important is that we’re always doing two requests to the directory_items endpoint (one with, the other without the .json extension) but one of them has incorrect parameters
I can’t however reproduce locally, I have two different requests, but they’re on different endpoints (groups/search.json vs directory_items)
Did you try adding more users to your local install? I wonder if you need more than 50 users, so not all of them are loaded at once.
I think the users who show wrong numbers were the ones not visible on first loading.
So this ended up being a nasty “race condition” between the “sentinel” we use to detect when we need to trigger the “load more” action and the rendering of the rows in the users directory
The Issue
When there were at least 50 users, the /u (users directory) page was triggering loadMore immediately on first load, before the user could scroll down. This caused an unwanted second page of results to load automatically.
Root Cause
Timing race condition during initial page load:
User navigates to /u
controllers/users starts loading data (isLoading: true)
Template renders with ConditionalLoadingSpinner showing spinner
IntersectionObserver is created and starts watching immediately
At this moment, the table is still calculating layout/expanding to full height
Sentinel is briefly visible in viewport (~292px from top) before table expands
Observer detects intersection → triggers loadMore
Table finishes expanding to full height (~3689px)
Sentinel moves to correct position (~3959px, below viewport)
The observer was “too eager” - it started watching before the content finished its layout, catching the sentinel during the brief moment when the table hadn’t reached its final height yet.
The Fix
Delay observer creation until content is ready:
Added an @isLoading parameter to LoadMore that prevents the IntersectionObserver from being created when content is still loading.
How It Works Now
Page load → isLoading=true → Modifier skips observer creation
↓
Data loads → isLoading=false → Modifier re-runs, creates observer
↓
Table fully expanded → Sentinel at correct position (below viewport)
↓
User scrolls down → Sentinel enters viewport → loadMore triggers ✓