You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Simplify the resource-refresh pass: drop mounted-output snapshots - #1392
This simplifies the resource-refresh pass added in #1329.
Theme.NotifyResourcesChanged() still refreshes what it was built for: Theme.Ref bindings, ThemeRef resource overrides, and values resolved during Render. It still walks into every memoized subtree, kept list item and unchanged TreeView without remounting anything, so component state, selection and expansion survive.
What's removed is support for brushes a memo factory or FlipView item builder captures eagerly with ThemeRef.Resolve(...). To refresh those, #1329 recorded what every memo layer and FlipView item mounted and diffed against that record. That needed:
per-layer snapshot state (KeyedMemoOutput, MoreKeyedMemoOutputs);
ordering for nested memos that share a key;
legacy-wrapper handling;
fresh-control markers;
a wrapper-value replay;
snapshot cleanup.
That is most of #1329's reconciler code, for a pattern with 0 uses in samples, docs or the agent kit, against about 1,100 uses of Theme.Ref. The styling guide and CHANGELOG now state the contract: a brush resolved eagerly and kept is a fixed value, so bind it with Theme.Ref.
What stays, or is simplified:
Keyed memos: during a refresh, a keyed memo re-runs its factory and diffs the output against itself. This reuses the existing path that walks into a memo when a component inside it updated its own state.
Transparent wrappers: a keyed memo, or a flyout that wraps its target, whose inner update returns a different control applies its own modifiers with mount semantics. This one rule replaces the fresh-control markers.
FlipView: keeps main's per-item record: the element each item mounted and whether it produced a control. That record is the baseline for updates, as the container tag is for a templated ListView, and stops an Empty item from misaligning them. It is cleared when the FlipView unmounts.
Rewrote Hosting_NotifyResourcesChangedRefreshesMemoizedThemeModifiers around the contract. After a resource edit, an ordinary re-render leaves values stale; NotifyResourcesChanged from a background thread then refreshes:
a UseMemo'd subtree and a keyed memo;
nested memos, including nested memos with the same key, inside a legacy wrapper, or under a flyout's target;
ComboBox items with Empty gaps;
ListView/GridView items with a kept array, and a LazyVStack mid-scroll;
TreeView content;
a FlipView with an Empty item;
a value resolved in Render.
Nested components are not remounted (one mount, no cleanups). TreeView expansion and ListView selection survive.
New probes for controls replaced on an ordinary render: a flyout whose target changes type is torn down once and its margin and OnMount reach the new target; a popup's always-remounting child keeps exactly one live copy.
Mutation checks. Each change made the listed probes fail:
Change
Failing probes
Remove the refresh condition on the keyed-memo arm
5
Remove the refresh skip bypass
9
Remove the transparent-wrapper rule
the flyout-modifier and visualizer-wrapper probes
Ignore the FlipView record
the FlipView Empty-gap and changed-in-place probes
| Remove the unmount clear | the held-unmounted-FlipView probe |
| Remove the hosted-slot unmount | the popup one-live-copy probe |
--self-test --filter "Hosting_", --filter "DataGrid_" and --filter "AsyncResource.": 0 failures.
Full selftest shards: not clean locally. Both crashed with 0xC0000409 inside DataGrid/AsyncResource fixtures, which pass on their own; unmodified main crashed the same way on this machine (3 out of 3 shard-2 runs). CI is authoritative.
Release builds of src/Reactor and tests/Reactor.AppTests.Host (--no-restore): 0 errors.
dotnet test tests/Reactor.Tests and the docs compile: not run locally. Restore needs packages from deps: Windows NuGet updates #1385 and nuget.org is unreachable from this machine. CI covers both. docs/guide/styling.md mirrors the one plain-prose paragraph added to the template.
Risk / breaking changes
No public API change; every removed member was internal.
Behavior change from #1329, which is unreleased: a brush captured eagerly inside a memo factory or FlipView item builder is no longer refreshed by NotifyResourcesChanged. This is documented.
Theme.NotifyResourcesChanged refreshes Theme.Ref bindings, ThemeRef resource
overrides and values resolved during Render. #1329 also tried to refresh brushes a
memo factory or item builder captured eagerly with ThemeRef.Resolve, by recording
what every memo layer and FlipView item mounted and diffing against that. That
needed per-layer snapshot state, same-key layer ordering, legacy-wrapper handling,
fresh-control markers, a wrapper-value replay and snapshot cleanup - most of the
reconciler change, for a pattern with no uses in samples, docs or the agent kit.
Drop it and document the contract instead: an eagerly captured brush is a fixed
value; bind it with Theme.Ref. A keyed memo now re-runs its factory against itself
during a refresh, through the existing dirty-descendant path.
Also move the hosted-slot unmount fix (UpdateHostedChild / UpdateChildTracked) out;
it fixes a leak on main independent of refresh and goes in its own PR.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Artifact sizes for d91e7b1 vs the base branch (fb618d7).
Packages (compressed .nupkg)
Artifact
base
PR
Δ
Microsoft.UI.Reactor.nupkg
1.83 MB
1.83 MB
-2.38 KB (-0.13%)
✅
Microsoft.UI.Reactor.Advanced.nupkg
493.3 KB
493.3 KB
+6 B (0.00%)
≈
Microsoft.UI.Reactor.Devtools.nupkg
288.1 KB
288.1 KB
+8 B (0.00%)
≈
Assemblies in Microsoft.UI.Reactor
Artifact
base
PR
Δ
Reactor.Analyzers.dll
410.5 KB
410.5 KB
+0 B (0.00%)
≈
Reactor.dll
2.55 MB
2.55 MB
-4.00 KB (-0.15%)
✅
Reactor.Localization.Generator.dll
16.0 KB
16.0 KB
+0 B (0.00%)
≈
Reactor.Wrappers.Abstractions.dll
10.5 KB
10.5 KB
+0 B (0.00%)
≈
Reactor.Wrappers.Generator.dll
99.5 KB
99.5 KB
+0 B (0.00%)
≈
Assemblies in Microsoft.UI.Reactor.Advanced
Artifact
base
PR
Δ
Reactor.Advanced.dll
1.00 MB
1.00 MB
+0 B (0.00%)
≈
Assemblies in Microsoft.UI.Reactor.Devtools
Artifact
base
PR
Δ
Microsoft.UI.Reactor.Devtools.dll
788.0 KB
788.0 KB
+0 B (0.00%)
≈
✅ smaller / ⚠️ larger / ≈ within noise. Sizes come from a Release dotnet pack on the CI runner: packages are the compressed .nupkg download size, assemblies the uncompressed DLL inside it. workflow run.
Coverage for d91e7b1 vs the base branch (fb618d7) — unit + selftest merged.
Metric
base
PR
Δ
Line
86.85%
86.80%
-0.05 pp
≈
Branch
77.36% (967/1250)
77.36% (967/1250)
0.00 pp
≈
No coverage change beyond the noise floor. ✅
✅ higher / ⚠️ lower / ≈ within noise. Δ is in percentage points; coverage is unit + selftest merged (Debug x64) on the CI runner. Cobertura reports attached to the workflow run as artifacts.
Simplifies Reactor’s resource-refresh implementation from #1329 by removing mounted-output snapshots and narrowing support for eagerly resolved brushes.
Changes:
Reuses memo self-diffing and removes snapshot bookkeeping.
Removes hosted-child tracking changes intended for a separate PR.
Updates selftests and documents the supported resource-refresh contract.
…r transparent wrappers
#1329 merged with the hosted-slot unmount fix, so it stays rather than being split
out (removing it would reintroduce double teardown on replacement and the popup/
Expander leaks). The FlipView keeps a per-item 'realized a control' map (no element
snapshot) so an Empty item can't misalign the update. A keyed memo or target-wrapping
flyout whose inner update returns a different control applies its own modifiers with
mount semantics - one rule instead of the fresh-control markers.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rebuilding the old view loses the baseline for ordinary updates too. With FlipView(items, ..., (item, _) => TextBlock(item.Name)), mutate an item's Name, then re-render with a fresh array containing that item. Both o.BuildItemView(i) and n.BuildItemView(i) now read the new name, so Element.ShallowEquals skips updating the TextBlock and its displayed text stays stale. The previous mounted-element record preserved the old text. Keep a reliable mounted-value baseline for ordinary updates and add this regression test; the presence-only map only preserves item alignment.
A view rebuilt from the old element reads the items' current values, so an item
changed in place compared equal to itself and stayed stale on an ordinary update.
Restore main's per-item (element, realized) record - the FlipView counterpart of the
element a templated ListView tags on its container - and cover the in-place change.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Fixed the "previously missed" FlipView finding in 6bc91d3. The FlipView keeps main's per-item record again: the element each item mounted and whether it realized a control. It diffs against that record, as a templated ListView diffs against the element tagged on its container, so an item changed in place is no longer compared to itself. New probe Replace_FlipViewItemChangedInPlaceUpdates: an item's Name is changed in place and the item is shown again in a fresh array, so the FlipView shows the new text. Mutation check: rebuilding the old view from o fails it.
The record holds item element graphs; an unmounted FlipView an app still holds must
not keep them, and FlipView never reaches pool cleanup. Restore main's clear at the
top of both unmount traversals and cover it.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Hosts: kept both sides. Main's DiagnosticsRootResolver alongside K's
diagnostics-implies-source-map setup; main's passRoot snapshot alongside K's
_renderingMountSite/_renderingFunctionRoot; on a null render, K's
RepublishRetainedRoot runs before main's _renderedRoot bookkeeping (independent).
API index regenerated (UPDATE_API_INDEX=1). K never relied on the secret
redaction #1332 removed: ReactorSource publishes static facts only.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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
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.
Summary
This simplifies the resource-refresh pass added in #1329.
Theme.NotifyResourcesChanged()still refreshes what it was built for:Theme.Refbindings,ThemeRefresource overrides, and values resolved duringRender. It still walks into every memoized subtree, kept list item and unchanged TreeView without remounting anything, so component state, selection and expansion survive.What's removed is support for brushes a memo factory or FlipView item builder captures eagerly with
ThemeRef.Resolve(...). To refresh those, #1329 recorded what every memo layer and FlipView item mounted and diffed against that record. That needed:KeyedMemoOutput,MoreKeyedMemoOutputs);That is most of #1329's reconciler code, for a pattern with 0 uses in samples, docs or the agent kit, against about 1,100 uses of
Theme.Ref. The styling guide and CHANGELOG now state the contract: a brush resolved eagerly and kept is a fixed value, so bind it withTheme.Ref.What stays, or is simplified:
UpdateHostedChild/UpdateChildTracked) from Hosts: ReactorHostControl idle wait; Theme.NotifyResourcesChanged() for runtime resource edits #1329 is unchanged.src/Reactor/Corechanges by +32/−348 lines against main.Linked issue / spec
Follow-up to #1329 (issue #1327).
Test plan
Rewrote
Hosting_NotifyResourcesChangedRefreshesMemoizedThemeModifiersaround the contract. After a resource edit, an ordinary re-render leaves values stale;NotifyResourcesChangedfrom a background thread then refreshes:Render.Nested components are not remounted (one mount, no cleanups). TreeView expansion and ListView selection survive.
New probes for controls replaced on an ordinary render: a flyout whose target changes type is torn down once and its margin and
OnMountreach the new target; a popup's always-remounting child keeps exactly one live copy.Mutation checks. Each change made the listed probes fail:
| Remove the unmount clear | the held-unmounted-FlipView probe |
| Remove the hosted-slot unmount | the popup one-live-copy probe |
--self-test --filter "Hosting_",--filter "DataGrid_"and--filter "AsyncResource.": 0 failures.maincrashed the same way on this machine (3 out of 3 shard-2 runs). CI is authoritative.src/Reactorandtests/Reactor.AppTests.Host(--no-restore): 0 errors.dotnet test tests/Reactor.Testsand the docs compile: not run locally. Restore needs packages from deps: Windows NuGet updates #1385 and nuget.org is unreachable from this machine. CI covers both.docs/guide/styling.mdmirrors the one plain-prose paragraph added to the template.Risk / breaking changes
No public API change; every removed member was
internal.Behavior change from #1329, which is unreleased: a brush captured eagerly inside a memo factory or FlipView item builder is no longer refreshed by
NotifyResourcesChanged. This is documented.