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
Add text-only component inspection to ReactorDiagnostics - #1332
Adds a provisional, text-only component-inspection surface to ReactorDiagnostics so a devtools inspector can go from a realized control to the component behind it without reflecting over reconciler internals or holding live Reactor objects.
Component lookup resolves a component's wrapper Border or a host root. Root anchors are the rendered control, ContentTarget, Window.Content, the dev-overlay wrapper, and a ReactorHostControl or its content.
A host claims its container only while its own content is installed there, so a stale host sharing that container can't answer for it.
When a root renders a component directly, its rendered control is also that child's wrapper. The wrapper wins there, and the root stays reachable through its other anchors.
Hosts publish the root that produced the displayed control (snapshotted when its render pass starts) together with the control, so a Mount from a root's own Render() never relabels the tree that pass commits. A root is no longer described once Mount retires it, even while its control is still on screen until the replacement renders.
Hooks and contexts describe themselves through internal virtuals on the hook cells, with no reflection. RenderContext.SnapshotHooks, which backs Reactor.Devtools' reactor.state, now uses the same seams. That drops its reflection while keeping its output.
Reactor owns the text:
Formatting: strings are quoted, collections are summarised from the count they advertise (never enumerated), and long values are cut.
Redaction:SecureString; secret-like type, element-type or member names; and strings or objects whose own text labels a secret value (JSON-quoted labels included). A value that would read back redacted is never written.
Parsing: for TrySetState, refused with a reason when it can't parse. A [Flags] value accepts only declared bits.
A throwing app ToString(), Count, Equals or props getter degrades to a placeholder instead of failing the description.
Applied properties come from a table that mirrors ApplyModifiers and its control-type ownership tests, kept in parity by a unit test. The whole decorator chain is merged in application order, and the list includes the caption-derived default AutomationProperties.Name.
ContentDialog tagging: with source mapping on, an open ContentDialog is tagged with its element, so its title and buttons resolve to the ContentDialog(...) call site. They live in a popup root, so this was the only way to attribute them. The tag is attribution-only (no phantom modifiers) and is cleared on close.
Render-path cost: reads are on demand, and only ReactorDiagnostics and devtools call them. The render path gains just two reference stores:
UseContext records the context it read, which also fixes memo change detection when a hook slot switches context.
Reference-list wiring keeps the authored list.
Thread affinity: everything is UI-thread-affine. Writes throw off the render thread.
Not in this PR: moving Reactor.Devtools' root-only reactor.state / reactor.fire reflection onto this API. It is a natural follow-up.
Added unit tests in tests/Reactor.Tests/Diagnostics/:
DiagnosticTextTests: formatting, collection counts, redaction (name / type / element type / text / string), parsing and refusals, and throwing ToString/Count.
ReactorDiagnosticsComponentsTests: snapshots of every primitive hook, props rows and fallbacks, contexts, state edits from text and their refusals, re-render, thread affinity, SnapshotHooks shape parity, applied-modifier map parity, decorator-chain merge, and the "text only" public-record check.
ReactorDiagnosticsReferenceEdgesTests, ReactorDiagnosticsDefaultAutomationNameTests and ContentDialogDiagnosticsTagTests.
Added selftest fixtures in tests/Reactor.AppTests.Host/SelfTest/Fixtures/:
Diagnostics_ComponentInspection, Diagnostics_RootAnchors and Diagnostics_ContentDialogTag.
Diagnostics_ReferenceEdges, Diagnostics_ReferenceEdgesPendingId and Diagnostics_ReferenceEdgesUnmountedLabel.
Mutation-checked the key oracles: secret redaction paths, the props fallback, the "null" / editable gate, truncation, Editable, Count probing, host and host-control ownership, unmounted-label resolution, and pending-LabeledBy teardown. Each mutation turned its targeted tests red.
dotnet build tests/Reactor.Tests -p:Platform=x64 -p:SkipSignaturesGen=true, then dotnet test tests/Reactor.Tests --no-build -p:Platform=x64: passed, 14,797 total, 0 failed, 64 skipped.
dotnet test tests/Reactor.SourceMap.Tests -p:Platform=x64: passed, 74/74.
dotnet test tests/Reactor.DocPipeline.Tests -p:Platform=x64: passed, 479/479.
dotnet run --project tests/Reactor.AppTests.Host --no-build -c Debug -p:Platform=x64 -- --self-test --filter <prefix>: 0 failures for each of these filters:
NativeAOT host published with dotnet publish tests/Reactor.AppTests.Host -c Release -p:Platform=x64 -r win-x64 -p:PublishAotInternal=true --self-contained. --self-test --filter Diagnostics_: 0 failures. Under AOT, trimmed props report a (members unavailable) row.
dotnet build src/Reactor/Reactor.csproj -c Release -p:Platform=x64: succeeded, no CS/IL warnings (trim/AOT warnings are errors in Release).
API index regenerated; both reactor.api.txt copies are byte-identical.
Docs: docs/_pipeline/templates/source-mapping.md.dt, compiled with mur docs compile --no-screenshots. The example is snippet-backed from the diagnostics doc app.
Repo pr-review skill (7 dimensions plus a multi-model cross-check): 7 rounds until clean. Every finding was fixed or dismissed with a reason; the dismissals are under Risk below.
CI: all checks green.
Risk / breaking changes
Additive, provisional public API.ReactorDiagnostics became partial, a non-breaking change. No existing public signature changed.
Internal seams on public types:Ref<T> implements an internal IRefValue, and IPropsReceiver gains two internal members that Component<TProps> implements explicitly. Neither is visible publicly.
Trimming / AOT: the only reflection is two IL2075-suppressed, diagnostics-only reads: props members and a collection's public Count. Both degrade when trimmed (a type-name row or "count unknown") and never fall back to ToString(). SnapshotHooks and DevtoolsStateTool lost their previous reflection suppressions.
Behaviour fixes outside diagnostics:
A nested component's rerender marshals onto its own host's UI thread (the reconciler's owning dispatcher) instead of the process-wide ReactorApp.UIDispatcher, which belongs to another window when hosts run on several UI threads.
A caption change replaces Reactor's truncated (>100 characters) default AutomationProperties.Name instead of mistaking it for an author override.
UseContext refreshes its slot's context every render.
Reference teardown clears a pending .LabeledBy("id").
The deferred .LabeledBy("id") handler keeps a still-pending id after Loaded.
Redaction is heuristic (by name, type and text). It reduces accidental exposure in a dev-time inspector; it is not a security boundary.
Known limitation: a host installed straight into Window.Content whose root renders a component directly has no separate root anchor; that element describes the child.
Dismissed review findings:
"Context default should track provider provenance": that needs per-render tracking of which provider supplied each value. Instead, Kind is documented as "default when the value equals the context default".
"reactor.api.txt drops reference nullability and prints out as ref": this is the generator's existing convention repo-wide.
An inspector (winapp devtools, Reactor.Devtools) can now go from a realized
control to the component behind it without reflecting over reconciler
internals or holding live Reactor objects:
- DescribeComponent(UIElement) -> ComponentSnapshot: name, kind, root flag,
props, hooks (State) and consumed contexts, every value as text.
- TrySetState(UIElement, hookIndex, text, out error): parses text to the
hook's type (numbers, char, bool, enums, dates, times, GUIDs, strings and
nullables; "null" for strings and nullables), writes it and re-renders
like the setter; refuses other types, non-state hooks and secrets with a
reason.
- Rerender(UIElement): re-renders the component, bypassing memoization.
- GetAppliedProperties(UIElement): the WinUI properties a control's common
modifiers set, plus the caption-derived default AutomationProperties.Name.
- GetReferenceEdges(UIElement): the references a control declares,
including pending ones whose target has not mounted. A resolved edge's
Target is the one live object (the mounted control).
Reactor owns formatting (quoted strings, collections summarised from the
count they advertise - never enumerated - and truncation) and redaction
(SecureString; types, element types and names ending in Password, Secret,
Credential, Token, ApiKey, PrivateKey or ConnectionString; and objects whose
own text names such a member). Lookup goes through a weak per-host
reconciler registry and a host root resolver that only claims a container
while its own content is installed there. Hook cells and contexts describe
themselves via internal virtuals; RenderContext.SnapshotHooks now uses the
same seams (no reflection) and Reactor.Devtools' count probe shares
DiagnosticText.TryCount. The only reflection is guarded reads of props
members and collection Count, which degrade - never to ToString() - when
trimmed. All reads are on demand and UI-thread-affine, with no render-path
cost. With source mapping on, an open ContentDialog is tagged with its
element so its chrome resolves to the ContentDialog(...) call site.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Artifact sizes for e174d59 vs the base branch (20a076b).
Packages (compressed .nupkg)
Artifact
base
PR
Δ
Microsoft.UI.Reactor.nupkg
1.82 MB
1.85 MB
+34.3 KB (+1.84%)
⚠️
Microsoft.UI.Reactor.Advanced.nupkg
493.4 KB
493.4 KB
+3 B (0.00%)
≈
Microsoft.UI.Reactor.Devtools.nupkg
288.2 KB
287.9 KB
-303 B (-0.10%)
✅
Assemblies in Microsoft.UI.Reactor
Artifact
base
PR
Δ
Reactor.Analyzers.dll
410.5 KB
410.5 KB
+0 B (0.00%)
≈
Reactor.dll
2.54 MB
2.60 MB
+65.5 KB (+2.52%)
⚠️
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
787.5 KB
-512 B (-0.06%)
✅
✅ 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 e174d59 vs the base branch (20a076b) — unit + selftest merged.
Metric
base
PR
Δ
Line
86.75%
86.97%
+0.22 pp
✅
Branch
77.44% (968/1250)
77.18% (974/1262)
-0.26 pp
⚠️
✅ 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.
…e, keep pending LabeledBy
- Mark the five public ReactorDiagnostics members [UIThreadOnly] so REACTOR_THREAD_001
flags background calls (they call ThreadAffinity.ThrowIfNotOnUIThread).
- FormatPlain applies the secret-member text check to strings and IFormattable
values too, not only to objects' own ToString().
- The deferred .LabeledBy("id") Loaded handler retires PendingLabeledBy only when
the target resolves, so an unresolved request stays reported as pending by
GetReferenceEdges even when the control is untagged.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This scalar gate omits the other text-supported scalar types. For example, DateTime and TimeSpan props fall through to reflection and are emitted as many member rows, while Guid can fall back to “members unavailable,” contradicting the one-row Props contract. Use the parser's scalar classification here so formatting and props projection agree.
Distinguish UseRef hooks from state hooks with Ref values
src/Reactor/Core/RenderContext.cs:2689
The generic argument alone does not identify a UseRef cell: UseState(new Ref<int>(...)) also creates ValueHookState<Ref<int>>, but sets SetterKindState. This branch reports that real state hook as ref, unwraps its value, and refuses edits as though it came from UseRef. Hook identity must account for SetterKind (and only unwrap IRefValue for an actual ref hook) while preserving the legacy SnapshotHooks shape.
Moves the inline ReactorDiagnostics example into the compiled diagnostics
doc app so InlineSnippetLedgerTests passes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Under NativeAOT the props record's property metadata is trimmed, so Props
degrades to a single '(members unavailable)' row. The fixture now accepts
that on AOT (still asserting the secret never leaks) and the docs say so.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…wing props
- ReactorHostControl attributes its Content only while it is Reactor's own
rendered root or overlay wrapper (Content is public).
- Document and pin the wrapper-wins precedence when a root renders a
component directly.
- A throwing props getter is reported as '<value unavailable>'.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…f; clear dialog tag
- TrySetState refuses a parsed value its next snapshot would redact.
- A [Flags] enum accepts only combinations of declared bits.
- Hook kind keys on origin: UseState(new Ref<T>()) is a state hook, not a
ref hook; reactor.state keeps its historical 'useRef' naming.
- A ContentDialog's diagnostics tag is cleared when ShowAsync completes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Normalize captions before author override and name updates
src/Reactor/Core/Reconciler.cs:3199
The update path normalizes only the new caption. For captions longer than 100 characters, the live name contains the truncated old caption, but authorOverride compares it with the untruncated oldCaption and incorrectly refuses every later update. A blank new caption also returns here without clearing the previous Reactor-derived name. Normalize both captions, distinguish an author override against the normalized old default, then set or clear the DP so the live value and GetAppliedProperties stay accurate.
… per render
- The labelled-secret pattern allows quotes around the member name and
value, so JSON-shaped text is redacted.
- UseContext refreshes the slot's Context each render (like LastValue), so
a slot switched to another context is described, redacted and
change-detected against the context it last read.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The realized dialog is tagged with the full element even though common modifiers and reference modifiers were applied to the collapsed placeholder returned by the handler, not to this ContentDialog. Consequently GetAppliedProperties(dialog) and the AutomationId branch of GetReferenceEdges(dialog) report declarations the dialog never received. Store an attribution-only copy without modifiers so source mapping still works without creating false diagnostics.
A non-record props object whose hand-written ToString() names a secret
with a value is withheld whole (Redacted) instead of listed member by
member; a record's generated text is not used, since its rows are already
redacted one by one (tests, mutation-checked on both arms).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ction
- The own-text check is skipped only for a record whose ToString and
PrintMembers are both [CompilerGenerated]; a hand-written record
ToString is judged like any object (test).
- When trimming strips that metadata (NativeAOT) the props are judged by
their own text and may become one redacted row; the selftest accepts
that shape (the secret still never leaks). Fixes the AOT Selftests
failure on CompInspect_ClassProps.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Resolve mounted root from its published destination
src/Reactor/Hosting/ReactorHost.cs:210
This resolves the container from the mutable ContentTarget request rather than the destination that produced the published _renderedRoot. If an Empty() root is mounted in target A and the app switches ContentTarget to B before the queued render, A immediately stops resolving even though it is still the only anchor displaying the old root; B correctly has not been claimed yet, so the root becomes unreachable during exactly the remount gap this code is meant to preserve. Track the rendered destination alongside _renderedRoot (using the existing claimed target, and releasing it when a render publishes to Window.Content) and resolve against that published destination until the next render succeeds.
ReactorHost resolves root anchors against the container the displayed
root was rendered into (the claimed ContentTarget, or the window content
once a window write releases the claim), not the requested ContentTarget.
Retargeting takes effect when the next render publishes, so the previous
container keeps anchoring the root through that gap (selftest,
mutation-checked).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ublic fields
- Quoted and unquoted values are matched separately: inside quotes any
non-space character is a value (",hunter2"), while "" and " " stay
plain labels.
- Generated record text also prints public fields, which are not listed
as rows, so a record with public fields is judged by its own text
(test, mutation-checked).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Long caption updates misclassify Reactor’s truncated automation name as an author override, leaving the UIA name stale.
0 open findings
Previously missed (1)
In code that hasn't changed since last review
UIA name stays stale when a truncated default caption changes
src/Reactor/Core/Reconciler.cs:3610
When the previous caption exceeds 100 characters, ApplyDefaultAutomationName stored its truncated form, but this update path compares the live name with the untruncated oldCaption. It therefore mistakes Reactor's own default for an author override, leaves the UIA name stale after the caption changes, and the new diagnostics surface no longer reports the default. Compare against DefaultAutomationNameFromCaption(oldCaption) as well; the truncation test currently covers only the mount path.
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
UpdateDefaultAutomationName compared the live name only with the full
previous caption, so after a >100-character caption (whose default is
truncated) Reactor mistook its own default for an author override and
left the UIA name stale. The decision moves into a pure helper that also
accepts the truncated form (test, mutation-checked).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… anchor survives retarget
- A component's rerender callback marshals onto its reconciler's owning
dispatcher (OwningDispatcher, renamed from DiagnosticsDispatcher), not
the process-wide ReactorApp.UIDispatcher, which can be another UI
thread's. Falls back to the process-wide one when no host set it
(selftest with a shut-down foreign process dispatcher, mutation-checked).
- ReactorHost keeps the window content (e.g. the overlay wrapper) as the
root's anchor after a ContentTarget is requested, until a render
publishes there (selftest, mutation-checked).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ohfi-reactor-component-diagnostics
- Hosts: keep #1328/#1323's root lifecycle (RetireRoot, deferred
retirement, _releaseReplacedTreeOnNullRender, RootRenderDiagnostics)
alongside the diagnostics rendered-root publication. The published root
is snapshotted at the start of the pass (beside _renderingRoot), so a
Mount() from the root's own Render() does not relabel the tree that pass
commits; RetireRoot clears it once the root is retired (immediately or
in the deferred pass), so a retired root is never described or written.
- Selftests: the remount gap now describes no root (the old one is
retired at Mount); new mid-pass Mount case; mutation-checked.
- SourceLocation's new required ColumnNumber in a unit test.
- CHANGELOG keeps both entries; API index regenerated (unchanged).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Generated-record exemption now requires a compiler-generated PrintMembers
on every record in the base chain, so a hand-written base.PrintMembers
that prints a secret under another name is judged by its own text.
- An Empty() render only claims a ContentTarget that is actually empty; an
occupied container keeps resolving to the host whose content it shows.
- Unit test + RootAnchor selftest, both mutation-checked.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
GetAppliedProperties sends DescribedByRefs, FlowsToRefs, and FlowsFromRefs through FormatPlain, bypassing Format's count-only collection handling. Ordinary arrays therefore show only a type name, while a custom IReadOnlyList<ElementRef> whose ToString() enumerates runs that enumeration on the UI thread during inspection. Route non-string collections through the existing count-summary path before this switch. Add a FormatPlain regression asserting that a self-formatting collection's ToString() and enumerator are never called.
FormatPlain (GetAppliedProperties) now routes non-string collections such as
DescribedByRefs/FlowsToRefs/FlowsFromRefs through the same count summary
Format uses. It never calls their ToString() or enumerator on the UI thread.
Adds a mutation-checked regression test.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
- ClaimContentTarget returns early when this host already owns the
target, so repeat renders allocate nothing.
- Document why SameRootAs compares identity, not the render delegate:
every Mount allocates a fresh Component/FuncContext.
- Clarify that only an app name equal to the derived (possibly shortened)
default is indistinguishable from it.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Host ownership, reentrant root lifecycles and cross-thread dispatch changes warrant final human review despite no confirmed blocking findings.
0 open findings
🧠 Review effort: Balanced
This branch has not been deployed
No deployments
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
Adds a provisional, text-only component-inspection surface to
ReactorDiagnosticsso a devtools inspector can go from a realized control to the component behind it without reflecting over reconciler internals or holding live Reactor objects.Borderor a host root. Root anchors are the rendered control,ContentTarget,Window.Content, the dev-overlay wrapper, and aReactorHostControlor its content.GetHosts()(Diagnostics: enumerate Reactor hosts and their roots; tag component boundaries for inspectors #1318), so there is one registry. Each host exposes its reconciler and installs a root resolver; a reconciler used without a host is not inspected, and one owned by another UI thread is skipped.Mountfrom a root's ownRender()never relabels the tree that pass commits. A root is no longer described onceMountretires it, even while its control is still on screen until the replacement renders.RenderContext.SnapshotHooks, which backsReactor.Devtools'reactor.state, now uses the same seams. That drops its reflection while keeping its output.SecureString; secret-like type, element-type or member names; and strings or objects whose own text labels a secret value (JSON-quoted labels included). A value that would read back redacted is never written.TrySetState, refused with a reason when it can't parse. A[Flags]value accepts only declared bits.ToString(),Count,Equalsor props getter degrades to a placeholder instead of failing the description.ApplyModifiersand its control-type ownership tests, kept in parity by a unit test. The whole decorator chain is merged in application order, and the list includes the caption-derived defaultAutomationProperties.Name.ContentDialogis tagged with its element, so its title and buttons resolve to theContentDialog(...)call site. They live in a popup root, so this was the only way to attribute them. The tag is attribution-only (no phantom modifiers) and is cleared on close.ReactorDiagnosticsand devtools call them. The render path gains just two reference stores:UseContextrecords the context it read, which also fixes memo change detection when a hook slot switches context.Reactor.Devtools' root-onlyreactor.state/reactor.firereflection onto this API. It is a natural follow-up.Linked issue / spec
Fixes #1331. Related: spec 010 (source mapping), spec 057 §3.1 (reference graph).
Test plan
tests/Reactor.Tests/Diagnostics/:DiagnosticTextTests: formatting, collection counts, redaction (name / type / element type / text / string), parsing and refusals, and throwingToString/Count.ReactorDiagnosticsComponentsTests: snapshots of every primitive hook, props rows and fallbacks, contexts, state edits from text and their refusals, re-render, thread affinity,SnapshotHooksshape parity, applied-modifier map parity, decorator-chain merge, and the "text only" public-record check.ReactorDiagnosticsReferenceEdgesTests,ReactorDiagnosticsDefaultAutomationNameTestsandContentDialogDiagnosticsTagTests.tests/Reactor.AppTests.Host/SelfTest/Fixtures/:Diagnostics_ComponentInspection,Diagnostics_RootAnchorsandDiagnostics_ContentDialogTag.Diagnostics_ReferenceEdges,Diagnostics_ReferenceEdgesPendingIdandDiagnostics_ReferenceEdgesUnmountedLabel."null"/ editable gate, truncation,Editable, Count probing, host and host-control ownership, unmounted-label resolution, and pending-LabeledByteardown. Each mutation turned its targeted tests red.dotnet build tests/Reactor.Tests -p:Platform=x64 -p:SkipSignaturesGen=true, thendotnet test tests/Reactor.Tests --no-build -p:Platform=x64: passed, 14,797 total, 0 failed, 64 skipped.dotnet test tests/Reactor.SourceMap.Tests -p:Platform=x64: passed, 74/74.dotnet test tests/Reactor.DocPipeline.Tests -p:Platform=x64: passed, 479/479.dotnet run --project tests/Reactor.AppTests.Host --no-build -c Debug -p:Platform=x64 -- --self-test --filter <prefix>: 0 failures for each of these filters:Diagnostics_,ContentDialog,Dialog,Flyout,LabeledBy,HostControl,ReactorHost,OverlayA11y,ModifierUnset,SourceMap,Issue487,PDM_Stackdotnet publish tests/Reactor.AppTests.Host -c Release -p:Platform=x64 -r win-x64 -p:PublishAotInternal=true --self-contained.--self-test --filter Diagnostics_: 0 failures. Under AOT, trimmed props report a(members unavailable)row.dotnet build src/Reactor/Reactor.csproj -c Release -p:Platform=x64: succeeded, no CS/IL warnings (trim/AOT warnings are errors in Release).reactor.api.txtcopies are byte-identical.docs/_pipeline/templates/source-mapping.md.dt, compiled withmur docs compile --no-screenshots. The example is snippet-backed from thediagnosticsdoc app.pr-reviewskill (7 dimensions plus a multi-model cross-check): 7 rounds until clean. Every finding was fixed or dismissed with a reason; the dismissals are under Risk below.Risk / breaking changes
ReactorDiagnosticsbecamepartial, a non-breaking change. No existing public signature changed.Ref<T>implements an internalIRefValue, andIPropsReceivergains two internal members thatComponent<TProps>implements explicitly. Neither is visible publicly.IL2075-suppressed, diagnostics-only reads: props members and a collection's publicCount. Both degrade when trimmed (a type-name row or "count unknown") and never fall back toToString().SnapshotHooksandDevtoolsStateToollost their previous reflection suppressions.ReactorApp.UIDispatcher, which belongs to another window when hosts run on several UI threads.AutomationProperties.Nameinstead of mistaking it for an author override.UseContextrefreshes its slot's context every render..LabeledBy("id")..LabeledBy("id")handler keeps a still-pending id afterLoaded.Window.Contentwhose root renders a component directly has no separate root anchor; that element describes the child.defaultshould track provider provenance": that needs per-render tracking of which provider supplied each value. Instead,Kindis documented as "default when the value equals the context default".reactor.api.txtdrops reference nullability and printsoutasref": this is the generator's existing convention repo-wide.