LearnNewsExamplesServices
Frontmatter
id9469
titleTreeStore: internalIdMap desyncs after bulk operations (expandAll), breaking selection
stateClosed
labels
bugai
assigneestobiu
createdAtMar 13, 2026, 7:17 PM
updatedAtMar 13, 2026, 7:32 PM
githubUrlhttps://github.com/neomjs/neo/issues/9469
authortobiu
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMar 13, 2026, 7:18 PM

TreeStore: internalIdMap desyncs after bulk operations (expandAll), breaking selection

Closed v12.1.0 bugai
tobiu
tobiu commented on Mar 13, 2026, 7:17 PM

The #rebuildKeysAndCount method in Neo.data.TreeStore was successfully rebuilding the main map, but it failed to synchronize the internalIdMap.

Because expandAll (and similar bulk operations) deliberately bypass the standard me.items = newItems setter to maintain Turbo Mode efficiency, it must manually replicate the internal map synchronization.

Without this, when a user clicks on a row after expandAll, the SelectionModel resolves the event target to an internalId (data-record-id), but the internalIdMap is stale. This causes getRow to fail, returning null, and resulting in no selection being applied visually.

Fix:

  • Updated #rebuildKeysAndCount to clear and properly populate me.internalIdMap when me.trackInternalId is true.
  • Declared the loop variables (internalId, item, key) outside of the for loop for V8 optimization, as this method operates on potentially massive arrays.
  • Added comprehensive E2E tests inside test/playwright/e2e/GridTreeBigData.spec.mjs to explicitly cover these edge cases ("Selection happy path" and "Selection persists correctly after structural changes (BUG REPRODUCTION)").
  • Added a specific unit test in test/playwright/unit/data/TreeStore.spec.mjs to prove the exact hydration mechanic and indexOf stability.
tobiu added the bug label on Mar 13, 2026, 7:17 PM
tobiu added the ai label on Mar 13, 2026, 7:17 PM
tobiu referenced in commit 223963c - "fix(data.TreeStore): Properly sync internalIdMap in #rebuildKeysAndCount after bulk operations to prevent selection failures (#9469)" on Mar 13, 2026, 7:17 PM
tobiu assigned to @tobiu on Mar 13, 2026, 7:17 PM
tobiu
tobiu Mar 13, 2026, 7:18 PM

Input from Gemini 3.1 Pro:

✦ I have successfully committed and pushed the fix along with the E2E and unit test coverage. The internalIdMap desynchronization has been fully resolved.

tobiu closed this issue on Mar 13, 2026, 7:18 PM
tobiu referenced in commit 3cb518d - "docs(data.TreeStore): Document internalIdMap synchronization requirements in #rebuildKeysAndCount for Turbo Mode (#9469)" on Mar 13, 2026, 7:30 PM
tobiu
tobiu Mar 13, 2026, 7:31 PM

Input from Gemini 3.1 Pro:

✦ Following up:

  • I removed the temporary console.log statements from both the E2E and Unit test files.
  • I applied the "Anchor & Echo" Knowledge Base Enhancement Strategy to #rebuildKeysAndCount, documenting the exact architectural reasoning behind why getInternalId() MUST be called to synchronize the internalIdMap in Turbo Mode.
  • These final test cleanups and documentation updates have been committed and pushed to dev.
tobiu
tobiu Mar 13, 2026, 7:32 PM

Input from Gemini 3.1 Pro:

✦ To clarify the actual root cause for future reference:

While synchronizing the internalIdMap inside #rebuildKeysAndCount was the correct architectural step, the initial attempt still failed in Turbo Mode.

The critical 1-liner fix was changing: internalId = me.getInternalKey(item); to: internalId = me.getInternalId(item);

Why? In Turbo Mode (autoInitRecords: false), the _items array contains raw data objects. getInternalKey() only reads an existing [Symbol.for('Neo.internalId')], which is undefined on raw objects that haven't been rendered yet. getInternalId(), on the other hand, forces the creation of the ID if it doesn't exist. Without this, the internalIdMap was completely ignoring the unhydrated nodes, causing RowModel selections to fail when trying to map the DOM's data-record-id back to the record!

tobiu referenced in commit ee2c3fe - "chore: Add test-e2e script to package.json (#9469)" on Mar 13, 2026, 7:36 PM