LearnNewsExamplesServices
Frontmatter
id8850
titleFix VDOM ID Mismatch in Cross-Window Move (LivePreview Helix Detach)
stateClosed
labels
bugairegressioncore
assigneestobiu
createdAtJan 21, 2026, 1:46 PM
updatedAtJan 21, 2026, 4:18 PM
githubUrlhttps://github.com/neomjs/neo/issues/8850
authortobiu
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJan 21, 2026, 4:18 PM

Fix VDOM ID Mismatch in Cross-Window Move (LivePreview Helix Detach)

Closed v11.23.0 bugairegressioncore
tobiu
tobiu commented on Jan 21, 2026, 1:46 PM

The VDOM createDeltas method is failing with an ID mismatch error during a cross-window move operation involving LivePreview.

Scenario:

  1. Open Portal App Home.
  2. Interact with the "Helix" Live Preview (Portal.view.home.parts.Helix), which renders examples.component.multiWindowHelix.
  3. Click "Detach Window" for the Helix Controls panel.
  4. Result: The new window opens and renders correctly, BUT the original content in the main window is NOT removed.
  5. Error: The VDOM worker throws: Error: createDeltas() must be called for the same node. new: {id: neo-container-35, cId: neo-container-35}, old: {id: undefined, cId: neo-container-35}

Analysis: The error old: {id: undefined, cId: neo-container-35} suggests that the vnodeMap lookup for the old node is failing to find the correct VDOM node, likely because id is missing on the stored VNode, even though componentId is present.

This typically happens when moveNode attempts to process a move for a component reference. The Teleportation update might be generating disjoint payloads that interact poorly with moveNode when a component is being moved out of a container (detachment).

We need to investigate vdom.Helper.createDeltas and moveNode logic, specifically how they handle component placeholders during batched updates.

tobiu added the bug label on Jan 21, 2026, 1:46 PM
tobiu added the ai label on Jan 21, 2026, 1:46 PM
tobiu added the regression label on Jan 21, 2026, 1:46 PM
tobiu added the core label on Jan 21, 2026, 1:46 PM
tobiu referenced in commit 37e9a9c - "fix(VdomLifecycle): Fix Teleportation race conditions and cross-window leaks (#8850)" on Jan 21, 2026, 3:11 PM
tobiu assigned to @tobiu on Jan 21, 2026, 3:12 PM
tobiu
tobiu Jan 21, 2026, 3:12 PM

Input from Gemini 3 Pro:

✦ Fixed critical regressions in VDOM Teleportation and SharedWorker support.

Summary of Fixes:

  1. Race Condition (src/mixin/VdomLifecycle.mjs):

    • Problem: executeVdomUpdate was synchronously snapshotting the VDOM via collectPayloads before subsequent synchronous DOM mutations (like Container.remove) in the same tick could complete. This resulted in "stale payloads" where removed components persisted as "Ghost Content".
    • Fix: Added await new Promise(resolve => setTimeout(resolve, 1)) to executeVdomUpdate. This forces a Macrotask yield, ensuring collectPayloads runs after all pending synchronous logic and microtasks have settled, capturing the correct clean state.
  2. Cross-Window Leak (src/mixin/VdomLifecycle.mjs):

    • Problem: Merged components (children) that moved to a new window were still included in the old parent's update batch. This caused deltas meant for the New Window (e.g., hiding a header) to be applied to the Old Window, corrupting the "Ghost Panel" state.
    • Fix: Added filtering in collectPayloads to strictly exclude components where component.windowId !== me.windowId.
  3. Blocked Update Registry (src/manager/VDomUpdate.mjs):

    • Problem: unregisterInFlightUpdate used getParentIds(component) to clear the "in-flight" status from ancestors. If a component moved during the update, this function returned the new parents, leaving the old parent (Viewport) permanently blocked (isChildUpdating = true).
    • Fix: Refactored to iterate descendantInFlightMap directly, ensuring all registered ancestors are cleared regardless of hierarchy changes.
  4. Component ID Stability (src/manager/Component.mjs):

    • Problem: Placeholders stored in me.vnode were missing id if they matched componentId, causing strict diffing errors in vdom.Helper.
    • Fix: Updated addVnodeComponentReferences to always assign id.
  5. Race Condition (examples/component/multiWindowHelix/ViewportController.mjs):

    • Problem: Logic toggled header.hidden before re-attaching the panel to the viewport, causing an independent update that raced with the insert.
    • Fix: Swapped order to ensure correct hierarchy merging.

Result: The Helix example now correctly detaches and re-attaches the controls panel across windows without artifacts, ghost content, or errors.

tobiu referenced in commit c3ebf6b - "fix(Gallery): Fix re-attach race condition and add regression test (#8850)" on Jan 21, 2026, 3:17 PM
tobiu closed this issue on Jan 21, 2026, 4:18 PM