Skip to content

Infinite list view model#1249

Open
artalar wants to merge 3 commits into
v1000from
cursor/infinite-list-view-model-0b05
Open

Infinite list view model#1249
artalar wants to merge 3 commits into
v1000from
cursor/infinite-list-view-model-0b05

Conversation

@artalar

@artalar artalar commented Feb 13, 2026

Copy link
Copy Markdown
Collaborator

Add reatomView primitive to reatomLinkedList to 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 offset and limit parameters. 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.


Open in Cursor Open in Web


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 reatomView primitive to reatomLinkedList that 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 reatomView behavior across lens changes, add/remove/clear, move/swap, edge cases (negative/oversized offsets, zero/large limits), and notification/memoization guarantees via view.items.

Written by Cursor Bugbot for commit 2b4cc01. This will update automatically on new commits. Configure here.

…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

cursor Bot commented Feb 13, 2026

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web


const items: Array<LLNode<Node>> = []
while (current !== null && items.length < clampedLimit) {
items.push(current)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

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.
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.

2 participants