Frontmatter
| id | 9469 |
| title | TreeStore: internalIdMap desyncs after bulk operations (expandAll), breaking selection |
| state | Closed |
| labels | bugai |
| assignees | tobiu |
| createdAt | Mar 13, 2026, 7:17 PM |
| updatedAt | Mar 13, 2026, 7:32 PM |
| githubUrl | https://github.com/neomjs/neo/issues/9469 |
| author | tobiu |
| commentsCount | 3 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Mar 13, 2026, 7:18 PM |
TreeStore: internalIdMap desyncs after bulk operations (expandAll), breaking selection

Input from Gemini 3.1 Pro:
✦ I have successfully committed and pushed the fix along with the E2E and unit test coverage. The
internalIdMapdesynchronization has been fully resolved.

Input from Gemini 3.1 Pro:
✦ Following up:
- I removed the temporary
console.logstatements 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 whygetInternalId()MUST be called to synchronize theinternalIdMapin Turbo Mode.- These final test cleanups and documentation updates have been committed and pushed to
dev.

Input from Gemini 3.1 Pro:
✦ To clarify the actual root cause for future reference:
While synchronizing the
internalIdMapinside#rebuildKeysAndCountwas 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_itemsarray contains raw data objects.getInternalKey()only reads an existing[Symbol.for('Neo.internalId')], which isundefinedon 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, theinternalIdMapwas completely ignoring the unhydrated nodes, causingRowModelselections to fail when trying to map the DOM'sdata-record-idback to the record!
The
#rebuildKeysAndCountmethod inNeo.data.TreeStorewas successfully rebuilding the main map, but it failed to synchronize theinternalIdMap.Because
expandAll(and similar bulk operations) deliberately bypass the standardme.items = newItemssetter to maintain Turbo Mode efficiency, it must manually replicate the internal map synchronization.Without this, when a user clicks on a row after
expandAll, theSelectionModelresolves the event target to aninternalId(data-record-id), but theinternalIdMapis stale. This causesgetRowto fail, returningnull, and resulting in no selection being applied visually.Fix:
#rebuildKeysAndCountto clear and properly populateme.internalIdMapwhenme.trackInternalIdis true.internalId,item,key) outside of theforloop for V8 optimization, as this method operates on potentially massive arrays.test/playwright/e2e/GridTreeBigData.spec.mjsto explicitly cover these edge cases ("Selection happy path" and "Selection persists correctly after structural changes (BUG REPRODUCTION)").test/playwright/unit/data/TreeStore.spec.mjsto prove the exact hydration mechanic andindexOfstability.