Skip to content

fix: bind route scope to window name only for @PreserveOnRefresh - #527

Merged
mshabarov merged 2 commits into
mainfrom
fix/526-routescoped-window-name-sharing
Aug 4, 2026
Merged

fix: bind route scope to window name only for @PreserveOnRefresh#527
mshabarov merged 2 commits into
mainfrom
fix/526-routescoped-window-name-sharing

Conversation

@mcollovati

Copy link
Copy Markdown
Contributor

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

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
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Test Results

143 tests  +10   143 ✅ +10   2m 26s ⏱️ +32s
 35 suites ± 0     0 💤 ± 0 
 35 files   ± 0     0 ❌ ± 0 

Results for commit 7f936d9. ± Comparison against base commit f6e86b0.

♻️ This comment has been updated with latest results.

Comment thread vaadin-cdi/src/main/java/com/vaadin/cdi/context/RouteScopedContext.java Outdated
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
mshabarov merged commit 13cb33b into main Aug 4, 2026
10 checks passed
@mshabarov
mshabarov deleted the fix/526-routescoped-window-name-sharing branch August 4, 2026 08:08
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants