Skip to content

fix: prevent dynamic component stylesheet flashes - #454

Merged
Mohamed Mansour (mohamedmansour) merged 11 commits into
microsoft:mainfrom
mohamedmansour:mohamedmansour-prevent-component-fouc
Aug 19, 2026
Merged

Mohamed Mansour (mohamedmansour) merged 11 commits into
microsoft:mainfrom
mohamedmansour:mohamedmansour-prevent-component-fouc

Conversation

@mohamedmansour

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

Copy link
Copy Markdown
Contributor

Dynamic Link-mode components could become visible before their stylesheet was ready during lazy creation or soft navigation. This change keeps the browser's native stylesheet link authoritative, prevents unstyled paint on the first client-created mount, and reuses an authorized constructable sheet for later mounts.

High-level design

The solution covers the three ways WebUI content reaches the browser:

  • Full SSR page loads: continue using parser-discovered native stylesheets and declarative shadow roots. Hydration preserves the server-rendered DOM.
  • Router navigation: JSON, streaming, and Router.ensureLoaded() template registration now expose a readiness handshake. The framework prepares Link styles before the router commits the new component, while stale navigations can still abort promptly.
  • Static component assets: the compiler records final generated CSS hrefs for each asset root and its shared closure. The protocol carries that metadata to the handler, which publishes it for the existing defineComponentAssets().preload(tag) lifecycle. CSS, the root asset, the component module, and optional data can therefore start in parallel without application code knowing generated hashes.

For a first client-created Shadow DOM instance, the framework:

  1. Uses <link rel="preload" as="style"> only to warm the matching style request.
  2. Keeps the original <link rel="stylesheet"> authoritative for CSP, MIME, integrity, CORS, redirects, and service workers.
  3. Guards the host until the native link is active, preventing unstyled paint.
  4. Releases the guard immediately, then promotes browser-authorized CSSOM into a shared constructable sheet when safe.
  5. Falls back to the original native links whenever promotion is unsupported or ambiguous.

Light-DOM component assets receive real, deduplicated document stylesheets because global CSS must be applied rather than merely preloaded. Failed asset or authored-module loads are evicted so a later preload or create call can retry.

Developer experience

There is no public raw-href API and no generated CSS filename in application code:

const assets = defineComponentAssets({
  'lazy-panel': {
    asset: './lazy-panel.webui.js',
    module: () => import('./lazy-panel.js'),
  },
});

assets.preload('lazy-panel');
panel.replaceChildren(await assets.create('lazy-panel'));

Bundlers may instead own the root asset import:

asset: () => import('./lazy-panel.webui.js')

Router-owned components continue using router template loading; component assets remain for non-routed deferred UI emitted through --emit-component-assets.

Measured impact

Independent Edge NTP retest of <ntp-waffle-menu>, with a fresh browser context per run, 4x CPU throttle, and 150 ms RTT:

Scenario 0.0.22 baseline Final Change
Cold click to styled paint 469.8 ms 241.4 ms -49%
Warm reopen 222.3 ms 25.1 ms -89%
50 ms intent dwell - 167.9 ms CSS and JS start in parallel

Every tested path made exactly one CSS request. Cold and warm runs produced 0/10 unstyled first paints, and constructable-sheet adoption succeeded in 10/10 cold runs. Instrumentation placed the framework CSS request start within about 1.6 ms of a directly injected preload; compiler-manifest parsing was not measurable.

Against origin/main in the 500-component responsiveness harness:

  • Unstyled visible frames: 8 -> 0
  • Cold styled paint: 140.38 -> 124.74 ms
  • Warm mount: 6.25 -> 3.815 ms
  • Main task time: 20.905 -> 16.975 ms
  • Heap: +9,976 bytes total, about 20 bytes per component
  • Backing storage: -169,200 bytes

JavaScript footprint is +4,514 gzip bytes in the main framework entry versus origin/main; the optional component-asset entry adds 271 gzip bytes versus the prior PR head.

Important behavior

  • Constructable-sheet promotion is a progressive enhancement. Native links remain the fallback.
  • A stylesheet error leaves a client-created component guarded instead of exposing unstyled content.
  • Automatic early component-asset style preloading requires handler or Protocol rendered HTML containing the compiler-owned manifest. Using build artifacts without rendering that protocol remains paint-safe, but CSS starts after the root asset reveals its template metadata.
  • Intent that never mounts may produce the browser's standard unused-preload warning before bounded cleanup removes the speculative node.

Validation

  • Full @microsoft/webui-framework unit and Playwright suites
  • Router unit and E2E coverage for JSON, streaming, stale navigation, and no-framework behavior
  • Component asset compiler, protocol, handler, Shadow/Light, and retry regressions
  • Developer documentation build
  • cargo xtask check

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents flashes of unstyled content for dynamically registered / lazily mounted components by bridging router template registration with framework stylesheet readiness, and by guarding first Link-mode shadow mounts until native link CSS is usable (while still keeping the browser as the authority for CSP/MIME/redirect/SW behavior).

Changes:

  • Router: template registration now supports a synchronous waitUntil() collector and awaits readiness (with abort-aware cancellation) before committing JSON/NDJSON navigations; NDJSON chunk-two state reading is deferred until after commit and is cancellable/cleanup-safe.
  • Framework: adds Link-mode stylesheet preparation + guarded mounts, promoting authorized native CSSOM to shared constructable stylesheets when safe, with broad fallback cases preserved.
  • Tests/docs/spec: expands Playwright and unit coverage for Link promotion/guarding, readiness waiting, and streaming cleanup; updates DESIGN/docs to describe the new contracts.

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/webui-router/tests/router-e2e.spec.ts Adds e2e coverage ensuring ensureLoaded() waits for link stylesheet readiness.
packages/webui-router/src/templates.ts Makes template registration dispatch optionally awaitable via waitUntil(); adds abort-aware readiness helper.
packages/webui-router/src/templates.test.ts New unit tests for readiness collection, abort behavior, and streaming/readiness interactions.
packages/webui-router/src/streaming.ts Refactors NDJSON handling to defer chunk-two consumption until after commit; adds cancellable deferred stream control.
packages/webui-router/src/router.ts Integrates readiness waits and deferred-stream lifecycle into navigation commit flow.
packages/webui-router/src/router.test.ts Extends router unit tests to ensure deferred readers don’t start before caching and can be started explicitly.
packages/webui-router/src/preload.ts Ensures speculative preloads can start/cancel deferred NDJSON tails after caching chunk one.
packages/webui-router/README.md Documents readiness waiting behavior and deferred-stream semantics.
packages/webui-framework/tests/fixtures/css-link/sw-css.js Adds SW fixture to validate service-worker-backed stylesheets remain native.
packages/webui-framework/tests/fixtures/css-link/styles/relative.css Adds fixture CSS exercising relative asset URL rewriting behavior.
packages/webui-framework/tests/fixtures/css-link/state.json Expands fixture state for new coverage scenarios.
packages/webui-framework/tests/fixtures/css-link/src/test-style-block-child/test-style-block-child.html Adds mixed <link> + <style> fixture to validate cascade preservation/fallback.
packages/webui-framework/tests/fixtures/css-link/src/test-link-event-child/test-link-event-child.html Adds compiled-event-on-link fixture to force native fallback behavior.
packages/webui-framework/tests/fixtures/css-link/src/test-link-dynamic-child/test-link-dynamic-child.html Adds dynamically bound link attribute fixture to validate native retention and re-arming.
packages/webui-framework/tests/fixtures/css-link/src/test-link-child/test-link-child.html Updates base child fixture to include a relative asset probe and parameterized content.
packages/webui-framework/tests/fixtures/css-link/src/test-lifecycle-style-child/test-lifecycle-style-child.html Adds lifecycle-added-style fixture to validate cascade boundaries and warm-mount fallback.
packages/webui-framework/tests/fixtures/css-link/src/index.html Extends fixture page to mount additional test components by state.
packages/webui-framework/tests/fixtures/css-link/relative-marker.svg Adds relative asset used to prove correct URL resolution behavior.
packages/webui-framework/tests/fixtures/css-link/alternate.css Adds alternate CSS used in dynamic-href swap readiness test.
packages/webui-framework/tests/fixtures/css-link/element.ts Expands fixture elements to support hydration/lifecycle assertions and dynamic link attributes.
packages/webui-framework/tests/fixtures/css-link/css-link.spec.ts Large Playwright expansion covering guarding, promotion safety constraints, CSP cases, and cancellation semantics.
packages/webui-framework/src/template.ts Hooks template registration event to prepare Link styles and register readiness via waitUntil().
packages/webui-framework/src/template-events.ts Extends template registration detail parsing to include optional waitUntil.
packages/webui-framework/src/template-element.ts Integrates guarded Link-mode mounts and deferred hydration completion for CSP-blocked guard scenarios.
packages/webui-framework/src/template-content.ts New shared template-content cache + stylesheet descriptor extraction for link analysis.
packages/webui-framework/src/element/styles.ts Updates Link strategy docs to reflect guarded native loading + promotion model.
packages/webui-framework/src/element/link-styles.ts New implementation: cache warmup, safety heuristics, guard install, native-CSSOM construction, and promotion/adoption logic.
packages/webui-framework/src/element/link-styles.test.ts New unit tests for top-level @import detection and safe URL rewriting behavior.
packages/webui-framework/src/component-asset/loader.ts Ensures component-asset registration awaits link-style preparation when present.
packages/webui-framework/README.md Documents guarded Link behavior and lifecycle implications ($ready, hydratedCallback) under CSP/native fallback.
examples/app/component-assets/tests/component-assets.spec.ts Adds browser coverage ensuring lazy asset insertion waits for link warmup before mount.
docs/guide/concepts/routing.md Documents waitUntil() readiness bridge and its interaction with JSON/NDJSON commits and deferred state.
docs/ai/SKILL.md Updates AI guidance/examples to reflect ensureLoaded() readiness semantics with the framework present.
DESIGN.md Updates spec for readiness waiting, NDJSON streaming ordering, and Link promotion constraints/guarantees.
deny.toml Adds an advisory ignore entry and normalizes formatting.
Suppressed comments (1)

packages/webui-router/src/streaming.ts:171

  • The _deferredStream marker is never cleared when the deferred reader finishes naturally. Since router/preload use this marker to decide whether to import streaming.js, leaving it set causes avoidable dynamic imports for already-consumed cached entries.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/webui-router/src/streaming.ts Resolved
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Start Link CSS through the existing component asset preload lifecycle without exposing generated stylesheet hashes. Preserve Light DOM cascade order and deduplicate metadata emission across document boundaries.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mohamedmansour Mohamed Mansour (mohamedmansour) changed the title fix: prevent dynamic component style flashes fix: prevent dynamic component stylesheet flashes Aug 19, 2026
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 62 out of 64 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/webui-framework/src/element/link-styles.ts:578

  • releaseStylesheetPreload() leaves a tombstone entry in speculativeStylesheetPreloads by calling .set(preload.href, undefined). Because preloadComponentAssetStyles() uses preloads.has(href) to dedupe, a stylesheet that was cleaned up after the TTL can never be speculatively preloaded again (and the Map can only grow over time). Deleting the key avoids the permanent dedupe and keeps the cache bounded.
    if (speculative.cleanupTimer !== undefined) {
      clearTimeout(speculative.cleanupTimer);
    }
    speculativeStylesheetPreloads?.set(preload.href, undefined);
  }

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mohamedmansour
Mohamed Mansour (mohamedmansour) merged commit 4583987 into microsoft:main Aug 19, 2026
24 checks passed
@mohamedmansour
Mohamed Mansour (mohamedmansour) deleted the mohamedmansour-prevent-component-fouc branch August 19, 2026 23:31
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>
Mohamed Mansour (mohamedmansour) added a commit that referenced this pull request Oct 29, 2026
Every client mount of a component that uses Link CSS paid for its own
copy of the stylesheet: a new `<link>`, a guard `<style>` that hides the
host, a `load` wait, a re-read of `link.sheet.cssRules`, and a
separately parsed `CSSStyleSheet` adopted by that root alone. Router
soft navigations, opened lazy regions and any client-created component
therefore paid N parses, N guards and N load waits for N instances.
Template-owned links already cache their sheets per template; component
catalog links had no cross-instance cache.

## Approach

Component Link stylesheets are now shared per Document and resolved
href, and promoted on their second use. What users see does not change.

- **First use stays native.** The first newly attached Shadow root to
mount a stylesheet keeps its native `<link>` and only records whether
that load is authorized under the existing #454 rule (unredirected, not
served by a service worker, successful). A component used once pays no
CSSOM serialization, second parse or duplicate contents.
- **Second use promotes once.** The next root builds one constructable
sheet from the CSSOM of any loaded, authorized link for that href and
caches it. Every later root adopts the cached sheet synchronously,
before its template is appended, with no link, guard or load wait. This
covers a later root in the same pass, a reopened lazy region, and a cold
load that settles.
- **No visible switch.** A guarded root that is still hidden may switch
to the shared sheet before it is revealed. A visible root never
switches.
- **One cascade per component.** Adopted sheets cascade after tree
styles, so roots whose cascade includes other author styles promote on
first use instead: templates with their own `<style>` or `<link>`,
server-rendered or reused roots, and Light descendants. All instances of
such a component adopt the same sheet, and a template-link reset that
clears the root can no longer drop a native catalog link.
- **Authorization is remembered** per Document and href, failures
included. Memory-cache hits add no resource timing entry, so a repeated
check could not pass.
- **Less per-root bookkeeping.** One `TargetStyleState` record per
target replaces five WeakMaps, and resource IDs stay plain strings until
a second ID arrives. When `TemplateElement` attached the Shadow root in
the current mount, adoption writes `adoptedStyleSheets` without reading
it. A read makes Blink build and retain a wrapper for the root's
lifetime. The public `installComponentStyles` still reads, so sheets an
author adopted are kept.
- Smaller wins: URL rewriting is skipped when the CSS has no `url(`,
`image-set(` or escape, and resolved hrefs are cached per base URL.

Cold loads, unpromotable stylesheets and failures keep the existing
native-link, guard and error handling.

## Performance

Edge 156 with 4x CPU throttling, measured with local Playwright
harnesses that bundle the framework's real `installComponentStyles` and
`prepareComponentStyleLinks`. Memory is
`performance.measureUserAgentSpecificMemory()` after GC, minus a no-CSS
page with the same roots. "This PR" is the path component mounts take.

**N instances of one card component (~3 KB of CSS)**, time to styled and
CSS memory, medians of 10 runs:

| Rows | main | This PR | One shared sheet (ideal) |
|---:|---:|---:|---:|
| 20 | 107 ms, 170 KB | 18 ms, 92 KB | 8 ms, 7 KB |
| 200 | 409 ms, 562 KB | 32 ms, 117 KB | 17 ms, 18 KB |
| 1,000 | 1,738 ms, 2,227 KB | 88 ms, 193 KB | 69 ms, 30 KB |
| Reopen 1,000 | 473 ms | 71 ms | 65 ms |

**200 component types (~1 MB of CSS), 1,199 Shadow roots** (a 1,000-item
list plus one instance of each other type), medians of 5 runs. No
component's rules leak into another's shadow root.

| Variant | Time to styled | CSS memory |
|---|---:|---:|
| main | 3,099 ms | 3,105 KB |
| This PR | 261 ms | 602 KB |
| Native `<link>` per root | 254 ms | 613 KB |
| One sheet per stylesheet (ideal) | 226 ms | 551 KB |

**Commerce app, Link CSS.** Two 30-sample runs with the server ports
swapped, averaged to cancel a measured 5-17 ms per-port bias. Mean +/-
standard error:

| Metric | main | This PR | Change |
|---|---:|---:|---:|
| Soft nav home -> product: soft FCP and LCP | 272.5 ms | 257.5 ms |
-15.0 +/- 5.2 |
| Soft nav home -> product: main-thread busy | 311.4 ms | 296.0 ms |
-15.4 +/- 3.2 |
| Soft nav product -> product: soft FCP | 164.0 ms | 160.0 ms | -3.9 +/-
2.8 |
| Full loads of home and product: FCP, LCP, hydration | | | each within
1 SE |

The same crossover with Module CSS shows no change on full loads. The
soft-nav gain comes from no longer promoting four single-use components
into four separate sheets: all 49 roots now keep native links.

## Review notes

- **Bundle size:** +1.0 KB gzip (+0.9 KB brotli) in the shared framework
chunk, about 4% of commerce's main chunk.
- **CSS loaded by the server-rendered page gets no gain.** Commerce's
repeated product cards stay on native links, as on main: their only
timing entry predates the mount, so the #454 rule cannot authorize them.
I did not relax that rule.
- **Stable responses are assumed.** Sharing assumes a stylesheet URL
returns the same CSS for the Document's lifetime, which holds for
compiler-emitted assets. Shared sheets and authorization results live as
long as the Document's style catalog.
- First-use roots remain slightly above native at scale (261 vs 254 ms),
within run-to-run variance.
- Direct callers of the public `installComponentStyles` keep the reading
path: 100 ms / 294 KB for 1,000 rows and 266 ms / 747 KB at scale.
- `link-styles.ts` grows from 1,245 to 1,561 lines.

## Tests

- New `component-link-sharing` e2e fixture (7 tests): one native link
then one shared sheet across a mount pass, sharing after the preload is
released, second use after a lone first mount, promotion from a later
root after the first is gone, guarded cold mounts, unpromotable
stylesheets, and eager promotion for components with template styles.
The sharing tests fail on main, where every instance gets its own link
and sheet.
- `component-assets` e2e: a single lazy panel now pins `{adopted: 0,
disabled: false, links: 1}` (first use stays native), and a new reopen
case pins `{adopted: 1, disabled: true, links: 1}`.
- Unit tests for sharing, eager promotion, write-only adoption on fresh
roots, preserving existing adopted sheets, remembered failed
authorization, base URL changes, template author-style detection and the
URL-rewrite fast path.
- `cargo xtask check` passes. Framework e2e: 391 passed.
component-assets: 5 of 5. Router: 42 passed (4 pre-existing `fixme`).

`specs/hydration.md` and the framework README describe the new Link
behavior.

---------

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.

3 participants