Skip to content

feat: add compiler-driven interaction hydration - #485

Merged
Mohamed Mansour (mohamedmansour) merged 4 commits into
microsoft:mainfrom
mohamedmansour:mohamedmansour-interaction-hydration-boundary
Aug 25, 2026
Merged

Mohamed Mansour (mohamedmansour) merged 4 commits into
microsoft:mainfrom
mohamedmansour:mohamedmansour-interaction-hydration-boundary

Conversation

@mohamedmansour

@mohamedmansour Mohamed Mansour (mohamedmansour) commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add neutral compiler work-policy metadata for interaction hydration and the combined lazy-render/interaction policy
  • generate the singleton interaction marker for authored and routed roots across buffered and streaming rendering
  • add the isolated @microsoft/webui-framework/interaction-hydration.js entry with pointer, focus, keyboard, and cancelable primary-click replay
  • add framework-neutral @microsoft/webui-router/preload.js preparation, cancellation, bounded buffering, and single-use handoff
  • preserve JSON and NDJSON streaming, inventory checks, cache completeness, startup retry, and no-duplicate-request behavior
  • document when to choose eager, lazy render, interaction, or their explicit combined form

The router package has no WebUI Framework dependency; FAST or another runtime supplies its own onIntent readiness and adopts the same prepared response.

Authoring contract

UI shape Policy Tradeoff
Visible and commonly interactive Eager default Lowest first-use latency
Repeated or offscreen w-render="lazy" Initial JS loads; hydration and browser rendering wait for relevance
Visible SSR shell that may never be used w-hydrate="interaction" JS and heap wait; first use pays a cold wake
One offscreen singleton that may never be used w-render="lazy" + reservation + w-hydrate="interaction" Browser rendering, JS, and heap wait; first use pays the same cold wake

w-render="lazy" plus w-hydrate="lazy" remains invalid because rendering deferral already includes visibility hydration. The combined interaction form is code 4, retains content-visibility, and hydrates eagerly after its deferred module loads so click replay cannot race the visibility queue.

Paired performance evidence

All matrices used fresh Chromium processes, disabled cache, alternating case order, seven rounds, and three forced-GC passes before heap samples. Fast-4G used 40 ms latency, 10 Mbps down, and 5 Mbps up.

Visible WebUI todo boundary

Metric Eager Interaction Delta
Executable JS before interaction 77,971 B 3,290 B -95.8%
Forced-GC heap before interaction 1,415,320 B 1,117,392 B -21.1%
Wake path Local P50 / P95 Fast-4G P50 / P95
Pointer effect 9.4 / 22.3 ms 187.7 / 203.6 ms
Focus effect 19.0 / 21.9 ms 188.1 / 204.4 ms
Keyboard effect 21.5 / 24.6 ms 190.3 / 205.9 ms
Primary-click effect 21.8 / 22.7 ms 193.1 / 205.1 ms

Routed intent

Profile Startup JS eager → intent Heap eager → intent Hit navigation eager → intent Miss navigation eager → intent
Local 111,181 → 8,989 B (-91.9%) 1,434,128 → 1,168,140 B (-18.5%) 74.0 → 73.6 ms 43.1 → 58.6 ms
Fast-4G 108,512 → 8,989 B (-91.7%) 1,425,120 → 1,168,140 B (-18.0%) 75.3 → 218.8 ms 96.5 → 422.8 ms

Every hit/miss issued exactly one destination partial request. Cancellation replaced /alpha with /beta, aborted retained work, and still issued one /beta request.

1,000-row offscreen policy matrix

Fast-4G medians:

Policy Pre-use JS Pre-use heap Initial layout Layout objects Initial task First effect
Eager 73,964 B 1,447,760 B 14.079 ms 5,009 45.847 ms 0.2 ms
Lazy render 77,823 B 1,342,620 B 0.086 ms 5 26.565 ms 0.2 ms
Interaction 2,620 B 1,281,964 B 13.701 ms 5,009 42.175 ms 180.8 ms
Combined 2,620 B 1,281,964 B 0.101 ms 5 23.765 ms 180.2 ms

The combined form preserves interaction's -96.5% initial JS and -11.5% heap versus eager while eliminating 99.3% of initial offscreen layout time and 99.9% of layout objects. It adds no measured first-use cost over plain interaction. Lazy render remains the better choice when first-use latency matters.

Correctness

  • pointer, focus, and keyboard start loading without cancellation
  • an eligible primary click is cancelled and replayed once on its original composed-path target
  • modified, non-cancelable, previously cancelled, and closed-shadow-ineligible clicks pass through
  • native checkbox activation and nested/replaced boundary replay occur exactly once
  • compiler markers are discovered through open Shadow DOM and released after terminal cleanup
  • JSON and first-line NDJSON preloads transfer without early parse or duplicate fetch
  • adopted navigation aborts, timeouts, malformed responses, stale inventory, and startup failure release owned resources
  • router destroy/restart and superseded startup failures cannot poison a newer navigation

Validation

  • pnpm --dir packages/webui-framework test
  • pnpm --dir packages/webui-router test
  • cargo xtask check
  • independent framework, router, and compiler/protocol reviews completed with all findings addressed

Net PR diff: 45 files, +2,915 / -201.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6131511a-87c1-48b8-8dd8-9eb9babc8d65
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6131511a-87c1-48b8-8dd8-9eb9babc8d65
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6131511a-87c1-48b8-8dd8-9eb9babc8d65
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6131511a-87c1-48b8-8dd8-9eb9babc8d65
@mohamedmansour
Mohamed Mansour (mohamedmansour) force-pushed the mohamedmansour-interaction-hydration-boundary branch from aa0848b to 88cba2d Compare August 25, 2026 18:53
@mohamedmansour Mohamed Mansour (mohamedmansour) changed the title feat: add interaction hydration boundary feat: add compiler-driven interaction hydration Aug 25, 2026
@mohamedmansour
Mohamed Mansour (mohamedmansour) merged commit 916cda7 into microsoft:main Aug 25, 2026
24 checks passed
@mohamedmansour
Mohamed Mansour (mohamedmansour) deleted the mohamedmansour-interaction-hydration-boundary branch August 25, 2026 20:12
Mohamed Mansour (mohamedmansour) added a commit that referenced this pull request Aug 26, 2026
## Release

Bumps WebUI to `0.0.26`.

Previous release tag: `v0.0.25`

## Changes since `v0.0.25`

Features:

- Python hosts now have an official typed PyO3 renderer package with
buffered, partial, template, token, and host-driven streaming APIs
(feat: add official Python renderer package #453 by @mohamedmansour).
- Progressive SSR can place transport-flushed streaming boundaries
inside reusable components while preserving component ownership and
continuation state (feat: support component-local streaming boundaries
#460 by @mohamedmansour).
- Components can defer compiler-owned hydration until interaction, with
router preload handoff and a combined lazy-render policy (feat: add
compiler-driven interaction hydration #485 by @mohamedmansour).
- Builds can opt into global Light DOM CSS while preserving explicit
authored Shadow roots and deterministic style closures (feat: make Light
DOM a global CSS opt-in #429 by @mohamedmansour).
- State projection supports application-owned TypeScript 7.0.2 while
retaining the TypeScript 6 migration path (feat: support TypeScript 7
projection compilation #452 by @mohamedmansour).
- WebUI Press supports layout-scoped compile-time named regions with
fallback HTML, state, components, and scripts (feat(press): add
compile-time named regions #484 by @mohamedmansour).
- FAST v2 and v3 gain plugin-owned local and npm component discovery
with validated FAST template transformation (feat: add plugin-owned FAST
component discovery #378 by @janechu).

Fixes:

- Missing condition identifiers are treated as falsy before negation and
logical evaluation, aligning SSR with the browser runtime (fix: negate
missing condition paths correctly #449 by @mohamedmansour).
- FAST route state now scales through escaped scalar kebab-case
attributes shared by FAST v2 and v3 (fix: scale FAST route state with
scalar attributes #450 by @janechu).
- Dynamic Link-mode components wait for native stylesheet readiness
across navigation and component assets, preventing unstyled flashes
(fix: prevent dynamic component stylesheet flashes #454 by
@mohamedmansour).
- Authored component definitions defer whenever compiled template
metadata has not arrived, including ordinary router navigation (fix:
defer authored define() whenever template metadata is missing #461 by
@mohamedmansour).
- Client structural updates preserve authored order and sibling
ownership for shared slots and raw HTML ranges (fix: preserve source
order for shared structural slots #465 by @mohamedmansour, fix: preserve
siblings around raw HTML updates #466 by @mohamedmansour).
- Native-element attributes no longer leak into the local state of a
later component (perf(handler): stop native attributes leaking into
component state #469 by @mohamedmansour).
- Templates-and-state-only SSR bootstrap payloads no longer require
component style metadata (fix: allow SSR bootstrap without component
styles #475 by @mohamedmansour).
- The high-level Rust `serve_request` path now preserves complete render
options, including CSP nonces, while sharing the same entry and request
path with partial rendering (fix: forward CSP nonces through
serve_request #488 by @mohamedmansour).
- Projection compilation bounds source reads, excludes binary and
unsupported-loader inputs from semantic analysis, and preserves
deterministic cleanup under large esbuild graphs (fix: bound projection
adapter source reads #489 by @mohamedmansour).
- Router pending UI remains mounted through pre-commit work and settles
at the synchronous DOM commit, with stale, aborted, and re-entrant
navigation cleanup kept generation-safe (fix: settle router pending UI
during navigation commits #491 by @mohamedmansour).

Docs:

- The documentation site adds accessible responsive navigation, improved
reading layouts, stronger search behavior, and Playground recovery
states (feat: polish WebUI documentation experience #459 by
@mohamedmansour).
- Slot-resolution implementation guidance now documents pre-order
lookup, pending placement ownership, and marker handling (chore: clarify
slot resolution comments #471 by @mohamedmansour).
- Repeated `w-ref` behavior is now explicit: refs are scalar and the
last wired occurrence wins, while stable authored IDs or item components
provide identity-based lookup (chore: clarify repeated w-ref behavior
#490 by @mohamedmansour).

Maintenance:

- Release and package policy metadata now constrains the transitive h2
advisory, uses SPDX NuGet licensing, and classifies publishing jobs
correctly (chore: allow constrained h2 advisory #451 by @janechu, fix:
use modern NuGet license metadata #457 by @janechu, chore: mark
publishing jobs as release jobs #458 by @janechu).
- The development and CI Rust toolchain is updated to 1.98 with the
resulting warnings resolved (Update rust toolchain version and fix
clippy warning #462 by @telecos).
- Release builds use Thin LTO to retain cross-crate optimization with
faster linking (perf: switch release builds to Thin LTO #464 by
@mohamedmansour).
- Handler and expression hot paths reduce attribute vtable calls, render
lookup and scope allocations, and single-term condition overhead
(perf(handler): centralize HTML attribute writing in ResponseWriter #467
by @mohamedmansour, perf(expressions): fast-path single-term conditions
#470 by @mohamedmansour, perf(handler): reduce render lookup and scope
allocations #472 by @mohamedmansour).
- Node rendering can reuse immutable prepared-state snapshots and
bounded per-route output capacity hints (perf(node): reuse prepared
state across renders #477 by @mohamedmansour, perf: reuse Node render
output capacity #478 by @mohamedmansour).
- Framework hydration releases bootstrap data sooner and reduces
allocations for bindings, empty hosts, and visible conditionals (perf:
reduce framework hydration allocations #479 by @mohamedmansour, perf:
release SSR bootstrap memory after hydration #480 by @mohamedmansour,
perf: reduce empty template host overhead #482 by @mohamedmansour, perf:
remove visible conditional anchors #483 by @mohamedmansour).
- Benchmark tooling now renders the full contact workload, runs
Criterion baselines per target, and restores the streaming hydration
fixture (fix(bench): render contacts in contact-book benchmark #468 by
@mohamedmansour, fix(xtask): run Criterion benchmarks per target #473 by
@mohamedmansour, fix: repair streaming hydration benchmark fixture #476
by @mohamedmansour).

## Validation

- `cargo xtask check`

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants