Skip to content

Add text-only component inspection to ReactorDiagnostics - #1332

Open
Alexandre Zollinger Chohfi (azchohfi) wants to merge 51 commits into
mainfrom
azchohfi-reactor-component-diagnostics
Open

Alexandre Zollinger Chohfi (azchohfi) wants to merge 51 commits into
mainfrom
azchohfi-reactor-component-diagnostics

Conversation

@azchohfi

@azchohfi Alexandre Zollinger Chohfi (azchohfi) commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

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.

ComponentSnapshot? ReactorDiagnostics.DescribeComponent(UIElement element);
bool ReactorDiagnostics.TrySetState(UIElement element, int hookIndex, string text, out string? error);
bool ReactorDiagnostics.Rerender(UIElement element);
IReadOnlyList<AppliedProperty> ReactorDiagnostics.GetAppliedProperties(UIElement control);
IReadOnlyList<ReferenceEdgeSnapshot> ReactorDiagnostics.GetReferenceEdges(UIElement control);
// records: ComponentSnapshot, DiagnosticValue, AppliedProperty, ReferenceEdgeSnapshot (strings, ints, bools)
  • 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.
    • Lookups walk the host registry behind 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.
    • 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.

Linked issue / spec

Fixes #1331. Related: spec 010 (source mapping), spec 057 §3.1 (reference graph).

Test plan

  • 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:
    • Diagnostics_, ContentDialog, Dialog, Flyout, LabeledBy, HostControl, ReactorHost, Overlay
    • A11y, ModifierUnset, SourceMap, Issue487, PDM_Stack
  • 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>
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

📦 Build metrics

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.

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.

Copilot review overview

🟡 Changes recommended

Secret-text redaction can be bypassed, pending AutomationId edges can disappear, and the new UI-thread APIs lack required analyzer metadata.

Review effort: Balanced
Findings: 1 High severity · 3 Medium severity

Open (4)
What changed in this PR

Adds a provisional diagnostics API for inspecting, editing, and rerendering Reactor components through text-only snapshots.

Changes:

  • Adds component, hook, property, and reference inspection APIs.
  • Adds formatting, parsing, redaction, host-resolution, and dialog-tagging infrastructure.
  • Adds comprehensive unit/selftests, documentation, API indexes, and changelog coverage.
File Description
tests/​Reactor.Tests/​Diagnostics/​ReactorDiagnosticsReferenceEdgesTests.cs Tests reference-edge projection.
tests/​Reactor.Tests/​Diagnostics/​ReactorDiagnosticsDefaultAutomationNameTests.cs Tests derived automation names.
tests/​Reactor.Tests/​Diagnostics/​ReactorDiagnosticsComponentsTests.cs Tests component diagnostics behavior.
tests/​Reactor.Tests/​Diagnostics/​DiagnosticTextTests.cs Tests text formatting and parsing.
tests/​Reactor.Tests/​Diagnostics/​ContentDialogDiagnosticsTagTests.cs Tests dialog-tagging gate.
tests/​Reactor.AppTests.Host/​SelfTest/​SelfTestFixtureRegistry.cs Registers diagnostics fixtures.
tests/​Reactor.AppTests.Host/​SelfTest/​Fixtures/​ReferenceEdgeFixtures.cs Exercises live reference resolution.
tests/​Reactor.AppTests.Host/​SelfTest/​Fixtures/​ContentDialogTagFixtures.cs Exercises live dialog tagging.
tests/​Reactor.AppTests.Host/​SelfTest/​Fixtures/​ComponentInspectionFixtures.cs Exercises live component inspection.
src/​Reactor/​Hosting/​ReactorHostControl.Diagnostics.cs Resolves host-control roots.
src/​Reactor/​Hosting/​ReactorHostControl.cs Installs diagnostics resolver.
src/​Reactor/​Hosting/​ReactorHost.cs Resolves hosted roots and anchors.
src/​Reactor/​Core/​V1Protocol/​ReferenceEdge.cs Adds shared reference labels.
src/​Reactor/​Core/​V1Protocol/​ReactorBindingT.cs Shares binding-slot constant.
src/​Reactor/​Core/​V1Protocol/​OverlayLifecycle.cs Tags live content dialogs.
src/​Reactor/​Core/​RenderContext.cs Exposes reflection-free hook diagnostics.
src/​Reactor/​Core/​Reconciler.Diagnostics.cs Registers and resolves reconcilers.
src/​Reactor/​Core/​Reconciler.cs Hooks registry lifecycle and name helper.
src/​Reactor/​Core/​Diagnostics/​ReactorDiagnostics.References.cs Adds reference-edge API.
src/​Reactor/​Core/​Diagnostics/​ReactorDiagnostics.cs Makes diagnostics class partial.
src/​Reactor/​Core/​Diagnostics/​ReactorDiagnostics.Components.cs Adds component inspection APIs.
src/​Reactor/​Core/​Diagnostics/​DiagnosticText.cs Implements text conversion and redaction.
src/​Reactor/​Core/​Diagnostics/​AppliedModifierMap.cs Maps modifiers to WinUI properties.
src/​Reactor/​Core/​Context.cs Adds diagnostic context metadata.
src/​Reactor/​Core/​Component.cs Exposes boxed props internally.
src/​Reactor.Devtools/​ReferenceOverlay.cs Reuses shared reference labels.
src/​Reactor.Devtools/​DevtoolsStateTool.cs Reuses collection-count logic.
skills/​reactor.api.txt Updates public API index.
plugins/​reactor/​skills/​reactor-dsl/​references/​reactor.api.txt Syncs packaged API index.
docs/​guide/​source-mapping.md Documents component inspection.
docs/​_pipeline/​templates/​source-mapping.md.dt Updates generated-doc source.
CHANGELOG.md Records the new diagnostics surface.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs
Comment thread src/Reactor/Core/Diagnostics/ReactorDiagnostics.Components.cs
Comment thread src/Reactor/Core/Diagnostics/ReactorDiagnostics.References.cs
Comment thread src/Reactor/Core/Diagnostics/ReactorDiagnostics.References.cs
Comment thread src/Reactor/Core/Diagnostics/AppliedModifierMap.cs Fixed
Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs Fixed
Comment thread src/Reactor/Core/Reconciler.Diagnostics.cs Fixed
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

🧪 Merged coverage

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>
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.

Copilot review overview

🟡 Changes recommended

Root-anchor ambiguity, incorrect host-content ownership, hook misclassification, and scalar-props projection need correction.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (4)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Use complete scalar classification for one-row prop projection

src/​Reactor/​Core/​Diagnostics/​DiagnosticText.cs:296

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.

Medium severity 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.

Comment thread src/Reactor/Core/Diagnostics/ReactorDiagnostics.Components.cs
Comment thread src/Reactor/Hosting/ReactorHostControl.Diagnostics.cs Outdated
Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs Fixed
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>
…onent-diagnostics

# Conflicts:
#	CHANGELOG.md
…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>

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.

Copilot review overview

🟡 Changes recommended

State redaction, hook classification, enum validation, and dialog-tag cleanup have unresolved correctness issues.

Review effort: Balanced
Findings: 1 High severity · 3 Medium severity

Open (4)
Resolved since last review (2)

Comment thread src/Reactor/Core/Diagnostics/ReactorDiagnostics.Components.cs
Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs
Comment thread src/Reactor/Core/RenderContext.cs Outdated
Comment thread src/Reactor/Core/V1Protocol/OverlayLifecycle.cs
…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>

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.

Copilot review overview

🟡 Changes recommended

Secret redaction has two bypasses, and caption updates can leave stale automation names.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (4)
Previously missed (1)

In code that hasn't changed since last review

Medium severity 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.

Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs
Comment thread src/Reactor/Core/Diagnostics/ReactorDiagnostics.Components.cs
… 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>

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.

Copilot review overview

🟡 Changes recommended

Structured props can bypass promised redaction, private getters can be exposed, and ContentDialog tags can produce false property diagnostics.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Avoid attributing placeholder modifiers to realized dialogs

src/​Reactor/​Core/​V1Protocol/​OverlayLifecycle.cs:225

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.

Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs Outdated
Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs Outdated
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.

🟡 Changes recommended

Structured props can bypass the documented own-text secret redaction and expose sensitive values.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs
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>

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.

🟡 Changes recommended

Hand-written record formatting can bypass own-text secret detection and expose a value the redaction contract promises to withhold.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs Outdated
…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>

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.

🔵 Needs a closer look

Root inspection can lose the displayed root’s only anchor during ContentTarget retargeting.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity 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.

🧠 Review effort: Balanced

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>

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.

🟡 Changes recommended

Two redaction gaps can expose secret values through punctuation-leading JSON values or generated record fields.

2 open findings

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs
Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs
…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>

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.

🔵 Needs a closer look

The cross-cutting public API, redaction, thread-affinity, and host-lifecycle changes warrant final human review despite extensive coverage.

0 open findings

2 resolved since last review

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

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.

🔵 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

Medium severity 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>

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.

🟡 Changes recommended

Root retargeting can temporarily lose valid anchors, and nested diagnostics writes can rerender on the wrong UI thread.

2 open findings

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread src/Reactor/Core/RenderContext.cs
Comment thread src/Reactor/Hosting/ReactorHost.cs Outdated
… 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>

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.

🔵 Needs a closer look

No blocking defect was identified, but the broad reconciler, host-lifecycle, threading, and redaction changes warrant final human review.

0 open findings

2 resolved since last review

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

…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>
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.

🟡 Changes recommended

Inherited record formatting can bypass redaction, and an empty-root render can invalidate another host’s container anchor.

2 open findings

🧠 Review effort: Balanced

Comment thread src/Reactor/Core/Diagnostics/DiagnosticText.cs Outdated
Comment thread src/Reactor/Hosting/ReactorHost.cs Outdated
- 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>

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.

🔵 Needs a closer look

Host ownership, cross-thread rerenders, and trimming-sensitive redaction warrant human review, and collection formatting remains unresolved.

0 open findings

2 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Avoid FormatPlain for reference collections in GetAppliedProperties

src/​Reactor/​Core/​Diagnostics/​DiagnosticText.cs:178

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.

🧠 Review effort: Balanced

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>

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.

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.

Copilot review overview

3 open findings

🧠 Review effort: Lite

Comment thread src/Reactor/Core/Reconciler.Diagnostics.cs
Comment thread src/Reactor/Hosting/ReactorHost.cs
Comment thread src/Reactor/Core/Diagnostics/AppliedModifierMap.cs Outdated
- 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>

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.

🔵 Needs a closer look

Root retirement, shared-container ownership, and cross-thread dispatch changes warrant final maintainer review despite no confirmed blocking findings.

0 open findings

3 resolved since last review

🧠 Review effort: Balanced

…onent-diagnostics

# Conflicts:
#	CHANGELOG.md

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.

🔵 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
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.

[Feature] Text-only component inspection API for devtools (ReactorDiagnostics)

2 participants