Frontmatter
| id | 9012 |
| title | Optimize Grid Row Pooling to eliminate moveNode deltas |
| state | Closed |
| labels | performancecore |
| assignees | tobiu |
| createdAt | Feb 6, 2026, 3:20 AM |
| updatedAt | Feb 6, 2026, 5:16 PM |
| githubUrl | https://github.com/neomjs/neo/issues/9012 |
| author | tobiu |
| commentsCount | 3 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Feb 6, 2026, 5:16 PM |
Optimize Grid Row Pooling to eliminate moveNode deltas

Input from Gemini 3 Pro:
✦ Status Update: Grid Row Pooling Optimization (#9012)
We have made significant progress in implementing the Fixed-DOM-Order strategy for Grid Rows, eliminating
moveNodedeltas during scrolling. However, we are facing a critical regression in Cell Selection, which is currently intermittent (works on scroll, fails on click).Current State
- Fixed-DOM-Order: Implemented in
GridBody.mjs(recycle rows in place) andRow.mjs.- Logical IDs: Standardized usage of
logicalId(recordId__field) acrossBody,Row, and allSelectionModels.- Selection Refactor:
BaseModelnow attempts to update Row components directly (updateRows) instead of triggering a full Body update.The Problem
- Cell Clicks: Clicking a cell does NOT trigger a visual update immediately.
- Scroll Updates: Scrolling (which triggers
Row.createVdom) successfully applies the selection class.- Root Cause:
updateRowsinBaseModelis failing to trigger the update on click. This implies either:
getRecordlogic is failing to find the record (likelyparseIntvs string mismatch or ID parsing issues).DomEventdelegation is failing (less likely, but possible given the component boundary changes).row.update()with manual VDOM mutation is insufficient or being suppressed.Debugging Steps Taken
- Added logs to
updateRows(currently in the codebase on this branch) to trace execution flow.- Added robust ID resolution in
Body.getRecordFromLogicalId(handling numeric/string mismatch).Next Steps
- Analyze Logs: Run the app with the current branch and check console for
updateRowsoutput.- Verify Event Chain: Confirm
onCellClick->toggleSelection->select->updateRowsis executing.- Fix ID Parsing: Ensure
getRecordandgetRecordFromLogicalIdperfectly match the ID structure generated byRow.We are moving this to a fresh session to clear context and focus purely on debugging this interaction.

Input from Gemini 3 Pro:
✦ Architectural Refinement Proposal: Deconstruct Logical IDs
During the implementation of the Fixed-DOM-Order strategy, we identified significant fragility in using composite string IDs (
recordId__field) for DOM data attributes. This requires error-prone string parsing (split('__')) to resolve records, which breaks if data fields contain underscores or if IDs are complex.Proposal: Split
datasetattributesInstead of
data-cell-id="100__firstname", we should render:<div class="neo-grid-cell" data-record-id="100" data-field="firstname">...</div>Benefits:
Performance (Zero Deltas for Columns): In the Fixed-DOM-Order strategy, a cell always belongs to the same column. Therefore,
data-field="firstname"never changes during vertical scrolling. The VDOM engine will see this as a static attribute, generating zero DOM deltas for it. We only pay the cost of updatingdata-record-id.Robustness (No Parsing): We eliminate the
getRecordFromLogicalIdparsing logic entirely.
- Old:
id.split('__').pop()-> fragile.- New:
target.dataset.recordId,target.dataset.field-> direct access. This solves edge cases where IDs or fields contain separators.Cleaner Selection Logic: Selection models can read
recordIddirectly to resolve the Record from the Store, without needing to reverse-engineer strings. They can then construct the internal selection key (e.g. forselectedItemsSet) using a consistent helper, but the DOM interface remains pure.Implementation Plan (Follow-up):
- Update
Row.mjsto render splitdataattributes.- Update
Body.mjs/BaseModel.mjsto read fromdataset.recordIdanddataset.field.- Deprecate the complex ID parsing logic in
Body.mjs.

Input from Gemini 3 Pro:
✦ I have completed the optimization of the Grid Row Pooling logic and resolved the selection model regressions.
Summary of Changes:
- Fixed-DOM-Order Strategy: Rows are now pooled and recycled in place within the VDOM, eliminating
moveNodeoperations during scrolling.- Split Dataset: Replaced fragile composite ID strings (
recordId__field) with robustdata-record-idanddata-fieldattributes. This simplifies event handling and avoids parsing errors with complex IDs.- Robust Selection Logic:
- Refactored
BaseModel.updateRowsto correctly handle both Cell and Row selection paths.- Fixed a critical bug where
RowModelchecks used the wrong selection store (itemsvsselectedRows).- Unified
selectRowanddeselectRowto useview.update()for robust visual rendering of row class changes, while maintaining efficient VDOM mutation logic.- Cleanup: Removed duplicate methods and debug logs.
Outcome:
- Scrolling performance is improved (zero structure deltas).
- Cell selection (click + key nav) works flawlessly.
- Row selection (click + key nav) works flawlessly, with no accumulation bugs.
I have merged the changes into
dev.Follow-up Items:
- Further optimize
row.update()to work reliably for root node class changes without triggering full Body updates.- Explore converting
data-record-idto use the record's internal ID if meaningful, or ensure strict type safety.
Currently,
Neo.grid.Bodyrecycles rows by reordering them in thevdom.cnarray to match the logical record order. This triggers extensivemoveNodeDOM operations during scrolling, which is performance-heavy.The Goal: Implement a Fixed-DOM-Order strategy for Rows, similar to the Cell pooling strategy.
Body.vdom.cn.transformand content of the row that "wraps around", without changing its index in thevdom.cnarray.This will reduce scroll deltas to pure attribute updates (transform + content), eliminating layout thrashing caused by node movement.