LearnNewsExamplesServices
Frontmatter
id19282
titleverifyComponentConsistency reports a vdom node withheld from the DOM as a mismatch
stateClosed
labels
bugaiagent-os
assigneesneo-opus-vega
createdAtSep 26, 2026, 3:25 PM
updatedAtSep 26, 2026, 3:51 PM
githubUrlhttps://github.com/neomjs/neo/issues/19282
authorneo-opus-vega
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 26, 2026, 3:49 PM

verifyComponentConsistency reports a vdom node withheld from the DOM as a mismatch

neo-opus-vega
neo-opus-vega commented on Sep 26, 2026, 3:25 PM

Context

Measured on dev e3d83f1b91 while probing TabHeaderActionsNL (#17844's census row). After a tab drag that leaves the container unfocused, app.verifyComponentConsistency(tabBarId) (the Neural Link's verify_component_consistency) returns:

consistent: false, counts: {dom: 5, items: 6, vdom: 6}
mismatches: [{type: 'order-or-membership', surfaces: ['vdom', 'dom'],
  a: [tab-1, tab-2, tab-3, spacer, next-tab, previous-tab], b: [tab-1, tab-2, tab-3, spacer, next-tab]}]

The missing DOM member is the withdrawn previous-tab action. With focus back in the container (the action revealed) the same call reports consistent: true with six nodes on every surface.

The Problem

Neo.ai.client.ComponentService#verifyComponentConsistency (src/ai/client/ComponentService.mjs:165–:178) reads the vdom surface as every cn entry's componentId or id, then diffChildSurfaces compares that list to the rendered child ids. A vdom node stamped removeDom: true is, by contract, present in the vdom and absent from the DOM: toolbar.Base#applyContextualActionState withdraws a focus-gated action exactly that way and raises domWithheld so show() keeps it out (#18069, src/toolbar/Base.mjs:277–:278). So every toolbar with a withdrawn action is "inconsistent" to the tool, and a spec that asserts consistency after an interaction that unfocuses the container reads a defect where there is none. The checker cannot tell a withheld node from a dropped one.

The Architectural Reality

  • verifyComponentConsistency builds vdomIds from component.getVdomItemsRoot?.()?.cn || component.vdom?.cn; the removeDom flag sits on those same nodes.
  • ComponentService.diffChildSurfaces({componentId, domIds, itemIds, vdomIds}) is static and pure, covered by test/playwright/unit/ai/client/ComponentService.spec.mjs (arms at :79–:118: consistent surfaces, duplicates, items-vs-vdom order, missing DOM root).
  • items still holds the withdrawn instance, so the items-vs-vdom comparison must keep seeing it; only the DOM expectation changes.

The Fix

src/ai/client/ComponentService.mjs:

  1. verifyComponentConsistency collects withheldIds, the ids of cn entries with removeDom === true, beside vdomIds.
  2. diffChildSurfaces takes withheldIds (default empty): the vdom-vs-dom comparison runs on vdomIds minus withheldIds; a withheld id that is in the DOM is its own mismatch (type: 'withheld-node-rendered'). The result carries withheldIds so a reader sees what was excused.
  3. Two arms in ComponentService.spec.mjs: a withheld node absent from the DOM is consistent; a withheld node present in the DOM is reported.

Acceptance Criteria

  • AC-1: diffChildSurfaces with one withheld id absent from domIds reports no mismatch and lists the id under withheldIds.
  • AC-2: diffChildSurfaces with a withheld id present in domIds reports withheld-node-rendered.
  • AC-3: red-first: AC-1's arm fails on dev with the order-or-membership mismatch.
  • AC-4: TabHeaderActionsNL:288 ("post-drag items, VDOM, and DOM remain exact") passes with the container unfocused after the drag, once the arm reaches it (it currently dies at :269, #19281).

Out of Scope

  • The focus loss that unfocuses the container after a tab drag: #19281.
  • Any other reader of removeDom.

Decision Record impact

none

Related

#17844 · #18069 (the withheld-node contract) · #19281 (the tab drag's focus)

unowned-rationale: one-PR, about twenty lines plus two unit arms; parked so the focus ticket lands first. Any seat may take it; I take it afterwards if it is still open.

Origin Session ID: e2fd8a01-5bfc-4d82-ad5b-5c889f4b6710 Retrieval Hint: "verifyComponentConsistency removeDom withheld vdom node dom mismatch diffChildSurfaces"

tobiu referenced in commit 108b6cc - "fix(ai): verifyComponentConsistency expects a withheld vdom node to be absent from the DOM (#19282) (#19287) on Sep 26, 2026, 3:49 PM
tobiu closed this issue on Sep 26, 2026, 3:49 PM