Infinite list view model#1249
Conversation
…list
Add a reatomView method to LinkedListAtom that provides a reactive
'lens' / 'view' into a linked list, selecting a range of elements
based on offset and limit parameters.
API:
list.reatomView(() => ({ offset, limit }), name?)
The lens callback is evaluated reactively, tracking any atoms read
inside it. The view automatically updates when:
- The source linked list changes (create, remove, swap, move, clear)
- The offset or limit values change (via tracked atoms)
Returns a LinkedListViewAtom with:
- items: Array<Node> - the nodes in the current view window
- totalSize: number - total size of the source list
- offset: number - clamped offset position
- limit: number - requested limit
Includes a .items computed that only notifies subscribers when the
actual items in the window change (ignores totalSize-only changes).
Features:
- Memoized items array reference when content is unchanged
- Clamped offset (0 to list size) and limit (>= 0)
- O(offset + limit) traversal per recomputation
- Full reactivity via reatom's computed tracking
New types: LinkedListViewState, LinkedListViewAtom
17 comprehensive tests covering:
- Basic windowing, reactive lens atoms, pagination
- List mutations (add, remove, removeMany, swap, move, clear)
- Items memoization and selective notification
- Edge cases (empty list, offset overflow, zero limit, negative offset)
- Sliding window / pagination pattern
|
Cursor Agent can help with this pull request. Just |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| prev?: LinkedListViewState<LLNode<Node>>, | ||
| ): LinkedListViewState<LLNode<Node>> => { | ||
| const ll = linkedList() | ||
| const { offset: rawOffset, limit: rawLimit } = lens() |
There was a problem hiding this comment.
View can read stale batched state
Medium Severity
reatomView computes from linkedList() even while list.batch() is mutating shared STATE in place. Unlike reatomMap, it has no batching guard, so a read during the same batch can cache and return a stale window until the batch finishes, producing incorrect intermediate behavior.
|
|
||
| const items: Array<LLNode<Node>> = [] | ||
| while (current !== null && items.length < clampedLimit) { | ||
| items.push(current) |
There was a problem hiding this comment.
Fractional pagination values produce wrong window
Low Severity
reatomView accepts numeric offset and limit but never normalizes them to integers. The loop conditions use < against fractional values, which effectively rounds up and returns too many/shifted items, causing incorrect window boundaries when lens() yields non-integer values.
Create a higher-level reatomInfinityList primitive that composes
a linked list with a reatomView to provide all the state needed
for an infinite scroll / virtual list implementation.
API:
reatomInfinityList(linkedList, { limit?, name?, fetcher? })
Core state (always present):
- offset: Atom<number> - viewport start position
- limit: Atom<number> - viewport size
- view: LinkedListViewAtom - the reactive windowed view
- items: Computed<Array<Node>> - visible items (memoized)
- topCount: Computed<number> - hidden items above (for padding)
- bottomCount: Computed<number> - hidden items below (for padding)
- totalSize: Computed<number> - total loaded items
- hasMore: Atom<boolean> - whether more data exists
- isEnd: Computed<boolean> - viewport reached end && !hasMore
Optional async fetcher integration (when fetcher is provided):
- loadMore: Action & AsyncExt - fetches next page and appends
- isLoading: Computed<boolean> - whether a fetch is in progress
- The fetcher receives (offset, limit) and returns Array<Params>
- Automatically sets hasMore=false when fetched < limit items
- Integrates with AsyncExt for error/pending/ready tracking
New types: InfinityListAtom, InfinityListFetcherAtom
17 tests covering:
- Basic viewport state, offset/limit changes
- Padding counts for virtual scroll (topCount * itemHeight)
- List mutations affecting viewport state
- Async fetching: loadMore, isLoading, hasMore auto-detection
- Multiple page loads, partial pages, error handling
- Near-real infinite scroll model with 73-item server simulation
- Edge cases: empty list, clear, default limit, isEnd detection
…hifts The connectLogger tests were hard-coding specific atom ID numbers from the global named() counter. This made them fragile to any new test file that creates atoms before them. Normalize IDs in assertions so only the graph structure is verified, not the absolute counter values.
Add
reatomViewprimitive toreatomLinkedListto enable efficient windowed views for infinite lists.This primitive provides a reactive "lens" that selects a range of elements from the linked list based on
offsetandlimitparameters. This is crucial for implementing infinite scroll patterns, as it allows rendering only a small visible window of a potentially large data source, improving performance by preventing unnecessary re-renders when items outside the viewport change.Note
Medium Risk
Introduces a new public API and computed traversal/memoization logic over the linked list; risk is mostly correctness/perf regressions in view calculations and notification behavior under list mutations.
Overview
Adds a new
reatomViewprimitive toreatomLinkedListthat exposes a reactive, windowed view (items,totalSize,offset,limit) computed from a lens of{offset, limit}, with clamping and memoization to avoid re-emitting unchanged arrays/state.Extends the test suite with comprehensive coverage for
reatomViewbehavior across lens changes, add/remove/clear, move/swap, edge cases (negative/oversized offsets, zero/large limits), and notification/memoization guarantees viaview.items.Written by Cursor Bugbot for commit 2b4cc01. This will update automatically on new commits. Configure here.