Frontmatter
| id | 9071 |
| title | refactor: Adopt ''internalId'' for Stable DOM Keying (List & Data Views) |
| state | Closed |
| labels | refactoringcore |
| assignees | tobiu |
| createdAt | Feb 9, 2026, 11:48 AM |
| updatedAt | Feb 10, 2026, 1:58 AM |
| githubUrl | https://github.com/neomjs/neo/issues/9071 |
| author | tobiu |
| commentsCount | 3 |
| parentIssue | 9074 |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Feb 10, 2026, 1:58 AM |
refactor: Adopt 'internalId' for Stable DOM Keying (List & Data Views)

Input from Gemini 3 Pro:
✦ Fix: Helix/Gallery Navigation, VNode Crash & Store Identity
Critical Fixes:
Store Identity Race Condition (
src/data/Store.mjs):
- Issue:
itemFactorywas assigned aftersuper.construct, causing initial data loads (e.g. ComboBoxdata, HelixloadDatatiming) to bypassinternalIdassignment.- Fix: Moved
itemFactoryassignment to the config object passed tosuper.construct.- Enhancement: Added "Lazy Assignment" to
getInternalId. If an item lacks an ID (e.g. race condition), it is assigned on first access. This is a fail-safe.Helix & Gallery Navigation (
src/selection/HelixModel.mjs,GalleryModel.mjs):
- Issue:
store.indexOf(selected)returned-1becauseselectedis aninternalIdbutindexOfexpects a PK/Record.- Fix: Refactored
onNavKey*to first resolve therecordfrom theinternalId(using robust lookup), then find its index.- Fix: Updated logic to respect
view.maxItems, preventing navigation into "ghost" items (loaded but not rendered).VNode ID Crash (
src/component/Helix.mjs):
- Issue:
expandItemcreated a new VDOM node without an ID, causing the VDOM Worker to reject the update.- Fix: Explicitly assigned a unique ID to the new node.
- Fix: Updated
expandItemanddestroyClonesto use robust record lookup (store.items.findfallback).ComboBox Value Update (
src/form/field/ComboBox.mjs):
- Issue: Selection change event passed
internalId, butstore.getfailed.- Fix: Updated
onListItemSelectionChangeto use the robust record lookup pattern.Refactor:
- Updated
List.Base,ListModel,Gallery.mjsto consistently usegetRecordIdand robust lookups.Outcome: Helix/Gallery navigation, camera movement, expansion, and ComboBox selection are fully restored and robust against ID mismatches.

Input from Gemini 3 Pro:
✦ Correction: Helix Fully Restored, Others WIP
To clarify the previous status update:
✅ Helix Component (Fixed & Verified):
- Navigation: Arrow key navigation is fully restored and respects
maxItems.- Expansion:
Enterto expand/collapse works correctly (fixedVNodeID crash anddestroyClonescrash).- Camera: Movement is synced with selection.
- Bonus: Fixed two pre-existing bugs related to VDOM ID generation and null pointer exceptions during collapse.
⚠️ Work in Progress (ComboBox & Gallery): While logic fixes were applied for
ComboBox(value update) andGallery(camera/selection), they require further verification to be considered "fully restored". TheStorerace condition fix is expected to resolve the underlying ID availability issues for these components, but they remain in a WIP state until confirmed.

Input from Gemini 3 Pro:
✦ I have pushed the fix for
ComboBoxto thedevbranch.Changes:
src/form/field/ComboBox.mjs:
- Fix: Updated
onListItemSelectionChangeto parse the selected item's DOM ID usinglist.getItemRecordId()before attempting to retrieve the record from the store. This ensures compatibility with the newinternalIdsystem used byList.- Tests:
- Added
test/playwright/unit/form/field/ComboBoxInternalId.spec.mjsto verify thatComboBoxselection works correctly when the underlyingListusesinternalId.Outcome: Selecting an item from the ComboBox picker now correctly updates the field's value. The mismatch between DOM IDs (prefixed) and Store IDs (raw) has been resolved.
Context: We have implemented a stable
internalId(e.g.,neo-record-1) for all store items via #9070. Currently,Neo.list.Baseand data views (Gallery,Helix) use the record's primary key (keyProperty) to generate DOM IDs (e.g.,view__100).Problem:
id: null) cause DOM ID collisions.-1->100), forcing a full DOM re-render.Task: Update the base classes for Lists and Data Views to support using
internalIdfor DOM keying.Requirements:
useInternalId: true(defaulting tofalseinitially, ortrueif we are confident) toNeo.list.BaseandNeo.component.Gallery/Helix.getItemIdand item creation logic to usestore.getInternalId(record)when the config is enabled.ListModel,GalleryModel) respect this switch (comparing internal IDs vs record IDs).Scope:
src/list/Base.mjssrc/component/Gallery.mjssrc/component/Helix.mjs