Frontmatter
| id | 9321 |
| title | Fix VDOM diffing race condition between innerHTML and textContent |
| state | Closed |
| labels | bugai |
| assignees | tobiu |
| createdAt | Feb 27, 2026, 12:16 AM |
| updatedAt | Feb 27, 2026, 12:19 AM |
| githubUrl | https://github.com/neomjs/neo/issues/9321 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Feb 27, 2026, 12:19 AM |
Fix VDOM diffing race condition between innerHTML and textContent
tobiu assigned to @tobiu on Feb 27, 2026, 12:18 AM

tobiu
Feb 27, 2026, 12:18 AM
Input from Gemini 3.1 Pro:
✦ The VDOM diffing logic has been updated and pushed to
dev.
innerHTMLandtextContentnow have their own dedicated execution blocks inHelper.compareAttributes. The logic now correctly handles mutual exclusivity:
- If a VNode transitions from
innerHTMLtotextContent(or vice versa), the engine skips sending aclearcommand (undefined) for the removed property, preventing theDeltaUpdatesfor...inrace condition.- If both properties are genuinely removed, the engine explicitly sends
innerHTML: ''instead ofundefined, keeping the delta payload clean and explicit.Closing this ticket.
tobiu closed this issue on Feb 27, 2026, 12:19 AM
This ticket addresses a severe race condition and delta payload bloat in the VDOM diffing engine (
src/vdom/Helper.mjs) when transitioning betweeninnerHTMLandtextContentstates.The Problem: When a cell component (like in the DevIndex Grid) is recycled, it might switch from using
innerHTMLtotextContent(or vice versa), or simply be cleared out. The previous logic inHelper.compareAttributeshandled these properties generically:case 'innerHTML': case 'textContent': if (value !== oldVnode[prop]) { delta[prop] = value }If a new VNode used
textContent: "new"and the old one hadinnerHTML: "old", the engine would generate:{ innerHTML: undefined, textContent: "new" }When processed by
DeltaUpdates.updateNode(delta), thefor...inloop iteration order is not guaranteed. IfinnerHTML: undefined(which evaluates tonode.innerHTML = '') executes afternode.textContent = "new", the DOM node is incorrectly wiped blank. Furthermore, sendingundefinedover the wire wastes bandwidth.The Fix: Separate
innerHTMLandtextContentinto their own evaluation blocks withincompareAttributes. Implement mutual exclusivity checks:innerHTML(it isundefined), but the new VNode hastextContent, do not send aninnerHTMLdelta. SettingtextContenton the DOM naturally overwrites the oldinnerHTML.textContentbut addinginnerHTML, skip thetextContentclear delta.innerHTML: ''rather than sendingundefined.