enhancement
major
minor
major
minor
major
#29602
TL Views: a URL adopted into an existing session keeps the slot content of the sidebar item that was active before
Problem
An application built on the view layer (com.top_logic.layout.view) has a <sidebar> with two <nav-item>`s, each holding a `<tile-stack> whose views project their breadcrumb into the app bar through <slot-content to="appbar-content"> (the layout of the issue tracker tl-dev: sections Tickets and Projects).
Steps (engine snapshot of 2026-09-16, build 37 of tl-layout-view):
- Log in, open the Tickets section, push a ticket page onto its stack (breadcrumb Tickets / TL-54).
- Switch to Projects through the sidebar, open a project (breadcrumb Projekte / TopLogic). The app bar shows one breadcrumb, as expected.
- Enter /view/tickets in the address bar (or reload the page on that URL).
Observed: the sidebar activates Tickets and the content area shows the ticket page, but the app bar now shows two breadcrumbs, Tickets / TL-54 and Projekte / TopLogic. The slot content of the Projects item, which is no longer displayed, stays projected into the app bar. The same happens in the other direction (/view/projects entered while Tickets is active: Projekte / TopLogic plus Tickets / TL-54).
Switching sections through the sidebar itself does not show the defect: after step 2 the app bar holds only the Projects breadcrumb, and clicking Tickets in the sidebar leaves only the Tickets breadcrumb.
Not app specific: the app declares nothing beyond <nav-item>, <tile-stack> and <slot-content>; the projection of a slot into the app bar is the engine's slot mechanism.
Expected
Adopting a URL results in the same display as reaching that state through the UI: the previous sidebar item's content, including everything it projected into slots, is gone from the app bar.
Cause
Neither the slot mechanism nor the sidebar's item switch is at fault, and the defect is not specific to URL adoption.
A slot contribution is bound to the display state of the control that declares it: SlotContentControl registers with the SlotRegistry when it is attached and unregisters when it is detached. Adopting a URL keeps that contract - once the page has been rendered, exactly one contribution is registered. What breaks it is the step after: the freshly loaded page opens its event stream, and SSEUpdateQueue.sendFullState() hands the client the state of every registered control, not only of the ones the window displays.
Every control registers itself with the window's update queue when it is constructed and is removed only when it is disposed. A sidebar keeps the content of a visited item in a cache and never disposes it, so the subtree of the item left behind is still registered - detached, but reachable. Serializing its state walks into the controls it embeds, and serializing a control attaches it (ReactControl.writeAsChild() calls attachOnRender(), which establishes "rendered implies displayed"). The resend therefore re-attaches a subtree the window does not display, its SlotContentControl registers a second time, and the app bar renders both contributions.
Two consequences beyond the reported symptom:
- Any event stream that is opened resurrects the orphaned subtrees a window holds - a reconnect after a network interruption as well. A typed URL or a reload is the reproducible path because it always opens a fresh stream.
- Everything else a resurrected subtree registers on attach comes back with it, in particular its `RoutingParticipant`s: the composed URL then carries segments of a page that is not displayed, the defect class that was removed one layer up in #29530.
Solution
The full-state resend sends the state of the controls the window displays. The queue already carries that notion for the events it drops for a control "that this window no longer displays: one that was disposed (and thereby unregistered), or one that a container detached"; the same predicate now decides what the resend hands out, so there is one definition of what a window displays instead of two. A control that is only registered for command dispatch is no longer serialized, and therefore no longer attached behind the display's back.
A regression test in com.top_logic.layout.view replays what the servlet does when a page is loaded into the tree a window already holds - the page being left is unloaded, the URL adopted, the tree attached, rendered and the adoption finished, and the loaded page opens its event stream - over an app shell whose app bar holds a slot and whose sidebar offers two items, each projecting into it. It asserts the invariant that every registered contribution belongs to a control the displayed tree contains, for the sidebar click, a deep link into a fresh display, and a URL adopted by a display that exists, with and without a drill-down in either item.