Skip to content

perf(listbox): register rows and items once per commit - #596

Draft
kianbazza wants to merge 1 commit into
ui-650-add-a-browser-benchmark-for-typing-in-menu-searchfrom
ui-651-register-menu-rows-and-items-once-per-commit
Draft

kianbazza wants to merge 1 commit into
ui-650-add-a-browser-benchmark-for-typing-in-menu-searchfrom
ui-651-register-menu-rows-and-items-once-per-commit

Conversation

@kianbazza

@kianbazza kianbazza commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Each row re-sorted every row by DOM position when it mounted, and each item re-scored every item. When hundreds of rows mount together (opening a long menu, or a Backspace that clears the search), that is a full compareDocumentPosition sort and a full commandScore pass per row.

Registrations now queue, and the store applies each batch at once: rows that mount in a commit are sorted once, and items that mount together are re-scored once.

 registerRow(id, element)           // layout effect
-  re-sort orderedRows, set state
+  queue the row
 registerItem(id, registration)     // passive effect
-  recomputeFilteredItems()
-  maybeAutoHighlightOnRegister(id)
+  queue the item
+
+flushPendingRegistrations()
+  one sort of orderedRows (only if rows were added), one set
+  one recomputeFilteredItems()
+  one auto-highlight check for the items registered since the last flush

Who calls flushPendingRegistrations():

  • List's layout effect. A parent's layout effects run after its children's, so it sees every row registered in the commit.
  • The store's registrationBatch counter, which List subscribes to. The first registration of a batch bumps it, so a commit List wasn't part of (an item's passive effect, a row that toggles on its own) re-renders List in the same update. Publishing stays inside React's update; jsdom act(...) warnings are unchanged.
  • getVisibleItemIds, selectByShortcut, and validateHighlight, before they read registration state.
  • A microtask, for rows outside a list.

Evidence

row-renders.test.tsx mounts 20 items in one commit and counts how often the store publishes the row order and the filter scores, right after render:

  • Before: expected { rowOrder: 20, scores: 20 } to deeply equal { rowOrder: 1, scores: 1 }
    After: passes. With List's flush removed it fails with { rowOrder: 0, scores: 0 }, so it checks the wiring, not the microtask.

bun run bench:browser -- --rows=1000 --throttle=1,4 --loops=10 --per-key, p50 in ms:

                                   jsx 1×        data 1×       jsx 4×          data 4×
Backspace, 728 rows come back      486 →  40     573 →  48     1878 →  74      2175 →  92
"1" → "12", 272 rows → 46           64 →  15      19 →  13      146 →  21        68 →  14
menu ready (incl. page load)       114 →  46     119 →  54      489 → 174       466 → 180

The "all rows match" phases don't change (no rows register on those keys); they move between ~13 and ~29 ms from run to run on either branch.

Merge Danger

Door: two-way

Blast Radius: medium

Every menu, select, and combobox registers through this store.

  • Code that calls registerRow or registerItem on an exported store directly and reads state.orderedRows, filteredItems, or filteredCount right after now sees the old value until flushPendingRegistrations() (or the next microtask). Inside components, List publishes before paint.
  • With filter={false}, if several items register in the same commit in an order different from orderedItems, auto-highlight picks the first in orderedItems. It used to pick whichever registered first. Items registering in separate commits behave as before.
  • *StoreState types gain registrationBatch.

Closes UI-651

@kianbazza
kianbazza added this pull request to stack #587 October 10, 2026 10:50
@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ui-canary Ready Ready Preview Oct 10, 2026 11:33pm UTC

Request Review

@linear-code

linear-code Bot commented Oct 10, 2026

Copy link
Copy Markdown

UI-651

This branch was successfully deployed

1 active deployment
Preview – ui-canary — a5a10307 Deployed Oct 10, 2026 by vercel[bot]
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