fix: bind route scope to window name only for @PreserveOnRefresh - #527
Merged
Conversation
Route scope storage was keyed by window name whenever the extended client details reported one. That check used to be an implicit `@PreserveOnRefresh` detector: up to Vaadin 24, Flow requested the window name only for preserved navigation chains. Since vaadin/flow#22719 the client details are collected during UI initialization and every tab gets a window name, so the check is always true. All `@RouteScoped` beans became shared between the UIs of the same browser window. When two such UIs are alive at the same time (duplicated tab, restored session, or reloads faster than the unload beacons that close the previous UI), the second UI is given a route target that still belongs to the first UI's state tree, and navigation fails with "Can't move a node from one state tree to another". The storage is now bound to the window only when the navigation target or one of its layouts is annotated with `@PreserveOnRefresh`, mirroring Flow's isPreserveOnRefreshTarget rule. Otherwise it is bound to a single UI and destroyed on its detach. Fixes #526
mshabarov
reviewed
Aug 3, 2026
The storage key of an owner switches between window and UI binding when navigating between a preserved and a plain child of the same layout. The storage is now relocated to the new key instead of letting a second one be created, so the beans of a layout that stays in the navigation chain are not recreated. Taking over the window storage is skipped while another UI of the same window is still alive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mshabarov
approved these changes
Aug 4, 2026
mcollovati
added a commit
that referenced
this pull request
Aug 4, 2026
) (#528) * fix: bind route scope to window name only for `@PreserveOnRefresh` Route scope storage was keyed by window name whenever the extended client details reported one. That check used to be an implicit `@PreserveOnRefresh` detector: up to Vaadin 24, Flow requested the window name only for preserved navigation chains. Since vaadin/flow#22719 the client details are collected during UI initialization and every tab gets a window name, so the check is always true. All `@RouteScoped` beans became shared between the UIs of the same browser window. When two such UIs are alive at the same time (duplicated tab, restored session, or reloads faster than the unload beacons that close the previous UI), the second UI is given a route target that still belongs to the first UI's state tree, and navigation fails with "Can't move a node from one state tree to another". The storage is now bound to the window only when the navigation target or one of its layouts is annotated with `@PreserveOnRefresh`, mirroring Flow's isPreserveOnRefreshTarget rule. Otherwise it is bound to a single UI and destroyed on its detach. Fixes #526 * fix: keep route scope storage when its owner changes scope The storage key of an owner switches between window and UI binding when navigating between a preserved and a plain child of the same layout. The storage is now relocated to the new key instead of letting a second one be created, so the beans of a layout that stays in the navigation chain are not recreated. Taking over the window storage is skipped while another UI of the same window is still alive. --------- Co-authored-by: Marco Collovati <marco@vaadin.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Route scope storage was keyed by window name whenever the extended client details reported one. That check used to be an implicit
@PreserveOnRefreshdetector: up to Vaadin 24, Flow requested the window name only for preserved navigation chains.Since vaadin/flow#22719 the client details are collected during UI initialization and every tab gets a window name, so the check is always true. All
@RouteScopedbeans became shared between the UIs of the same browser window. When two such UIs are alive at the same time (duplicated tab, restored session, or reloads faster than the unload beacons that close the previous UI), the second UI is given a route target that still belongs to the first UI's state tree, and navigation fails with "Can't move a node from one state tree to another".The storage is now bound to the window only when the navigation target or one of its layouts is annotated with
@PreserveOnRefresh, mirroring Flow's isPreserveOnRefreshTarget rule. Otherwise it is bound to a single UI and destroyed on its detach.Fixes #526