Skip to content

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

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 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:

  1. 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.
  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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions