microsoft / microsoft/webui

[Bug]: Second `<outlet />` at the same route level silently renders nothing

Open
#515 0 comments 1 reaction 2 assignees View on GitHub

@mohamedmansour is already working on this.

Since Sep 19, 2026.

  • #551 by @copilot-swe-agent — open
bug
Dominant language
Rust
Stars
90
Forks
23
Avg merge
13h 14m
Merged PRs (30d)
68

Description

Before filing
  • 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() {
    return Ok(());
}

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
  1. 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.
  2. Build and render a request path that matches a child route.
  3. 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 78b7617f
  • 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:

  1. Is multiple outlets per level a supported authoring pattern? If yes, this is a straightforward fix now that #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.
  2. 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.