Skip to content

fix: preserve committed pages in connector range management in combo-box#9220

Open
DiegoCardoso wants to merge 6 commits intomainfrom
fix/combo-box/connector-range-management
Open

fix: preserve committed pages in connector range management in combo-box#9220
DiegoCardoso wants to merge 6 commits intomainfrom
fix/combo-box/connector-range-management

Conversation

@DiegoCardoso
Copy link
Copy Markdown
Contributor

@DiegoCardoso DiegoCardoso commented Apr 29, 2026

Summary

A lazy combo-box can get stuck on loading=true when the connector receives a programmatic scrollToIndex jump to an unloaded far page — the existing range-management logic wipes already-committed data and the data-fetch flow stalls. This fix tracks committed pages separately from pending callbacks so the active-range eviction trigger stays accurate, and the non-sequential branch fires a viewport-range RPC that covers all pending pages (the server's last-write-wins semantics mean a per-page RPC drops earlier-pending requests).

ComponentRenderer-rendered pages outside the new viewport are evicted client-side: the server passivates the rendered components when items leave the KeyMapper's active set, so cached node ids would otherwise bind to detached virtual children. Plain-data pages stay in cache.

This is a prerequisite for the focusSelectedItem feature in #9194, which exercises this path on every open with a far-positioned selected item.

Notes for reviewers

A summary comment with the deeper context behind the assertion changes in ComboBoxClientSideDataRangeIT, ItemCountUnknownComboBoxIT, and the commitPage / non-sequential-branch fixes is posted as a separate PR comment to keep the diff readable.

`commitPage` previously did not delete the entry from `pageCallbacks`,
so subsequent `dataProvider` requests for different pages saw stale
active-range entries — the non-sequential branch then wiped already-
committed data and the data-fetch flow stalled when a far-page request
came in (e.g. a programmatic `scrollToIndex` jump on lazy data). Track
committed pages in a dedicated set so the active-range eviction trigger
stays accurate while pageCallbacks reflects only pending requests.

Also relax the non-sequential branch to fetch only the new page rather
than wiping everything: the original wipe-and-refetch was a side effect
of the stale-pageCallbacks bug, and removing it lets the connector
serve far-page requests correctly without triggering a stuck-loading
cycle.

Adjusts ItemCountUnknownComboBoxIT and ComboBoxClientSideDataRangeIT
to match the new fetch cadence (fewer redundant fetches now that
committed data isn't needlessly evicted on non-sequential scroll).
The behavior under test is preserved; the implementation-detail
RangeLog assertions and over-aggressive eviction expectations are
relaxed, with comments pointing to the connector fix.

Marks LazyLoadingIT.setComponentRenderer_scrollDown_scrollUp_itemsRendered
as @ignore — pre-existing FlowComponentRenderer behavior that relied
on the connector's wipe-and-refetch to re-attach component-renderer
slots after scroll. Tracked separately as a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vaadin vaadin deleted a comment from github-actions Bot Apr 29, 2026
…s on non-contiguous request

Two follow-on fixes to the connector range-management change:

1. Cover all pending pages in the non-sequential setViewportRange.

   `_scroller.scrollIntoView(N)` triggers per-page `dataProvider` calls
   for every visible placeholder page in one rAF batch. Each call hit
   the non-sequential branch and fired its own `setViewportRange` RPC.
   These RPCs overwrite each other on the server (last write wins), so
   any earlier-pending page that wasn't covered by the latest range
   never received data — its `pageCallbacks` entry lingered and the
   combo-box stayed `loading=true`. The fix expands the range to cover
   every entry in `pageCallbacks` so the latest RPC always covers the
   earlier ones.

2. Evict ComponentRenderer-rendered committed pages outside the new
   range so their stale node ids don't get re-bound on scroll-back.

   PR #9220 preserves committed pages by design, but server-side
   ComponentRenderer components are passivated when items leave the
   KeyMapper's active set — virtual children are detached and the
   `*_nodeid` properties cached on the client point at deleted state-
   tree nodes. Re-rendering from the cached items showed stale (or
   empty) renderer slots. The eviction is scoped to pages whose first
   item carries an `*_nodeid` property, so plain-data pages stay
   intact and `ComboBoxClientSideDataRangeIT`'s page-0-preserved
   assertion still holds.

Un-`@Ignore`s `LazyLoadingIT.setComponentRenderer_scrollDown_scrollUp_itemsRendered`. The pre-existing
`@Ignore` blamed FlowComponentRenderer; the actual cause was the
combination above.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@DiegoCardoso DiegoCardoso changed the title fix(combo-box): preserve committed pages in connector range management fix: preserve committed pages in connector range management in combo-box Apr 30, 2026
DiegoCardoso and others added 2 commits April 30, 2026 10:33
- Flatten the filter callback to a direct boolean expression instead
  of an inline early-return guard.
- Drop the redundant length check: clearPageCallbacks runs forEach
  over its input, which is a no-op on an empty array.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop comments that explain what the code USED to do (or describe the
PR rather than the resulting code). Keep only comments that document
non-obvious WHY of the resulting code — invariants, server-behavior
contracts, hidden constraints. Historical context moved to PR #9220.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@DiegoCardoso
Copy link
Copy Markdown
Contributor Author

Reviewer notes — context for the trimmed comments

The latest commit (d54a46832d) removes some long inline comments that read more like commit-message material than code documentation. Posting the historical context here so it's preserved without bloating the source files. Each item below corresponds to a block that was shortened or dropped.

comboBoxConnector.js — non-sequential branch

The full rationale for "fire setViewportRange covering all currently-pending pages, not just the new one":

  • _scroller.scrollIntoView(N) jumps the virtualizer; visible placeholders fire index-requested → the data-provider-controller turns them into per-page dataProvider({page, ...}, callback) calls in one rAF batch.
  • With pageSize=50 and a render buffer that spans more than one page, the call comes in for two adjacent pages back-to-back (e.g. pages 5 and 6 when scrolling to index 300).
  • Each call previously fired its own setViewportRange RPC. These overwrite each other on the server (last write wins), so the earlier-pending page never received data and its pageCallbacks entry lingered → combo-box stuck on loading=true.
  • The new range covers every entry in pageCallbacks so the latest RPC always covers the earlier ones.

comboBoxConnector.jscommitPage deletion of pageCallbacks[page]

Without delete pageCallbacks[page] after committing, every subsequent fetch for a different page would treat the just-committed entry as a gap in the active range and incorrectly invoke clearPageCallbacks() across all pages. The deletion is what makes the committedPages Set the single source of truth for "delivered" while pageCallbacks reflects only pending requests.

ComboBoxClientSideDataRangeIT.scrollToEnd_* assertion change

The pre-PR assertion was "1 page loaded after jumping to the end" (only the last page). The PR's committedPages change preserves page 0 (loaded by openPopup) when the connector receives a non-contiguous request — the older code wiped already-committed pages on a jump as a side effect of the range-management bug. Memory still stays bounded by maxLoadedItemsCount, which kicks in only when total active pages exceed the cap.

ItemCountUnknownComboBoxITRangeLog assertions dropped

The original tests verified a specific server-side fetch cadence via RangeLog entries. That cadence depended on the connector's pre-fix range-management bug — every non-contiguous request triggered a clearPageCallbacks() that wiped previously-loaded pages, causing the WC to re-fetch them. With the connector fix, far-page fetches don't disturb already-loaded pages, so fewer round-trips happen and the index/range sequence is different (and inherently timing-sensitive). The behavior the tests still verify — item count growth, visible labels, count-mode switching — is what matters for the feature; the cadence assertions are tied to the old bug and not worth re-asserting under the new behavior.

Adds a `non-contiguous page requests` describe block to the connector
WTR suite covering the three connector behaviors changed in this PR:

1. A multi-page rAF batch fires one setViewportRange covering every
   pending page, not per-page (regression guard for the stuck-loading
   bug — `setViewportRange` is last-write-wins on the server).
2. Already-committed pages no longer drag their entries into a later
   non-contiguous request range.
3. ComponentRenderer-rendered committed pages are evicted on a
   non-contiguous jump while plain-data pages stay cached.

Also moves the `confirmUpdate` server stub and `plainItems` /
`rendererItems` factories into `shared.ts` so additional test cases
can use them.

Run via `node scripts/wtr.js combo-box`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

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.

1 participant