---
id: 9012
title: Optimize Grid Row Pooling to eliminate moveNode deltas
state: CLOSED
labels:
  - performance
  - core
assignees:
  - tobiu
createdAt: '2026-02-06T02:20:37Z'
updatedAt: '2026-02-06T16:16:53Z'
githubUrl: 'https://github.com/neomjs/neo/issues/9012'
author: tobiu
commentsCount: 3
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-02-06T16:16:48Z'
---
# Optimize Grid Row Pooling to eliminate moveNode deltas

Currently, `Neo.grid.Body` recycles rows by reordering them in the `vdom.cn` array to match the logical record order. This triggers extensive `moveNode` DOM operations during scrolling, which is performance-heavy.

The Goal: Implement a **Fixed-DOM-Order** strategy for Rows, similar to the Cell pooling strategy.
1.  Keep Row component references in a stable order in `Body.vdom.cn`.
2.  Use CSS transforms (already in place) to position rows visually.
3.  When scrolling, simply update the `transform` and content of the row that "wraps around", without changing its index in the `vdom.cn` array.

This will reduce scroll deltas to pure attribute updates (transform + content), eliminating layout thrashing caused by node movement.

## Timeline

- 2026-02-06T02:20:38Z @tobiu added the `performance` label
- 2026-02-06T02:20:38Z @tobiu added the `core` label
- 2026-02-06T02:20:58Z @tobiu assigned to @tobiu
- 2026-02-06T14:51:07Z @tobiu referenced in commit `13e4a08` - "feat: Optimize Grid Row Pooling and Selection Logic (#9012)"
### @tobiu - 2026-02-06T14:51:24Z

**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 `moveNode` deltas during scrolling. However, we are facing a critical regression in **Cell Selection**, which is currently intermittent (works on scroll, fails on click).
> 
> ### Current State
> 1.  **Fixed-DOM-Order**: Implemented in `GridBody.mjs` (recycle rows in place) and `Row.mjs`.
> 2.  **Logical IDs**: Standardized usage of `logicalId` (`recordId__field`) across `Body`, `Row`, and all `SelectionModels`.
> 3.  **Selection Refactor**: `BaseModel` now 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**: `updateRows` in `BaseModel` is failing to trigger the update on click. This implies either:
>     - `getRecord` logic is failing to find the record (likely `parseInt` vs string mismatch or ID parsing issues).
>     - `DomEvent` delegation 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
> 1.  **Analyze Logs**: Run the app with the current branch and check console for `updateRows` output.
> 2.  **Verify Event Chain**: Confirm `onCellClick` -> `toggleSelection` -> `select` -> `updateRows` is executing.
> 3.  **Fix ID Parsing**: Ensure `getRecord` and `getRecordFromLogicalId` perfectly match the ID structure generated by `Row`.
> 
> We are moving this to a fresh session to clear context and focus purely on debugging this interaction.

### @tobiu - 2026-02-06T14:55:49Z

**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 `dataset` attributes**
> 
> Instead of `data-cell-id="100__firstname"`, we should render:
> ```html
> <div class="neo-grid-cell" data-record-id="100" data-field="firstname">...</div>
> ```
> 
> **Benefits:**
> 
> 1.  **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 updating `data-record-id`.
> 
> 2.  **Robustness (No Parsing):**
>     We eliminate the `getRecordFromLogicalId` parsing 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.
> 
> 3.  **Cleaner Selection Logic:**
>     Selection models can read `recordId` directly to resolve the Record from the Store, without needing to reverse-engineer strings. They can then construct the internal selection key (e.g. for `selectedItems` Set) using a consistent helper, but the DOM interface remains pure.
> 
> **Implementation Plan (Follow-up):**
> 1.  Update `Row.mjs` to render split `data` attributes.
> 2.  Update `Body.mjs` / `BaseModel.mjs` to read from `dataset.recordId` and `dataset.field`.
> 3.  Deprecate the complex ID parsing logic in `Body.mjs`.

- 2026-02-06T16:16:17Z @tobiu referenced in commit `053bf9a` - "feat: Implement Fixed-DOM-Order Row Pooling, Split Dataset, and Robust Selection (#9012)"
- 2026-02-06T16:16:32Z @tobiu cross-referenced by PR #9013
- 2026-02-06T16:16:49Z @tobiu closed this issue
### @tobiu - 2026-02-06T16:16:53Z

**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:**
> 
> 1.  **Fixed-DOM-Order Strategy:** Rows are now pooled and recycled in place within the VDOM, eliminating `moveNode` operations during scrolling.
> 2.  **Split Dataset:** Replaced fragile composite ID strings (`recordId__field`) with robust `data-record-id` and `data-field` attributes. This simplifies event handling and avoids parsing errors with complex IDs.
> 3.  **Robust Selection Logic:**
>     *   Refactored `BaseModel.updateRows` to correctly handle both Cell and Row selection paths.
>     *   Fixed a critical bug where `RowModel` checks used the wrong selection store (`items` vs `selectedRows`).
>     *   Unified `selectRow` and `deselectRow` to use `view.update()` for robust visual rendering of row class changes, while maintaining efficient VDOM mutation logic.
> 4.  **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-id` to use the record's internal ID if meaningful, or ensure strict type safety.

- 2026-02-06T16:26:45Z @tobiu cross-referenced by #9014

