fix: render reachable-component ordering deterministically - #519
Merged
Mohamed Mansour (mohamedmansour) merged 1 commit intoSep 4, 2026
Merged
Conversation
…ally Route-reachable components were collected into a HashSet<String> before emitting CSS <link> hrefs, CSS module specifiers, and component template payloads at body_end. Rust's HashSet iteration order depends on a per-process randomized hash seed, so this order could change between renders/process restarts even though the underlying traversal order was already deterministic (collect_reachable_component_order_for_request already returns a deduplicated, traversal-ordered Vec<String>). Keep the Vec<String> instead of collecting into a HashSet, so <head> CSS <link>/style-module emission and template payload order always follow document/traversal order. This can otherwise flip cascade-order- sensitive CSS (e.g. competing same-specificity order/position rules) and change where <for>-repeated elements visually land across renders. Updated HandlerPlugin::emit_templates / collect_template_payloads / BootstrapExtensionContext::components to take &[String] instead of &HashSet<String> to match. Added a regression test asserting stylesheet <link> tags follow declared component order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6645d147-3fce-466c-90e6-495762fe71c2
Jane Chu (janechu)
force-pushed
the
fix/deterministic-css-link-order
branch
from
September 4, 2026 01:48
bb50b62 to
d260428
Compare
Jane Chu (janechu)
marked this pull request as draft
September 4, 2026 02:29
Mohamed Mansour (mohamedmansour)
marked this pull request as ready for review
September 4, 2026 22:08
Mohamed Mansour (mohamedmansour)
approved these changes
Sep 4, 2026
Mohamed Mansour (mohamedmansour)
deleted the
fix/deterministic-css-link-order
branch
September 4, 2026 22:09
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Route-reachable components were collected into a
HashSet<String>before emitting:<link>hrefs (Link CSS strategy)Rust's
HashSetiteration order depends on a per-process randomized hash seed (RandomState), so this emission order could change between renders / process restarts, even though the underlying data was already deterministic:collect_reachable_component_order_for_requestalready returns a deduplicated, traversal-orderedVec<String>(see its siblingfilter_needed_componentsdoc comment: "Input order is preserved... so downstream<head>CSS<link>emission follows document/traversal order").If two components apply competing same-specificity CSS (
order,position,float, etc.) to elements inside a<for>loop, cascade resolution flips depending on which stylesheet loaded last — this is the most plausible mechanism for<for>-repeated content visually landing in different positions across renders, since the<for>reconciliation itself (serverprocess_*_for_loopand clientelement/diff.ts) is already fully array-order deterministic.Fix
reachableas the traversal-orderedVec<String>already produced upstream instead of collecting it into aHashSet<String>.HandlerPlugin::emit_templates,HandlerPlugin::collect_template_payloads, andBootstrapExtensionContext::componentsto take&[String]instead of&HashSet<String>to match (the streaming checkpoint path already used borrowed slices). This is a minor breaking change to the publicHandlerPlugintrait for any external plugin implementors; behavior for the two built-in plugins (webui, FAST) is unchanged aside from now-deterministic ordering.link_strategy_emits_stylesheets_in_deterministic_document_order, a regression test asserting stylesheet<link>tags follow declared component order.Code review checklist (framework change)
Applied the
code-reviewskill checklist since this toucheswebui-handlercore rendering:HashMap/HashSetfor order-dependent logic" → "use insertion order / explicit ordering field." Replaced theHashSet<String>with the already-orderedVec<String>..collect::<HashSet<_>>()hash-table build entirely; the&[String]signature changes are zero-cost slice reborrows, no new clones.Vec/insertion order, notHashSet— this fix aligns the code with that rule.window.__webui.templates[tagName]), not by array position, so reordering the emitted payloads doesn't affect hydration correctness.Testing
cargo test -p microsoft-webui-handler— 454 unit tests + 35 streaming tests pass, including the new regression test.cargo xtask check— full gate (license-headers, fmt, clippy, deny, test, build, wasm build, examples, docs) passes.Closes #520
Status: draft — pending resolution of reviewer feedback questioning whether this ordering should be a guaranteed contract; see discussion on #520.