LearnNewsExamplesServices
Frontmatter
id9012
titleOptimize Grid Row Pooling to eliminate moveNode deltas
stateClosed
labels
performancecore
assigneestobiu
createdAtFeb 6, 2026, 3:20 AM
updatedAtFeb 6, 2026, 5:16 PM
githubUrlhttps://github.com/neomjs/neo/issues/9012
authortobiu
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtFeb 6, 2026, 5:16 PM

Optimize Grid Row Pooling to eliminate moveNode deltas

Closed v12.0.0 performancecore
tobiu
tobiu commented on Feb 6, 2026, 3:20 AM

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.

tobiu added the performance label on Feb 6, 2026, 3:20 AM
tobiu added the core label on Feb 6, 2026, 3:20 AM
tobiu assigned to @tobiu on Feb 6, 2026, 3:20 AM
tobiu referenced in commit 13e4a08 - "feat: Optimize Grid Row Pooling and Selection Logic (#9012)" on Feb 6, 2026, 3:51 PM
tobiu
tobiu Feb 6, 2026, 3:51 PM

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
tobiu Feb 6, 2026, 3:55 PM

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:

<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.
tobiu referenced in commit 053bf9a - "feat: Implement Fixed-DOM-Order Row Pooling, Split Dataset, and Robust Selection (#9012)" on Feb 6, 2026, 5:16 PM
tobiu cross-referenced by PR #9013 on Feb 6, 2026, 5:16 PM
tobiu closed this issue on Feb 6, 2026, 5:16 PM
tobiu
tobiu Feb 6, 2026, 5:16 PM

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.
tobiu cross-referenced by #9014 on Feb 6, 2026, 5:26 PM