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:
verifyComponentConsistency collects withheldIds, the ids of cn entries with removeDom === true, beside vdomIds.
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.
- 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
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"
Context
Measured on dev
e3d83f1b91while probingTabHeaderActionsNL(#17844's census row). After a tab drag that leaves the container unfocused,app.verifyComponentConsistency(tabBarId)(the Neural Link'sverify_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-tabaction. With focus back in the container (the action revealed) the same call reportsconsistent: truewith six nodes on every surface.The Problem
Neo.ai.client.ComponentService#verifyComponentConsistency(src/ai/client/ComponentService.mjs:165–:178) reads the vdom surface as everycnentry'scomponentIdorid, thendiffChildSurfacescompares that list to the rendered child ids. A vdom node stampedremoveDom: trueis, by contract, present in the vdom and absent from the DOM:toolbar.Base#applyContextualActionStatewithdraws a focus-gated action exactly that way and raisesdomWithheldsoshow()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
verifyComponentConsistencybuildsvdomIdsfromcomponent.getVdomItemsRoot?.()?.cn || component.vdom?.cn; theremoveDomflag sits on those same nodes.ComponentService.diffChildSurfaces({componentId, domIds, itemIds, vdomIds})is static and pure, covered bytest/playwright/unit/ai/client/ComponentService.spec.mjs(arms at:79–:118: consistent surfaces, duplicates, items-vs-vdom order, missing DOM root).itemsstill 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:verifyComponentConsistencycollectswithheldIds, the ids ofcnentries withremoveDom === true, besidevdomIds.diffChildSurfacestakeswithheldIds(default empty): the vdom-vs-dom comparison runs onvdomIdsminuswithheldIds; a withheld id that is in the DOM is its own mismatch (type: 'withheld-node-rendered'). The result carrieswithheldIdsso a reader sees what was excused.ComponentService.spec.mjs: a withheld node absent from the DOM is consistent; a withheld node present in the DOM is reported.Acceptance Criteria
diffChildSurfaceswith one withheld id absent fromdomIdsreports no mismatch and lists the id underwithheldIds.diffChildSurfaceswith a withheld id present indomIdsreportswithheld-node-rendered.order-or-membershipmismatch.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
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"