enhancement
major
minor
defect
critical
major
minor
major
#30045
View layer slots: report a <slot-content> that reaches no <slot>, empty fallback of a <slot>, slot positions in split panels and the sidebar
Follow-up to #29767. Related: #29108 (introduced <slot> / <slot-content>).
<slot-content to="X"> (SlotContentElement, package com.top_logic.layout.view.slot) declares content of a view that is displayed at the nearest <slot name="X"/> (SlotElement) of the element tree; SlotRegistry routes every contribution to the placeholder of the same name with the smallest tree distance (SlotPath.distance). The developer article view-layer/layout (section "Content shown in an enclosing region") describes it. Three gaps remain.
1. A contribution without a slot disappears silently
SlotRegistry.reroute() skips a contribution for which findNearest finds no placeholder: its content is displayed nowhere and nothing is reported. A misspelled to or a view used in a shell without the slot looks like a rendering bug.
Fix: report a contribution that stays unrouted. Since routing changes while placeholders attach and detach (a slot in a dialog or a lazily built tab may appear later), report it once the interaction that attached it has completed (e.g. when the DeliveryScope settles), as a warning in the log naming the slot name and the view source of the contribution, and as a diagnostic of the SlotContentControl (putDiagnostic("unrouted", true)) so that the UI inspector shows it. An attribute optional="true" on <slot-content> suppresses the warning for content that is meant to be shown only where a shell offers the slot.
Test: unit test on SlotRegistry (beside TestSlotUrlAdoption): a contribution to an unknown name is reported, one whose slot attaches in the same interaction is not, one with optional="true" is not.
2. No fallback for an empty slot
SlotElement has only name; a slot without contributions renders nothing. A region that should show something by default (a default title in appbar-content, a hint "no actions" in a footer) needs the content in every contributing view.
Fix: an optional <empty> list of UI elements on <slot>, built in the context of the declaring view and displayed while no contribution is routed to the slot (SlotPlaceholderControl.setRoutedContributions switches between the routed controls and the fallback).
Test: a view with <slot name="x"><empty><text .../></empty></slot> shows the text; after a <slot-content to="x"> is displayed it shows only the contribution, and the text again when the contribution leaves the display.
3. Split-panel panes and sidebar items share one slot position
Containers extend the slot path of their children through ViewContext.withChildSlotPath(...) (ContainerElement, ColumnsElement, AppBarElement, ContentSection for tabs and accordion sections, ReactSwitchControl, ReactWizardControl, ...). SplitPanelElement and SidebarElement do not, so all panes of a split panel (and all items of a sidebar) have the same position. Two slots of the same name in two panes are then equally near to a contribution, and the slot registered first wins (findNearest uses a strict <), regardless of where the contribution sits.
Fix: extend the slot path per pane (by the pane id or index) in SplitPanelElement and per item in SidebarElement, as ContentSection does with the tab id; then drop the corresponding pitfall from the article view-layer/layout.
Test: a split panel with a <slot name="x"/> and a <slot-content to="x"> in each of two panes: each contribution is displayed in the slot of its own pane.
4. A slot shows old and new content together for a moment
A container switching its content attaches the new content before it retires the old one (ReactWizardControl.displayCurrentStep, likewise <switch>, tabs, sidebar items, frames). Since the registry rerouted on every registration, the slot received two updates in one interaction - first old and new contributions together, then only the new ones - and the browser painted the intermediate state for a frame: the buttons of the step left and of the step entered stood side by side in the wizard footer.
Lösung
- End-of-interaction hook: DeliveryScope.afterInteraction(Runnable) (com.top_logic.layout.react) runs work once when the outermost interaction of the thread completes, while the scope is still open and before its updates are delivered, so that what the work changes goes out with the interaction; work registered during the run runs as well, a failing work item is logged without stopping the rest or the delivery. Outside an interaction the work runs at once. DeliveryScope.isOpen() tells whether an interaction is open.
- Routing once per interaction: a registration or unregistration of a slot or a contribution only marks the routing as pending; SlotRegistry computes it once when the interaction has completed, and a slot about to be written settles it first (SlotRegistry.settleRouting() from SlotPlaceholderControl.onBeforeWrite()), so a page is written with its slots filled and a slot switches from the old contributions to the new ones in one update. Outside an interaction the routing is computed at once.
- Report of an unrouted contribution: in the same end-of-interaction pass, every contribution that reaches no slot and is not optional is marked with the diagnostic unrouted (shown by the UI inspector), and a warning names the slot and the configuration location of the <slot-content>. A contribution is reported once while it stays without slot and again only after a slot has caught it in between; routed, it loses the mark. Outside an interaction nothing is checked (a session that ends disposes its trees without one, its slots leaving the display before the contributions). The drawer toggle a <sidebar> puts into the slot named by drawer-open-slot-name is reported the same way, since without the slot the compact drawer cannot be opened.
- optional on <slot-content> (SlotContribution.isOptional()) suppresses the report for content meant only for shells offering the slot.
- <empty> on <slot>: elements built in the context of the declaring view (slot path segment empty), owned by the placeholder, displayed while no contribution is routed to it, detached while a contribution covers them and disposed with the slot. The slots of the shell's app bar have the ids appbar-leading and appbar-content, so that an application can give appbar-content a default with an overlay of app.view.xml (<slot config:ref="appbar-content"><empty>...); the shipped shell shows no default.
- Slot paths: SplitPanelElement extends the slot path per pane (pane id, else index), SidebarElement per item (item id) and for the header and footer of the rail.
- Demo: the wizard demo of tl-demo-react shows a hint in its footer slot during the auto-advancing step without buttons; the Back button of the first question leads past that step to the profile, since a step entered again waits instead of moving on.
- The articles view-layer/layout and view-layer/app-shell describe optional, <empty>, the report, the routing at the end of an interaction and the slot ids; the pitfalls about the silent contribution and the shared position of panes / sidebar items are removed.