Frontmatter
| id | 8906 |
| title | Preserve Functional Component Scroll State with Early ID Generation |
| state | Closed |
| labels | bugaicore |
| assignees | tobiu |
| createdAt | Jan 29, 2026, 3:04 PM |
| updatedAt | Jan 29, 2026, 4:06 PM |
| githubUrl | https://github.com/neomjs/neo/issues/8906 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Jan 29, 2026, 4:06 PM |
Preserve Functional Component Scroll State with Early ID Generation
tobiu assigned to @tobiu on Jan 29, 2026, 4:05 PM

tobiu
Jan 29, 2026, 4:05 PM
Input from Gemini 3 Pro:
✦ I have implemented the fix for preserving functional component scroll state.
Changes:
- Early VDOM Update:
continueUpdateWithVdomnow updatesme.vdomwith the parsed VDOM before identifying the root and assigning IDs.- State Preservation:
processVdomForComponentsnow accepts avnodeMap(O(1) lookup) to efficiently copyscrollTopandscrollLeftfrom the persistent VNode to the new VDOM.- VdomLifecycle Hardening: Added a null check for
me.vnodeinsyncVnodeTreeto prevent crashes during initialization sequences where the vnode might not yet exist.- Tests: Added
test/playwright/unit/functional/ScrollState.spec.mjsto verify state preservation for both root and child nodes. Verified against existing ID generation and Button tests.The solution ensures that transient state like scroll position, which is captured from the main thread into the App Worker's VNode tree, is not lost when a Functional Component regenerates its VDOM structure.
tobiu closed this issue on Jan 29, 2026, 4:06 PM
Problem: Functional Components recreate their VDOM on every update. This wiped transient state stored on the VDOM object, such as
scrollTopcaptured from the Main Thread viaonScrollCapture. WhenVDom.syncVdomStateran, it sometimes overwrote valid scroll state with stale VNode state. Additionally, late ID generation caused state preservation logic to fail for child nodes because they lacked IDs during processing.Solution:
continueUpdateWithVdom,me.vdomis now updated with the newparsedVdomimmediately. The root is then identified usingme.getVdomRoot()and the component ID is assigned unconditionally and before ID generation. This ensuresgenerateIdsuses the correct scope prefix.processVdomForComponentsnow accepts a pre-calculatedvnodeMap(flattened fromme.vnode). It uses this map to preservescrollTop/scrollLeftfrom the persistent VNode tree to the new VDOM tree efficiently.continueUpdateWithVdomwas refactored to support this new flow, removing redundant logic.Verification:
test/playwright/unit/functional/ScrollState.spec.mjsverifies state preservation for root and child nodes.IdGeneration.spec.mjsandButton.spec.mjspassed.