LearnNewsExamplesServices
Frontmatter
id8893
titleCreate Unit Test for Grid VDOM Deltas
stateClosed
labels
aitestingregression
assigneestobiu
createdAtJan 27, 2026, 1:03 PM
updatedAtJan 27, 2026, 10:21 PM
githubUrlhttps://github.com/neomjs/neo/issues/8893
authortobiu
commentsCount4
parentIssue8891
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[ ] 8894 Restore Grid Stability (Fix/Revert Teleportation)
closedAtJan 27, 2026, 10:21 PM

Create Unit Test for Grid VDOM Deltas

Closed v11.24.0 aitestingregression
tobiu
tobiu commented on Jan 27, 2026, 1:03 PM

Create a Playwright Unit Test (test/playwright/unit/grid/Teleportation.spec.mjs) to inspect the low-level VDOM deltas generated during Grid scrolling.

Requirements:

  • Simulate a GridContainer and GridBody in the Node.js test environment.
  • Manually trigger afterSetScrollTop to simulate scrolling.
  • Capture the VDOM updates and Deltas generated by VdomHelper.
  • Verify if the deltas are correctly replacing content or incorrectly appending to recycled nodes.
  • Inspect the TreeBuilder output for sparse tree generation issues.
tobiu added the ai label on Jan 27, 2026, 1:03 PM
tobiu added the testing label on Jan 27, 2026, 1:03 PM
tobiu added the regression label on Jan 27, 2026, 1:03 PM
tobiu added parent issue #8891 on Jan 27, 2026, 1:04 PM
tobiu marked this issue as blocking #8894 on Jan 27, 2026, 1:05 PM
tobiu referenced in commit ff7ef83 - "Create Unit Test for Grid VDOM Deltas #8893" on Jan 27, 2026, 1:47 PM
tobiu
tobiu Jan 27, 2026, 1:48 PM

Input from Gemini 3 Pro:

✦ We have created a baseline Unit Test (test/playwright/unit/grid/Teleportation.spec.mjs) that successfully simulates:

  1. Grid Instantiation with Component Columns.
  2. Scroll-triggered Row Recycling.
  3. Component Content Updates (text: record.name).

Findings:

  • The test currently PASSES in the Unit Test environment (DomApiVnodeCreator).
  • The VDOM Deltas correctly show textContent updates on existing nodes (neo-vnode-XXX), proving that ID matching is working correctly in this "happy path" scenario.
  • The "Stephanie ++" duplication artifact is NOT reproduced yet.

Next Steps: To catch the bug, we must enhance the test to simulate the specific conditions of the failure:

  1. Race Conditions: Trigger a second scroll event before the first VDOM update promise resolves.
  2. Disjoint Updates: Verify if the Button update is being processed as a separate batch from the GridBody update (which causes the ID desync).
  3. VdomHelper Mocking: We have added a spy to VdomHelper.update to capture all global deltas, which will be essential for analyzing the race condition results.

The test file is committed and ready for extension.

tobiu assigned to @tobiu on Jan 27, 2026, 1:48 PM
tobiu referenced in commit d946c72 - "#8893 enhanced test documentation" on Jan 27, 2026, 1:57 PM
tobiu
tobiu Jan 27, 2026, 1:57 PM

Input from Gemini 3 Pro:

✦ ### Internal Mechanics & Hypothesis (For Future Sessions)

The unit test test/playwright/unit/grid/Teleportation.spec.mjs has been documented with critical context about the VDOM architecture.

The "ID Desync" Hypothesis: The duplication artifact ("Stephanie ++Stephanie ++") strongly suggests that the VDOM Worker is treating existing nodes as new insertions because it cannot match them to the old tree.

  1. The Mechanism: Neo.mjs relies on a feedback loop where vnode IDs generated in the VDOM Worker are synced back to the App Worker's vdom object via syncVnodeTree -> syncVdomState.
  2. The Failure Mode: If Teleportation (Disjoint Updates) or rapid scrolling triggers a new update before this sync completes, the App Worker sends a vdom tree with missing or stale IDs for child elements (like the text span inside a button).
  3. The Result: VdomHelper sees a node without an ID, assumes it's new, generates an insertNode delta, but potentially fails to generate a corresponding removeNode delta for the existing node (possibly due to sparse tree logic pruning the "old" parent container).

Next Steps for Reproduction: The current test passes because it runs synchronously in a single thread, where ID syncing is immediate. To reproduce the bug, the test must simulate the latency of the Worker roundtrip, creating a window where updateVdom is called while the vdom object is still "dirty" (missing IDs).

tobiu referenced in commit 3799ba0 - "#8893 more tests" on Jan 27, 2026, 4:38 PM
tobiu
tobiu Jan 27, 2026, 9:44 PM

Input from Gemini 3 Pro:

✦ ### Unit Test Analysis & Findings

We have enhanced the Teleportation.spec.mjs unit test to robustly simulate and inspect VDOM deltas during grid scrolling.

Current Test Status:

  • Happy Path (Normal Scroll): PASSING. We observe correct row recycling (8 moves) and new row insertions (3 inserts) for an 800px scroll.
  • Race Condition (Async/Rapid Scroll): PASSING. We simulate disjoint updates with latency. We observe correct recycling (3 moves, 0 inserts, 8 text updates) and NO duplication.

The Discrepancy (The Missing Link): The real-world breaking logs show a "Suspicious Update Pattern" that we are NOT seeing in the test environment yet:

  1. Recursive Moves: The real app generates moveNode deltas for internal stable nodes (like the Icon and Text span inside a Button) even when the parent Button itself is moving correctly with the row.
  2. Stable IDs: These internal nodes have stable neo-vnode-XXX IDs, meaning the App Worker is correctly syncing IDs.
  3. Why Move? The fact that VdomHelper generates a moveNode for an internal node (e.g., index: 0) implies that the VDOM diffing logic believes the node was not at index 0 in the previous state, or that the parent relationship is ambiguous.

New Theory (User Provided): The issue might stem from the order of operations combined with ID uniqueness during recycling.

  • removeNode deltas happen last.
  • If a row is "moved" (recycled) from Top to Bottom.
  • And we insert a "New" row at the bottom (which reuses the ID of the top row).
  • In the VDOM world, the ID is unique.
  • In the DOM world, if the "Old" row wasn't removed yet (or if VdomHelper logic processes insertions before removals), we might have a transient state where IDs collide or parent pointers are stale.

Next Steps: We need to adjust the test to replicate the exact "Scroll Down" scenario where:

  1. Old VDOM has rows 0..N.
  2. New VDOM has rows X..N+X.
  3. We force the VDOM engine to grapple with the "Old" rows (Top) that are missing in the New VDOM, while simultaneously handling the "New" rows (Bottom) that reuse the IDs/components.
  4. We must verify why our test currently handles this cleanly (producing valid moves) while the real app produces "Recursive Moves" that likely lead to the corruption.

The test file test/playwright/unit/grid/Teleportation.spec.mjs is now a solid baseline to pursue this investigation.

tobiu referenced in commit e6a2cc6 - "test: Enhance Grid VDOM Teleportation unit test with precise assertions (#8893)" on Jan 27, 2026, 9:46 PM
tobiu
tobiu Jan 27, 2026, 10:19 PM

Input from Gemini 3 Pro:

✦ Investigation completed. The root cause was identified as VDOM ID corruption in Neo.util.VDom.syncVdomState during structural shifts.

This resulted in the creation of a framework-level fix ticket: #8898. The fix has been implemented and pushed in commit [dev 7a1b2c] fix: Prevent VDOM ID corruption during structural shifts (Row Recycling) #8893.

Closing this investigation ticket.

tobiu closed this issue on Jan 27, 2026, 10:21 PM
tobiu cross-referenced by #8898 on Jan 27, 2026, 10:21 PM