Frontmatter
| id | 8814 |
| title | Race condition in Portal Toolbar causes duplicate DOM nodes |
| state | Closed |
| labels | bugai |
| assignees | [] |
| createdAt | Jan 19, 2026, 4:08 PM |
| updatedAt | Jan 20, 2026, 4:31 AM |
| githubUrl | https://github.com/neomjs/neo/issues/8814 |
| author | tobiu |
| commentsCount | 4 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Jan 20, 2026, 4:31 AM |
Race condition in Portal Toolbar causes duplicate DOM nodes


Input from Gemini 3 Pro:
✦ I have conducted a deep investigation into the race condition and encountered a significant logical conflict in the test suite that requires a fresh look.
The Conflict
We have two existing test suites with contradictory expectations regarding VDOM update merging at
updateDepth: 1(the default):
RealWorldUpdates.spec.mjs: Fails unless merging is enabled for direct children (Depth 1, Distance 1). It expects a parent update to return child deltas. This implieshasUpdateCollisionshould use<=(so1 <= 1is true).AsymmetricUpdates.spec.mjs: Explicitly expects NO collision for Depth 1 vs Distance 1. It assertsexpect(hasCollision).toBe(false). This implieshasUpdateCollisionshould use<(so1 < 1is false).The Paradox
The original codebase used
<(verified). However,RealWorldUpdatesreportedly passed. This is mathematically impossible under the standard merging logic: if1 < 1is false, the child does not merge, the parent updates alone (Depth 1), prunes the child placeholder, and returns 0 deltas (failing the test).Hypothesis: There is a "hidden variable" or mechanism in the original passing state that allowed
RealWorldUpdatesto pass despite<. Possibilities:
parent.updateDepthwas somehow initialized to > 1 (e.g., viaContainerlogic or test setup side effects).- The test environment (
domApiRenderer: true) interacts withTreeBuilderdifferently than analyzed.The Proposed Fix (Verified in Isolation)
I have implemented a race condition guard that successfully fixes the original "Duplicate DOM Node" issue:
VDomUpdate.hasInFlightDescendant(ownerId, updateDepth): Detects if any descendant is currently updating.VdomLifecycle.isChildUpdating(resolve): Defers the parent's update if a descendant is in-flight, preventing the parent from overwriting the child's concurrent work.This fix works perfectly for
RaceCondition.spec.mjs. The blocker is integrating it without breaking the conflicting legacy tests.Next Steps for Fresh Session
- Checkout
dev(Clean State): RunRealWorldUpdates.spec.mjsimmediately with logging to confirm it passes and inspectparent.updateDepthat runtime. This will solve the "Why did it pass?" mystery.- Apply Fix: Re-apply the
isChildUpdatinglogic.- Resolve Conflict: If
RealWorldUpdatespasses ondevdue to a specific depth, ensure my fix preserves that state. If it relies on<=(merging), we must reconcile it withAsymmetricUpdates(possibly by updating the test expectation if the architecture should support merging).

Input from Gemini 3 Pro:
✦ I have pushed the current investigation state to a new feature branch:
feat/8814-race-condition.Branch State:
- Code:
src/mixin/VdomLifecycle.mjshas the newisChildUpdatinglogic enabled (uncommented).hasUpdateCollisionuses<(default dev state, preventing direct child merging).- Tests:
test/playwright/unit/vdom/RaceCondition.spec.mjs: PASSES. Confirms theisChildUpdatingfix works for the specific race condition.test/playwright/unit/vdom/RealWorldUpdates.spec.mjs: FAILS. Fails because it expects merging (Child updates merged into Parent), but<logic prevents it at default depth. Logging added to inspect depth.test/playwright/unit/vdom/AsymmetricUpdates.spec.mjs: FAIL/PASS. Adjusted to expecttrue(collision) in an attempt to reconcile with<=, but current code is<.How to Use: Future sessions can diff
feat/8814-race-conditionagainstdevto see the exact implementation of the race condition fix and the new tests (RaceCondition.spec.mjs,Collision.spec.mjs,ReverseCollision.spec.mjs).Recommended Diff:
git diff dev...feat/8814-race-conditionThis branch captures the "broken" state where the fix works for the new bug but exposes the latent conflict with
RealWorldUpdates. It serves as the starting point for resolving the "Merging vs. Blocking" architectural paradox.

Fix implemented and verified with new test suite. Merging to dev.
Duplicate DOM nodes appear in the Portal Toolbar when navigating to a ticket route (e.g.,
#/news/tickets/8691). This appears to be a race condition involving rapidhidden: falseupdates triggered by state bindings onPageContainer, causing both the parent container and the child component to attempt to insert the child node into the DOM simultaneously.Reproduction
#/news/tickets/8691(or similar).Expected Behavior
The DOM should exactly match the VDOM (one button instance).
Hypotheses
hiddentoggles.VdomLifecycleorVDomUpdatenot correctly merging or locking updates duringmountorinsertNodeoperations.