You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I searched existing issues and did not find a duplicate.
This is not a security vulnerability.
Area
Handler
Summary
A template containing two <outlet /> elements at the same route level renders only the first one. process_outlet moves the current route level out of the render context and never restores it, so every outlet after the first sees an empty children list and returns early.
Minimal reproduction
No playground link; this is visible by inspection in crates/webui-handler/src/lib.rs. process_outlet opens with:
let children = std::mem::take(&mut context.route_children);if children.is_empty(){returnOk(());}
context.route_children is emptied by that mem::take and is never reassigned to the level it held on any return path. Inside the matched branch, the save/restore pair operates on the already-emptied field:
let saved_route_children = std::mem::take(&mut context.route_children);// already empty
context.route_children = grandchildren;// ...render the matched child...
context.route_children = saved_route_children;// restores empty
So when process_outlet returns, the caller's route level is gone rather than restored.
Steps to reproduce
Author a routed component template whose body contains two <outlet /> elements, for example a layout that wants to project the matched child route into both a main region and a sidebar region.
Build and render a request path that matches a child route.
Observe the emitted HTML.
Expected behavior
Both outlets render the matched child route, or the framework rejects the second outlet at build time with a diagnostic explaining that only one outlet per route level is supported.
Actual behavior
The first <outlet /> renders the matched child route. Every subsequent <outlet /> at that level emits nothing at all, silently: no <webui-route> element, no diagnostic, no warning.
Environment
OS: any
Rust version: any
Node.js version: n/a
WebUI package or CLI version: reproduces on main at 78b7617
Command: any full render of a routed template with two sibling outlets
Logs or terminal output
n/a - the failure is silent, which is the main hazard here.
Context and why this is filed separately
Found while reviewing #511 / #513, which reduce per-render handler allocations. The mem::take is load-bearing for the current design: it is how process_outlet extracted grandchildren without deep-cloning the protocol's route subtree. #513 replaces that hack with a plain borrow, which makes restoring the level trivial, and an early revision of #513 did restore it. That was deliberately reverted so those PRs stay byte-identical in output, since a rendering change does not belong in a perf PR.
Two things worth deciding here:
Is multiple outlets per level a supported authoring pattern? If yes, this is a straightforward fix now that perf: avoid cloning nested route trees #513 removes the ownership constraint: restore route_children before returning from process_outlet. If no, it should be a build-time diagnostic rather than silent empty output, per the repo's diagnostics conventions.
There is no test either way. Whichever answer is correct, it should be pinned by a test, because the current behavior is an accident of an allocation optimization rather than a decision anyone recorded.
Worth confirming against the client-side router (packages/webui-router) too, since it does the chain diffing and may or may not assume a single outlet per level.
Before filing
Area
Handler
Summary
A template containing two
<outlet />elements at the same route level renders only the first one.process_outletmoves the current route level out of the render context and never restores it, so every outlet after the first sees an empty children list and returns early.Minimal reproduction
No playground link; this is visible by inspection in
crates/webui-handler/src/lib.rs.process_outletopens with:context.route_childrenis emptied by thatmem::takeand is never reassigned to the level it held on any return path. Inside the matched branch, the save/restore pair operates on the already-emptied field:So when
process_outletreturns, the caller's route level is gone rather than restored.Steps to reproduce
<outlet />elements, for example a layout that wants to project the matched child route into both a main region and a sidebar region.Expected behavior
Both outlets render the matched child route, or the framework rejects the second outlet at build time with a diagnostic explaining that only one outlet per route level is supported.
Actual behavior
The first
<outlet />renders the matched child route. Every subsequent<outlet />at that level emits nothing at all, silently: no<webui-route>element, no diagnostic, no warning.Environment
mainat 78b7617Logs or terminal output
Context and why this is filed separately
Found while reviewing #511 / #513, which reduce per-render handler allocations. The
mem::takeis load-bearing for the current design: it is howprocess_outletextracted grandchildren without deep-cloning the protocol's route subtree. #513 replaces that hack with a plain borrow, which makes restoring the level trivial, and an early revision of #513 did restore it. That was deliberately reverted so those PRs stay byte-identical in output, since a rendering change does not belong in a perf PR.Two things worth deciding here:
route_childrenbefore returning fromprocess_outlet. If no, it should be a build-time diagnostic rather than silent empty output, per the repo's diagnostics conventions.Worth confirming against the client-side router (
packages/webui-router) too, since it does the chain diffing and may or may not assume a single outlet per level.