Skip to content

perf(listbox): compute row position flags once per order change - #583

Draft
kianbazza wants to merge 1 commit into
ui-640-re-render-only-rows-whose-visibility-changes-while-filteringfrom
ui-641-compute-row-position-flags-once-per-order-change
Draft

kianbazza wants to merge 1 commit into
ui-640-re-render-only-rows-whose-visibility-changes-while-filteringfrom
ui-641-compute-row-position-flags-once-per-order-change

Conversation

@kianbazza

@kianbazza kianbazza commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Each positional selector filtered the whole row order for every row on every store update, so a long menu paid roughly rows² work per update. The positions are now worked out once per order change and every selector is a lookup.

 isFirstInGroup(state, rowId)
   if virtualized: return false
-  row = orderedRows.find(r => r.id === rowId)
-  siblings = orderedRows.filter(r => r.groupId === row.groupId)
-  return siblings[0].id === rowId
+  return getRowPositions(orderedRows).firstInGroupIds.has(rowId)

+getRowPositions(orderedRows)      // cached per orderedRows array
+  one walk: first/last loose row, first/last group, first/last row per group

The same change applies to isFirstRow, isLastRow, isFirstGroup, isLastGroup and isLastInGroup. orderedRows is already replaced wholesale whenever a row mounts or unmounts, so the array itself keys the cache.

Evidence

ListboxStore.test.ts › "reads the row order a bounded number of times per order change": 101 rows. It counts reads of orderedRows elements while every row's six selectors run, then runs them again after a highlight change, which notifies the store without changing the order.

                                        before    after
first sweep after an order change       61,206    101    (asserted <= 101)
second sweep after a highlight change   61,206    0      (asserted 0)
  • Before: AssertionError: expected 61206 to be less than or equal to 101
    After: passes, with the flags checked for the first, last, first-in-group and last-in-group rows

positional-attrs.test.tsx passes unchanged.

Merge Danger

Door: two-way

Blast Radius: small

A wrong lookup would put data-first, data-last, data-first-group, data-last-group, data-first-in-group or data-last-in-group on the wrong row, which changes styling but not behaviour. positional-attrs.test.tsx covers these attributes through filtering.

Closes UI-641

@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:34pm UTC

Request Review

@linear-code

linear-code Bot commented Oct 10, 2026

Copy link
Copy Markdown

UI-641

@kianbazza
kianbazza force-pushed the ui-641-compute-row-position-flags-once-per-order-change branch from 4cde3a3 to 851a609 Compare October 10, 2026 23:33

This branch was successfully deployed

1 active deployment
Preview – ui-canary — 851a609c 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