Skip to content

Guide audit: fix stale samples, phantom APIs, and the doc-pipeline gaps that let them rot - #1120

Merged
Alexandre Zollinger Chohfi (azchohfi) merged 67 commits into
mainfrom
azchohfi-guide-sample-audit
Aug 26, 2026
Merged

Guide audit: fix stale samples, phantom APIs, and the doc-pipeline gaps that let them rot#1120
Alexandre Zollinger Chohfi (azchohfi) merged 67 commits into
mainfrom
azchohfi-guide-sample-audit

Conversation

@azchohfi

@azchohfi Alexandre Zollinger Chohfi (azchohfi) commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Audit of the entire user guide for staleness, run as nine parallel sessions over disjoint slices, then integrated. Every slice's commits are preserved.

Why this was needed

The docs pipeline compiles code blocks written as ```csharp snippet="topic/id", but not bare ```csharp blocks, ```xml blocks, prose inside backticks, code inside string literals, or /// XML doc comments. At the start, 239 of 654 code blocks (37%) had no compile gate at all.

That is where the rot was. All 50 doc apps built clean on main the whole time — the snippet-backed code was fine. The damage was concentrated exactly where nothing looked.

Headline fixes

  • The documented minimal .csproj did not work for anyone outside this repo. index.md.dt told readers to use <ProjectReference Include="..\Reactor\Reactor.csproj" />. Now a PackageReference pinned to the {{reactorVersion}} token. Verified by actually building it: the block was extracted from the template by regex, scaffolded outside the repo with all package sources cleared, and compiled. Six variants were tested to establish that <Platforms> is neither necessary nor sufficient — the load-bearing property is the RuntimeIdentifier auto-resolve line, without which a bare dotnet run (which the next sentence tells the reader to do) fails with the Windows App SDK architecture error.
  • controls.md claimed to catalogue "every control" and listed 46 of ~93. Three whole categories were missing. Now 99 rows, plus a ControlCatalogCompletenessTests gate that parses the registration bootstrap and fails the build when a registered control has no catalog row.
  • A documented SetWindowRgn recipe was a double-free. using var region = … then SetWindowRgn(hwnd, region.DangerousGetHandle(), true) — the system takes ownership on success, so the using freed GDI memory the compositor still read.
  • charting documented .Margin(t, r, b, l) in CSS order; the real signature is Margin(left, top, right, bottom). Silently wrong margins, no error.
  • UseEffect's reference page documented 1 of its 8 overloads. The generator keyed pages by short name, so overloads collided and all but one were silently dropped — including the Func<Action> cleanup form.
  • Unresolvable <!-- ref:X --> markers were emitted verbatim into published Markdown, rendering as three invisible gaps mid-sentence in win2d-canvas.md.
  • docs check-tier --topic fabricated errors for any topic whose app directory name differs from its template id (11 topics), reporting found 0 snippet refs for pages with five.
  • ~40 phantom APIs removed across the guide: UseTheme(), RenderContext.Current, ElementDescription.Of, mur devtools serve, ProgressBar(...), VStackElement, A11Y_KEYBOARD_001 (a fabricated analyzer ID promising runtime coverage that doesn't exist), Optional.Of(...), Text(...) as an element factory, and more.

The rot ran backwards, into shipped source

Text(...), UseTheme(), Optional.Of(...) and three With*Transition modifiers were not only in the guide — they were in /// doc comments in src/, which ship in Reactor.xml as the IntelliSense every NuGet consumer sees. In several cases the templates were quoting the source comment; extending-reactor-controls.md.dt tracks Optional.cs's wording almost verbatim. Element.cs's implicit string operator calls Factories.TextBlock(text) while its own doc comment two lines above said Text("Hello").

Fixing only the guide would have let the source re-seed it.

The same phantoms had also reached contributor guidance: AGENTS.md and two pr-review skill dimensions documented RegisterV1BuiltInHandlers (deleted by spec 048) and a PoolableWireFlags type that never existed. Those misdirect every future contributor and agent session before work starts.

Durability, not just cleanup

Cleanup alone would rot again, so this adds gates:

  • REACTOR_DOC_PHANTOM_001 — a docs-pipeline lint over templates, string literals and /// comments. Deliberately not a mur check rule: CheckCommand only invokes rules per parsed MSBuild diagnostic and prints ok when there are none, so a rule over surfaces that compile clean could never fire. Ships at Error severity, so mur docs compile --ci fails the Docs build on new rot; Warning was tried first and did not gate CI. The pre-existing backlog still passes via a per-(file, phantom) ceiling budget rather than a blanket suppression (new rot fails; fixing rot passes). The lint reads fenced blocks and inline code spans, since code formatting in prose reads as an endorsement, and matches phantom receivers (UI.Text()) as well as phantom members.
  • AgentKitMarkdown_NamesNoPhantomApis — extends the same matcher over the plugins/ and skills/ markdown packed into the NuGet under agentkit/, which is what an AI assistant reads when writing Reactor code. It asserts it found files before reporting clean, so a broken sweep reddens instead of looking green.
  • ControlCatalogCompletenessTests — makes the "every control" claim checkable.
  • MinimalCsprojDocTests — asserts the landing-page csproj parses, references the package not a project path, uses the version token not a literal, and keeps the RuntimeIdentifier line.
  • ~130 bare blocks promoted to compiled snippets, so CI enforces them permanently. Promotion paid for itself immediately — it surfaced defects nobody predicted, including ComponentElement having no .Set, a WithKey example that never compiled (CS0314), and a keys.FirstOrDefault() that compiled fine but silently yielded default(RowKey) instead of null.

Verification

  • 53/53 doc apps build Release/x64 clean.
  • Reactor.Tests: 13,334 passed, 0 failed. Reactor.DocPipeline.Tests: 379/379.
  • docs/guide is idempotent — 190 generated files byte-identical on recompile.
  • Every fix that could be mutation-tested was, against real historical defects rather than synthetic fixtures. The phantom lint's own author noted that had the fixture been written by hand as Text("Hello") instead of copied from the real pre-fix line — which uses escaped quotes inside a string literal — it would have passed and shipped blind.

Not verified locally: the full Release solution build cannot complete on an offline machine — three src/vs-reactor/** projects fail restore with NU1301. Confirmed environmental by running the identical command on unmodified main, which fails identically; this branch touches only src/vs-reactor/README.md. CI has network and will run that gate properly.

Known issues, deliberately not fixed

  • The reference generator skips #ctor, so positional records lose their <param> docs entirely. MutationOptions.md has no Parameters section because OnError/OnSuccess documentation lives on the skipped constructor. Separate bug, larger than this change.
  • REACTOR_DOC_REFGEN_001 rises 54 → 64, and that is correct. −12 from the cref fix, +22 newly surfaced: 18 previously-dropped overloads are now rendered and their prose cites out-of-category types, which degrade to inline code by design. Those crefs were "absent" only because the prose containing them was being deleted. A per-cref diff confirms no in-category cref regressed.
  • The three Win2D <!-- ref: --> markers are removed, not bypassed. If Advanced element types become routable later, that capability would ship with nothing pointing at it; restoring the cross-references needs a deliberate edit to win2d-canvas.md.dt.
  • 21 of 71 templates declare an app: directory that doesn't exist. Benign — 17 are 100% source: snippets, which need no doc app — but the metadata is inaccurate.

Reviewing this

The nine slices are independent and each merge commit is one slice, so reviewing per-merge works well. Two files have two authors (index.md.dt, text-and-media.md.dt) and both contributions were verified to survive.

One caveat worth knowing: docs/guide/** is generated. docs/guide/win2d-canvas.md conflicted at integration and was resolved by regenerating, not by picking hunks — merging generated output textually can produce a file matching no valid regeneration. UseFocusTrap.md also needed regeneration to pick up a compound effect no single branch produced: a doc-comment fix on one branch only becomes visible once the generator on another branch renders <example> blocks it previously dropped.

z and others added 30 commits August 24, 2026 15:05
Staleness audit of the recipes folder plus dev-tooling, vs-extension,
testing, performance, and diagnostics. Every bare (uncompiled) csharp
block in scope was checked symbol-by-symbol against skills/reactor.api.txt
and src/**; broken examples were promoted to compiled snippets where
self-contained, corrected in place otherwise.

Phantom APIs removed (named in docs, absent from the codebase):
  - testing: ElementDescription.Of, A11yDiagnostic.Rule, "ButtonName",
    UseAsync, and a NamedButton fixture that was referenced but never defined
  - dev-tooling: Spacer(), Application.Current.Reload()
  - diagnostics: HResults.E_FAIL
  - performance: ResetDebugCounters(), ReconcileCore()

CLI surface corrected against `mur --help` and Program.cs:
  - `mur devtools serve` does not exist; `mur devtools` is the launcher
    + MCP host. Rewrote the MCP section, which described a docs-inventory
    server rather than the live-app inspection surface it actually is.
  - added doctor / upgrade / figma / docs check-tier and the top-level
    --create/--skill/--api options; fixed --tier and --skip-screenshots
  - removed dead link to docs/contributing/devtools.md (file does not exist)

Internal-vs-public boundaries clarified: UseDevtools() has no Component
forwarder (the old example could not compile), and HotReloadService,
DiagnosticLog, LogCategory, HResults and ReactorEventSource are internal,
so app-facing examples no longer name them.

Recipes: 9 of 10 were already idiomatic. recipe-login hand-rolled
submitting/error UseState pairs that UseMutation now owns — rewritten to
UseMutation with IsPending/Error. The recipes index still advertised
paginated-list, multi-step-form, command-palette and drag-reorder as
"Phase 2.5 / coming soon"; all four shipped, so they are now linked.

Also: added the headless-test COMException constraint and the two-place
fixture registration gotcha to testing; added the HotReload keyword
(0x2000) to the diagnostics keyword table; corrected the supported
Visual Studio versions on vs-extension.

Verified: all three touched doc apps build clean in Release/x64,
`mur docs compile` regenerates with no new warnings, tier-lint reports
0 errors, and every relative link in scope resolves. The compiled-snippet
loop was mutation-checked (breaking a promoted symbol reddens the build).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Audit of the onboarding & setup slice (index, getting-started,
thinking-in-reactor, xaml-developers, reactor-vs-xaml, cheat-sheet,
packaging). Bare (uncompiled) code blocks and prose API references had
drifted from the real public surface.

index:
- "Minimal Project Setup" told external readers to consume Reactor via
  <ProjectReference Include="..\Reactor\Reactor.csproj" />, a path that
  only exists inside this repo, so the documented first run was broken.
  Now uses <PackageReference Include="Microsoft.UI.Reactor"
  Version="{{reactorVersion}}" /> via the pipeline token.
- Dropped the explicit Microsoft.WindowsAppSDK 2.1.* reference: the
  package already brings Microsoft.WindowsAppSDK.WinUI 2.1.0
  transitively (verified against the published nuspec).
- Added the RuntimeIdentifier auto-resolve condition. Without it a bare
  `dotnet build` / `dotnet run` - which the very next line tells the
  reader to run - fails with "WindowsAppSDKSelfContained requires a
  supported Windows architecture". <Platforms> alone does not fix it.
- ai:lock markers preserved.

getting-started:
- Text(...) is not a factory; the compiled snippet on the same page
  already used TextBlock(...). Fixed 3 prose references.
- "Running with devtools" was wrong on three counts: the dev menu is
  not gated by #if DEBUG, there is no Ctrl+Shift+D shortcut, and a
  plain `dotnet run -c Debug` starts with no dev menu because the
  default launch profile passes no args. Rewritten around the real
  model: Reactor.DevtoolsSupport + `--devtools app`.

reactor-vs-xaml:
- Added a doc app so its examples are compiled by CI; promoted the two
  self-contained bare blocks to snippets (datatemplate-foreach,
  style-as-composition).
- TextElement does not exist -> TextBlockElement (field Content, not Text).
- UsePersistedState -> UsePersisted.
- UseNamedStyle / .Named() do not exist -> named-style fluents
  (.AccentButton/.SubtleButton/.TextLink).
- .WithAnimation() is not an element modifier -> .Animate()/.Transition();
  AnimationScope.WithAnimation is the ambient scope.
- Fixed dead link [Observable<T>](#) and the brittle "35 token" count.

cheat-sheet:
- ReactorApp.Run has no `preview` parameter -> fullScreen?/configure?.
- ReactorWindow.Open<TComp>() does not exist -> ReactorApp.OpenWindow.
- .Bind(button) does not exist -> Button(command) / .Command(cmd).

packaging:
- Template stopped pinning Microsoft.WindowsAppSDK in #1096; it is now
  added by a dotnet-new post-action at latest stable. Two claims updated.
- The default launch profile passes no arguments; named the separate
  Devtools profile instead.

Element.cs: the element-record snippet's XML doc comment carried the
same Text("hi") mistake and is rendered verbatim into five guide pages;
corrected at the source, which is why four out-of-scope generated pages
carry the identical one-line change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Audit of the core-controls guide slice (controls, forms, collections,
text-and-media, status-and-info) against today's source.

Catalog completeness (controls.md.dt)
- The page billed itself as a catalog of "every Reactor control" but
  listed 46 of the 95 element families registered by
  ReactorApp.RegisterAllBuiltIns(), and was missing three whole
  categories. Added Layout & Containers, Navigation, and Shapes & Icons
  (with snippet-backed thumbnail groups + doc-manifest captures), and
  completed the existing tables: 46 -> 99 control rows.
- `MarkdownTextBlock` was a phantom name in two places; the factory is
  `Markdown(...)` and it lives in Microsoft.UI.Reactor.Advanced.
- Added ControlCatalogCompletenessTests so the claim is checked rather
  than asserted: deleting a single catalog row now reddens three tests.

Phantom APIs corrected
- forms: `TextBox(initial:)` (no such parameter), `DisabledFocusable()`
  -> `.IsDisabledFocusable()`, `.KeyDown` -> `.OnKeyDown`.
- text-and-media: `Selectable()` -> `.IsTextSelectionEnabled()`,
  `RichEditBox(string text = "")` -> `Optional<string>`.
- collections: `theme.IsDark` -> `UseIsDarkTheme()`, and a cache example
  that used `Dictionary<int, Element>` against a string-keyed selector.
- status-and-info: the `ProgressBar(...)` factory does not exist at all;
  the page described it as merely deprecated.

Wrong-overload bug
- text-and-media's "Do" example for Markdown in a scrolling list used
  `Memo(ctx => ..., deps)`, which only skips work on a parent re-render
  and does nothing on a scroll recycle. Replaced with the keyed
  `Memo(key, factory)` cross-recycle cache and explained the distinction.

Dead links
- Two links pointed at github.com/microsoft/reactor1 (repo renamed).

Snippet promotion
- 11 uncompiled blocks moved into the doc apps behind snippet markers,
  so CI compiles them from now on. Across the five topics: 25 bare
  blocks -> 14, compiled snippets 56 -> 70. The 14 remaining are factory
  signature listings and "Don't" anti-patterns that cannot compile
  standalone; every symbol they name was verified against source.

Verification: all five doc apps build Release/x64; `mur docs compile`
regenerates with no collateral diff; tier lint clean on all five;
Reactor.Tests 13308 passed / 0 failed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cross-session handoff from the "Guide: recipes and tooling" audit; both
lines live in index.md.dt, which that session did not own.

- VS Extension: src/vs-reactor/README.md line 15 requires Visual Studio
  2022 (17.8+) *or* 2026 (18.x). The listing named 2022 only. The
  "rough experimental" framing is unchanged - still verbatim accurate
  against that README.
- Testing: "snapshot tests" described an ElementDescription.Of golden-file
  harness that does not exist. Independently reconfirmed here with a
  positive-controlled scan of src/ + tests/ + tools/ (1887 .cs files):
  ElementDescription = 0 hits while Assert.IsType = 651 and
  "abstract record Element" = 35 in the same pass, so the zero is a real
  absence and not a broken probe. Now "structural assertions".

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…locks to compiled snippets

Audited the 7 core-framework templates (58 uncompiled `csharp` blocks). Bare
blocks are never compiled by CI, so they had drifted onto APIs that do not
exist. Eight were invented outright; all are now corrected, and 39 of the 58
blocks are promoted to snippet-backed code that CI compiles from the doc apps.

Phantom APIs removed (each verified against src/ with a positive control, and
three caught by the compiler once promoted):

- `ctx.UseTheme()` -> `ctx.UseColorScheme()` (advanced). Never implemented;
  existed only in design specs. The same line had been copied into
  `src/Reactor/Hooks/UseMemoCells.cs`, which feeds the generated reference
  page `docs/guide/reference/hooks/UseMemoCells.md` - fixed at the source.
- `RenderContext.Current` -> `this RenderContext ctx` extension (hooks). No
  static `Current` exists anywhere in src/.
- `Text(...)` as an element factory -> `TextBlock(...)` (commanding, hooks,
  components). Proven by compiler: CS0103. Only CellRenderers.Text,
  DragData.Text and Editors.Text exist, none on Factories.
- `Theme.Stroke` -> `Theme.CardStroke`, `Theme.Error` -> `Theme.SystemCritical`
  (components). `static class Theme` has neither member.
- `intl.Get(...)` -> `intl.Message(new MessageKey(ns, key))` (commanding).
- `Component<T>().Set(...)` (advanced) - ComponentElement has no `.Set`;
  rewritten onto a control-backed element, which is the real hazard shape.
- `.WithKey(item)` on an unconstrained generic (components) - `WithKey`
  requires `string` or `TKey : IReactorKeyed`; the render-props example now
  takes a key selector.

Bare blocks 58 -> 19: 6 are `ai:lock` (only single-symbol `Text` -> `TextBlock`
corrections applied, lock structure preserved) and 13 are genuine
fragments/anti-examples that cannot compile standalone; every symbol they name
was verified to exist.

Verification: all 6 modified doc apps build clean in Release/x64; docs tier
lint reports 0 errors across all 7 topics; regenerated guide diff is confined
to the audited topics. The detection loop was validated non-vacuously by
injecting a bad symbol and confirming the build reddens before restoring.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Line 3 advertised Visual Studio 2022 only while line 15 (the requirements
line) correctly stated 2022 (17.8+) or 2026 (18.x). The guide page
vs-extension.md reconciles against this README, so the inconsistency was
propagating ambiguity into the docset.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 11263efd-3cf9-4dc2-b8ed-6b6380193e9a
Audit of the rich-controls slice (dialogs-and-flyouts, data-system,
charting, win2d-canvas, docking). Promotes 11 uncompiled blocks to
CI-enforced snippets and corrects the symbols in the rest.

Reader-visible breakage
- win2d-canvas: the three `<!-- ref:Win2D*Canvas -->` markers never
  resolved (reference generation is gated to the `hooks` category and
  reads core Reactor.xml, so Advanced element types can never route).
  The unresolved markers leaked into docs/guide as raw HTML comments,
  rendering as three empty gaps. Replaced with same-page section links.
  Clears all three REACTOR_DOC_REFMARKER_001 warnings.

Wrong API that would not compile
- charting: `using static Microsoft.UI.Reactor.Charting.D3.D3Charts` ->
  `...Charting.D3Charts`; `D3Text`/`D3TextRight`/`D3TextCenter` do not
  exist (real names `Text`/`TextRight`/`TextCenter`); the scale types are
  in `Charting.D3`, not members of `D3Charts`.
- charting: `DashStyle.Dot` / `DashStyle.Dash` do not exist.
- charting: `.Margin(t, r, b, l)` documented CSS order; the real
  signature is `Margin(left, top, right, bottom)`, defaults 40/20/20/30.
- charting D3 sample: `.Domain()`/`.Range()` are properties not methods,
  `scale(v)` is not invocable (use `.Map(v)`), `D3Axes` needs six args
  and returns `Element[]` (must be spread), and `.Stroke`/`.Fill` on D3
  shapes take a `Brush`, not a string.
- charting live-feed sample: `await` inside a non-async `UseEffect`
  lambda, plus an undeclared token.
- charting type-switch sample: `ComboBox` reports an index, not a string.
- dialogs-and-flyouts: `.WithFocusTrap(trap)` does not exist (real:
  `.FocusTrap(trap)`) and `UseFocusTrap()` requires an `isActive` flag.
- dialogs-and-flyouts: `UseElementRef`/`UseFocusTrap` are extension
  methods on Component, so a bare call does not resolve.
- data-system: `AutoColumns<T>` takes `overrides`, not `columnOverrides`
  (that name belongs to the registry-based `DataGrid` overload);
  `FilterOperator` has 13 members, not 10; `GetPageAsync` takes the
  cancellation token as a separate parameter; lifted selection state must
  be typed `IReadOnlySet<RowKey>` for the setter to bind.

Consistency
- charting, data-system, docking, and text-and-media told readers to add
  an unversioned `<PackageReference Include="Microsoft.UI.Reactor.Advanced" />`,
  which does not restore without central package management. All four now
  use `Version="{{reactorVersion}}"`, matching getting-started.

Verified: all five doc apps build clean in Release/x64; tier lint reports
0 errors; every relative link and intra-page anchor in the slice resolves;
docs/guide regenerates deterministically with no collateral churn. The
win2d examples were checked against REACTOR_WIN2D_001 — the shared-device
example opts in, and the canvas-own-device example is correctly exempt.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Audit of the App architecture / interop slice of the user guide. Every bare
(uncompiled) ```csharp block in scope was checked against the real API and
either promoted to a CI-compiled snippet= block or corrected symbol by symbol.
In-scope templates go from 30 bare / 39 compiled to 6 bare / 82 compiled;
windows.md.dt goes from 16 bare / 3 compiled to 0 bare / 20 compiled.

Defects found and fixed (each was uncompilable or factually wrong):

- migration/050-optional-t: told authors to write `Optional.Of(value)`.
  There is no non-generic `Optional` helper — every real call site in the
  repo uses `Optional<T>.Of(...)`. Nine occurrences corrected; a note now
  explains the generic spelling. Verified: injecting the old spelling into
  a compiled snippet yields CS0305.
- wpf-interop: "mount a ReactorHostControl and assign it a ComponentType".
  ReactorHostControl has no ComponentType — that property belongs to the
  WinForms XamlIslandControl. Replaced with the real ComponentFactory /
  Mount(...) shape, now compiled.
- winforms-interop: the content-factory snippet's key lines were commented
  out, hiding a call to `host.SetComponent<T>()`, which does not exist.
  Rewritten as live code so CI enforces it.
- accessibility: `var label = UseElementRef<FrameworkElement>();` does not
  compile inside a component — it is an extension on Component/RenderContext
  and needs `this.`. Corrected and promoted.
- windowing-advanced: the SetWindowRgn recipe wrapped the HRGN in `using` and
  passed DangerousGetHandle(). SetWindowRgn transfers ownership to the system
  on success, so that frees GDI memory the compositor still reads. Rewritten
  to delete the region only when the call fails.
- windows: "TaskbarItem groups all taskbar features" was wrong — JumpList is
  a taskbar surface that is not on TaskbarItem. JumpList / JumpListItem /
  LaunchKind / ReactorApp.Run(Action<ReactorAppContext>) had zero guide
  coverage; added a Jump list section with a compiled snippet and the
  argument-validation caveats.
- persistence: "will eventually trigger an analyzer warning" — the analyzer
  shipped. Now names REACTOR_PERSIST_001 and its code fix.
- windowing-advanced: pointed at a `samples/apps/hud-overlay/` that was never
  added; retargeted at the shipping window-styles and tool-palette samples.

Structure:

- docs/guide/migrations/054-windowing-evolution.md was an orphan: it lived in
  `migrations/` (plural) with no .md.dt template, so `mur docs compile` could
  neither generate nor keep it in sync. Given a template under `migration/`
  (singular, matching 050), its examples promoted to compiled snippets, and
  the duplicate directory removed. Both migration notes are now linked from
  windows.md.
- New doc apps for windowing-advanced and wpf-interop, which previously
  declared an `app:` that did not exist and had never compiled a line.
- Advanced Windowing was missing from the index; added under App architecture.

Verification: all touched doc apps build clean in Release/x64; `mur docs
compile` is idempotent (zero drift on re-run); all 71 in-scope snippet
references resolve; zero broken relative links. The build loop and the link
and snippet checkers were each validated with a deliberate mutation or
positive control before their results were trusted.

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

Audit of the 16 Under-the-hood guide templates for drift against the
current runtime. Templates are the source of truth; docs/guide/** is
regenerated via `mur docs compile`.

Structural fix (preferred): promote hand-written code to
`snippet="source:<path>#<region>"` so the doc is pinned to real source
and cannot drift again.

- reconciliation, architecture-overview: the hand-written `Reconcile`
  body matched no live code (it showed an `Update`/`UnmountAndReturnNull`
  switch). Both now inline `Reconciler.cs#reconciler-entry`.
- element-pool: the "why interactive controls are safe" section embedded
  `RenderContext.cs#ui-thread-invariant`, an unrelated snippet. Now
  inlines a new `Element.cs#click-trampoline` region (markers only).

Corrected mechanisms (symbols existed; the explanation did not match):

- Per-element state is `ReactorAttached.StateProperty` -> `ReactorState`,
  not `sender.Tag`, and `GetElementTag` is not generic. Tagging is
  gated by `NeedsTag`. (reconciliation, element-pool,
  control-reconciler-protocol)
- `UseState` setter identity is stable because the delegate is cached on
  the hook cell (`ValueHookState<T>.Setter`), not because a captured
  index never changes; the closure holds the cell, not the index.
  (hooks-internals, reactivity-model, effects-scheduling)
- `Component<TProps>.ShouldUpdate` already compares props structurally;
  "props always re-render, equality is on the roadmap" was wrong.
  (reactivity-model)
- Echo suppression is the documented hybrid: value-diff arm
  (`PendingEchoMatch` / `ArmExpectedEcho`) plus the retained
  `ChangeEchoSuppressor` counter fallback and the non-consuming
  `EchoSuppressScopeDepth` scope. (threading-and-dispatch,
  control-reconciler-protocol)
- Custom containers register through `ControlRegistry` /
  `RegisterHandler`; `RegisterType` takes no `ReconcileChildren`
  callback. (reconciliation)
- MCP tool dispatch runs inline on the transport thread; the UI hop is
  opt-in per handler via `server.OnDispatcher<T>`. The tool registry,
  descriptor and `SchemaNode` are internal, so custom tool registration
  is a framework-contributor path, not a host-app one. (devtools-internals)
- `Reactor.Analyzers` targets netstandard2.0, cannot reference
  `Reactor.Cli`, and every id needs an `AnalyzerReleases.Unshipped.md`
  row (RS2008) - previously it named Shipped.md as optional.
  `REACTOR_DOC_001` ships from `Reactor.Analyzers.Internal`, and
  analyzer tests live in `tests/Reactor.Tests/AnalyzerTests/`.
  (analyzer-architecture)

Corrected symbols in bare (uncompiled) blocks:

- No `Text(...)` element factory and no `TextElement` record exist; the
  DSL factory is `TextBlock(...)` -> `TextBlockElement`. The three
  indexed `Text(` entries are a DataGrid cell renderer, a DataGrid
  editor and `DragData.Text`, none of which is an element factory.
  (architecture-overview, hooks-internals, modifier-system,
  animation-pipeline, source-mapping)
- The focus-trap modal used `Layer(...)`, `Dialog(...)` and a
  parameterless `.OnMount(...)`; none exist. Rewritten on the controlled
  `ContentDialog(...)` element with `IsOpen`, matching
  REACTOR_DIALOG_001. (focus-and-input-internals)
- `Button`/`TextBox`/`ToggleSwitch` are in `ElementPool.PoolableTypes`;
  architecture-overview claimed interactive controls are not pooled.
- 14 ETW keywords, not 7 (spec 044 + 049 added seven).
  (perf-instrumentation)

Verification: `mur docs compile` is idempotent and touches only these
topics; all relative links resolve; `Reactor.DocPipeline.Tests` 331/331;
tier lint reports 0 errors (the one `winui-ref` warning is pre-existing
and identical on untouched topics). The promoted snippets were
mutation-checked - breaking a symbol inside `click-trampoline` fails the
`src/Reactor` build at that line, then restored.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reactor has no core `Text(...)` element factory. All four `Text(` members
in src/ belong to other types — `CellRenderers.Text` and `Editors.Text`
(both `Func<object, Element>`, for DataGrid), `DragData.Text`, and
`D3Charts.Text(x, y, text, …)` (positional, canvas coordinates). None of
them is `Text(string) -> Element`. Verified with a positive control:
`public static TextBlockElement TextBlock(string content)` (Dsl.cs:60) is
found by the same probe shape, and `Factories` declares no `Text` member
across all nine of its partial files.

- docking/App.cs: the fake editor panes in the `two-pane` and `tab-group`
  snippets rendered `Render() => Text("Hello")` as example source inside
  string literals, so the compiler could not see them. Corrected to
  `TextBlock(...)`. This is the uncompiled-rot class hiding inside a
  compiled file.

- charting.md.dt: prose introduced earlier in this audit claimed `Text`
  "collides with the core `Text(...)` factory". That was wrong, and wrong
  in the same direction as the bug above — the three bare `Text(...)`
  lines in skills/reactor.api.txt sit under `## Public types`, where
  entries are grouped beneath their declaring type, not under
  `## Factories`. Replaced with the verifiable distinction: `D3Charts.Text`
  is positional and unrelated to `TextBlock(...)`, and no core `Text(...)`
  element factory exists. The page now inoculates against the phantom
  instead of propagating it.

Verified: docking doc app builds clean Release/x64; docs recompile with no
REFMARKER warnings; no bare `Text(` remains in the six generated pages in
this slice except the charting prose that names it to warn against it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The guide audit found Text(...) used as an element factory across four
templates. It also reached product source: 12 XML doc comments cite
Text("...") in element position, plus three phantom modifiers.

None of these APIs exist:

  Text(...) as an element factory. The three real Text(...) overloads
    return Func<object,Element> (DataGrid cell renderer), DragData, and
    a DataGrid editor builder -- none returns an Element. The correct
    factory is TextBlock(...). Element.cs's own implicit string operator
    calls Factories.TextBlock(text) while its doc comment two lines
    above said Text("Hello").

  WithOpacityTransition()   -> OpacityTransition()
  WithThemeTransitions(...) -> WithTransitions(...)
  WithImplicitTransition    -> OpacityTransition

These ship in Reactor.xml, so they surface as IntelliSense tooltips for
every consumer of the NuGet package. They do not affect docs/guide: a
full docs compile after this change produces zero diff there, because
the generated reference pages do not render these example blocks.

Element.cs:33 is deliberately left alone -- it is already fixed on
azchohfi-potential-sniffle (onboarding slice); the two combine to zero.

Verified: src/Reactor builds Release/x64 with 0 warnings and 0 errors;
docs compile succeeds with no docs/guide diff; a recursive 697-file
re-scan finds exactly 1 remaining phantom, the one intentionally
skipped. An earlier scan of this same tree reported "none" -- that was a
broken probe, since PowerShell's src\**\*.cs globs only two levels and
never reached src\Reactor\Core\.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 11263efd-3cf9-4dc2-b8ed-6b6380193e9a
TeachingTipPage's SourceCode string showed:

    var target = UseElementRef<FrameworkElement>();

UseElementRef is an extension method on Component, so that line does not
compile as written. The page's own working code on line 18 uses
this.UseElementRef<FrameworkElement>() -- the file contradicted its own
displayed sample.

This is the same rot class the guide audit is chasing: example code
living inside a string literal, invisible to the compiler even though
the surrounding file is compiled and shipped.

Scope checked: a scan of all 336 sample .cs files found this as the only
receiverless call of UseElementRef / UseFocusTrap / UseValidationContext
/ UseElementFocus.

Verified: ReactorGallery builds clean; tools/Reactor.SearchIndex
regenerates reactor-search-index.json byte-identical (93 controls, 0
skipped), so the CI byte-compare gate is unaffected -- this snippet is
not the page's first sample.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 11263efd-3cf9-4dc2-b8ed-6b6380193e9a
…f-truth

`Optional.Of(...)` does not exist. `Of` is declared only on the generic
struct (`Optional.cs:46`, `public static Optional<T> Of(T value)`), and
there is no non-generic `Optional` type anywhere in src/ - so the bare
form cannot bind. Verified by mutation: injecting `Optional.Of("")` into
`src/Reactor/Core/Optional.cs` fails the build with

  error CS0305: Using the generic type 'Optional<T>' requires 1 type arguments

The only valid spelling is `Optional<Brush>.Of(null)`.

Two fixes:

- `extending-reactor-controls.md.dt:350` - the occurrence in my slice.
  Routed from the app-architecture session, which found the same defect
  x9 in `migration/050-optional-t.md.dt`. Note it sits in an inline code
  span inside an `ai:caveat` block, not a fenced block, so no amount of
  snippet promotion would ever have caught it. Symbol corrected only;
  wording and intent preserved.

- `src/Reactor/Core/Optional.cs:57` - the XML doc on the implicit
  conversion operator carries the identical wrong spelling, and it is
  almost certainly where the four affected templates copied it from.
  `GenerateDocumentationFile=true`, so this text ships in the public
  reference. Fixing the origin stops it re-propagating into the next
  doc pass. Comment-only; `src/Reactor` builds clean.

Left alone deliberately: 7 further `Optional.Of(` occurrences in src/
are all `//` implementation comments or an analyzer message string, none
compiled and none public-API reference text.

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

`Optional.Of(...)` does not exist. `Optional<T>` is a generic struct whose
`Of` is a static member on the struct itself (src/Reactor/Core/Optional.cs:46),
so the only valid spelling is `Optional<double>.Of(5.0)`. There is no
non-generic `Optional` helper anywhere in src/ (verified exhaustively across
all 732 .cs files, with a positive control).

Fixed 4 occurrences in advanced.md.dt prose:
- the authority paragraph (`Optional.Of(value)`)
- the snap-back flow sentence (`Optional.Of(5.0)`)
- the ClearValue paragraph (`Optional.Of<Brush?>(brush)` - type argument was
  also on the wrong side of the dot)
- the null-vs-Unset sentence (`Optional.Of(null)`)

Added an explicit note naming both wrong spellings as counterexamples, so the
correct placement of the type argument is stated rather than implied.

These were invisible to the previous audit because they live in inline code
spans in prose, not in fenced blocks - nothing compiles a Markdown inline span.
Swept all 7 core-framework templates for the same class of defect: 272
prose-embedded API spans (164 unique), checked by identifier existence and then
by shape for `Type.Member(...)` and `.Modifier(...)` forms. Everything else
verified real, including `ReactorApp.RegisterAllBuiltIns`,
`ControlRegistry.Register`/`RegisterDecorator`, `IsDisabledFocusable`,
`ToolTipPlacement`/`ToolTipPlacementTarget` and `WithToolTip`.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Audit of the 41 uncompiled `csharp` blocks in layout, flex-layout,
styling, animation, input-and-gestures, and theming-tokens. Bare blocks
are never compiled by CI, so they had drifted from the current API.

Promoted 10 self-contained blocks into their doc apps as `snippet=`
blocks so CI compiles them from now on (56 -> 66 compiled blocks;
41 -> 31 bare). input-and-gestures went from 5 compiled / 15 bare to
10 / 10.

Symbols that do not exist:
- `FlexPanel(...)` as a DSL factory. `FlexPanel` is the WinUI control;
  the factories are `Flex` / `FlexRow` / `FlexColumn`.
- `.FlexDirection(...)` on any element. Direction is the `FlexElement`
  init property (`with { Direction = ... }`) or the
  `Flex(FlexDirection, ...)` factory overload. (`.Direction(...)` does
  exist, but only as `ExpanderElement.Direction(ExpandDirection)` --
  a different element and a different enum.)
- `Text("...")` as an element factory -> `TextBlock(...)`. The four real
  `Text(...)` methods are the grid cell renderer/editor builders,
  `DragData.Text(string)`, and `D3Charts.Text(x, y, text, ...)`; none
  takes a lone string and returns an Element.
- `VStackElement` -> `StackElement` (what `VStack`/`HStack` return).
- `Focusable(...)` -> `.IsTabStop(true)` (ElementExtensions.cs:2764).
- `.On<Event>Handled(...)` "handled-too" overload -- no public extension
  in src is named `*Handled*`; documented the
  `AddHandler(..., handledEventsToo: true)` escape hatch instead.
- `A11Y_KEYBOARD_001` -- AccessibilityScanner only emits A11Y_001..008 and
  has no keyboard rule; REACTOR_A11Y_004 is the only guard.
- `Command.AccessKeyModifiers` -> `Command.Accelerator`.
- `.WithImplicitTransition(...)` -> `.OpacityTransition()` and friends.
  Its only occurrence in src is a stale doc comment.
- `.WithModifier(...)` (0 hits anywhere) and a headless `Mount(...)`
  returning a chainable tree. `Mount` does exist -- as reconciler, host
  and IElementHandler API -- but every overload either returns `void` or
  a `UIElement`, which a headless xUnit test cannot construct. Rewrote
  to the real `AccessibilityScanner.Scan(tree)` pattern used by
  tests/Reactor.Tests.
- `Severity.Success` / `.Critical` -> `InfoBarSeverity`. The single
  public `Severity` enum is Info/Warning/Error (validation).

Probe method: every verdict above was re-confirmed with a bare-name
search (never `Name\(`), because the API index renders generic members
as `T.Name<T>(...)` -- 184 of the 643 lines in `## Modifiers` use that
shape and are invisible to a paren-anchored grep. Each negative is
paired with a positive control sharing the same shape: `WithBorder`,
`IsTabStop` and `OpacityTransition` are generic extension modifiers and
all return non-zero on the identical probe.

That method also corrected one of my own findings: `Border("string")` is
*valid* -- `Element` defines an implicit `string` -> `TextBlockElement`
conversion (Element.cs:342) -- so those examples are left unchanged.

Prose sweep: fenced-block promotion cannot reach API names in headings
and inline code spans, so those were checked separately. Extracted all
375 prose-embedded API references outside fences across the six
templates (131 distinct identifiers) and verified each. One real defect:
the "Forgetting `Focusable(true)` on a clickable Border" heading named a
modifier that does not exist and contradicted its own body, which
already said `.IsTabStop(true)`. Everything else resolved: locals
(`setOffset`, `updateCount`), WinUI APIs (`AddHandler`, `Measure`), CSS
notation (`min(N, min-content)`), tuple-element names (`RequestFocus`
from `UseElementFocus`), or deliberate negatives.

Token catalog: the "full catalog" was missing `Theme.SystemSolidAttention`
and counted 35 instead of 36. The swatch-grid doc app claimed to render
"every named Theme.* token" while omitting 6 Signal tokens; both fixed.

Also documented `TryGetSafeLocalFiles` and the REACTOR_INPUT_002 analyzer,
which had shipped but were absent from the drag-and-drop section; the
`ListView<T> : IReactorKeyed` constraint that promotion surfaced; and the
`.OnDoubleTap(Action)` / `.OnDoubleTap(Action<Point>)` convenience
overloads, which the reference table omitted while the compiled snippet
used them.

All 6 doc apps build Release/x64; tier lint 0 errors; 0 broken links.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…, topic->app binding

Four defects in `mur docs` generation, each of which failed silently or
produced a false signal.

1. Reference pages dropped all but one overload (REFGEN_002, 18 sites).
   Pages are keyed by short name, so every overload of a method routed to
   the same file and the first writer won; the rest never appeared
   anywhere in the docset. `UseEffect` documented 1 of its 8 overloads --
   the `Func<Action>` cleanup form, the only way to register a
   self-tearing-down effect, was absent entirely.

   A page now represents every member routed to it, one `##` section each
   (signature, summary, parameters, returns, discussion), ordered from the
   cref rather than from XML declaration order so output is stable. One
   page per short name is preserved, so inbound links and `<!-- ref:Name -->`
   markers keep working. Signatures come from a new `CrefSignature` that
   renders a cref as readable C# and slugs it to a GitHub-compatible
   anchor. REFGEN_002 is retained but narrowed to its real meaning: a page
   name claimed by two unrelated declaring scopes. Those members are now
   rendered too, and still reported.

2. Member crefs failed to resolve even when the declaring type had a page
   (REFGEN_001, 12 sites). A positional record's properties are
   compiler-generated and never appear in the XML doc file, so
   `MutationOptions<,>.OnSuccess` and friends had nothing to resolve
   against. `CrefResolver` now falls back to the declaring type's page,
   keyed on the full type cref so a same-named type in another namespace
   cannot capture it.

3. `ExpandMarkers` emitted an unresolvable `<!-- ref:X -->` verbatim into
   published Markdown. The marker is an HTML comment, so the reader saw an
   empty gap mid-sentence with nothing indicating a cross-reference had
   gone missing -- three of them in win2d-canvas.md. It now degrades to
   inline code, matching what `CrefResolver.Rewrite` already does for an
   unresolvable cref, and still reports REFMARKER_001.

4. `docs check-tier --topic` reported fabricated TIER_003 errors. App
   discovery filtered by directory name against the topic id, but the
   binding is the template's `app:` field, and the two differ for 13
   shipping topics (`async-resources` -> `async-resources-cookbook`, every
   `recipes/<x>` -> `recipe-<x>`). Those topics discovered no app at all,
   so every `snippet=` failed to resolve and the documented fast inner
   loop fired on correct pages. Discovery is now driven by `app:` plus the
   app ids named in `snippet=` refs; `compile --topic` had the same bug.

Regenerated docs/guide/reference/** accordingly; two consecutive compiles
produce byte-identical output across all 190 generated pages.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The "Minimal Project Setup" block on the docset landing page is the first
thing a new user copies, and nothing validated it. It is a fenced xml
block, so no compiler sees it, and it sits inside ai:lock markers, which
authoring passes skip by design. It shipped telling readers to consume
Reactor via <ProjectReference Include="..\Reactor\Reactor.csproj" /> -- a
path that does not exist outside this repo -- so the documented first-run
was broken for every external reader.

Adds MinimalCsprojDocTests, which extracts the block from index.md.dt and
asserts it: parses as XML; references the Microsoft.UI.Reactor package and
carries no ProjectReference; pins the {{reactorVersion}} token rather than
a literal that goes stale at the next release; keeps the RuntimeIdentifier
auto-resolve line; declares the required WinUI properties; agrees with the
scaffolded dotnet-new template on the package id; and produces a generated
index.md with a real version and no leaked token.

Non-vacuous by natural mutation, not a synthetic one. Run against the
pre-fix template the suite reports 4 failed / 5 passed, and the failure
names the historical defect exactly:

  'Minimal Project Setup' must not use ProjectReference -- that path does
  not exist for a reader outside this repo. Found: ..\Reactor\Reactor.csproj

With the fix merged: 9/9 pass, and the full project is 340/340.

The 5 that pass in both states (extraction, well-formedness, required
properties, scaffold cross-check) are what show the harness itself works,
so the 4 failures are a measurement rather than a broken probe.

A build fixture was considered and rejected: restoring Microsoft.UI.Reactor
at the version in Directory.Build.props couples CI to a package that is not
yet published at release time. This runs offline with no feed.

RuntimeIdentifier is asserted because it is load-bearing and non-obvious --
adding <Platforms> instead does not fix the architecture error; that was
measured against a real build before the fix shipped.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 11263efd-3cf9-4dc2-b8ed-6b6380193e9a
AGENTS.md is the file that orients every future contributor and agent
session, so a stale claim here misdirects work before it starts. Two were
wrong; verified against source with positive controls.

1. "Register it in RegisterV1BuiltInHandlers" -- that bootstrap no longer
   exists. No such method is defined anywhere in src/ or tests/; the only
   surviving mention is a stale comment. Reconciler.cs:264 records why:
   "Spec 048 SS3.4 -- no more bootstrap. Built-in handlers register themselves
   lazily on first factory call (per-control Reg<>/RegDecorator<> cctor latch
   in Dsl.cs), or callers register explicitly via ControlRegistry.Register<,>."
   Also notes the spec-058 [GenerateReactorWrapper] static-cctor path, and
   that `new MyElement(...)` alone registers nothing (Reconciler.Mount.cs:268).

2. The Element pooling paragraph was wrong twice over. PoolableWireFlags has
   zero hits across src/ and tests/. And the CWT in ElementPool is not
   event-wiring bookkeeping: ElementPool.cs:20 declares
   ConditionalWeakTable<UIElement, object> _compositorTainted, tracking
   elements that had GetElementVisual() called so they are EXCLUDED from
   pooling (they permanently lose the XAML implicit-transition APIs).
   ElementPool.cs:10 also states V1 pools only non-interactive controls,
   so there is no double-subscribe problem to solve.

Probe note: the zeros above are measurements, not silent misses. The same
scan over the same 1847-file set found ConditionalWeakTable< in 30 files,
and re-verified 14 other AGENTS.md symbols (PendingEchoMatch,
ArmExpectedEcho, ShouldSuppressEcho, ChangeEchoSuppressor, WriteSuppressed,
ReactorAttached, StateProperty, ModifierEventHandlerState,
ControlEventStateBox, ReactorState, ControlDescriptor, IElementHandler,
RuleRegistry, valueDiffEcho) as present. The rest of the file checks out.

Found by the under-the-hood guide-audit session; both claims had already
been propagated into this audit's own session briefs before being caught.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 11263efd-3cf9-4dc2-b8ed-6b6380193e9a
…ments

Optional.Of(...) does not exist. Of is declared only on the generic struct
(src/Reactor/Core/Optional.cs:46: `public static Optional<T> Of(T value)`),
so the sole valid spelling is Optional<T>.Of(...). Injecting the bare form
yields CS0305 "Using the generic type 'Optional<T>' requires 1 type
arguments".

This is the fourth phantom traced to shipped XML doc comments during the
guide audit, after Text(...), UseTheme(), and the With*Transition family.
The templates were quoting the source: extending-reactor-controls.md.dt
tracks the Optional.cs doc comment almost word for word. Fixing the copies
without the original just lets it re-seed.

Fixed 15 of the 16 shipped (///) occurrences:
  src/Reactor/Core/Element.cs                 x11  Optional<int>.Of(-1)
  src/Reactor.Analyzers/OptionalSentinelAnalyzer.cs  x3
  src/Reactor.Analyzers/NoOpModifierAnalyzer.cs      x1

Also corrected OptionalSentinelAnalyzer's DiagnosticDescriptor Description,
which is shown to users in the IDE and already used the correct
Optional<T>.Of / Optional<T>.Unset spelling two lines either side of the
wrong one.

Left alone by design: 6 `//` implementation comments (never shipped) and
one `//` comment in NoOpModifierAnalyzerTests. Optional.cs:57 is the 16th
and is fixed on azchohfi-guide-under-the-hood-audit; the two branches
combine to zero, as with the Element.cs Text( split.

Types verified, not assumed: every Element.cs site is a
`Optional<int> SelectedIndex`/`SelectedPageIndex` property, and the
CalendarDatePicker site is Optional<DateTimeOffset?>.

Verified: src/Reactor and src/Reactor.Analyzers both build Release with 0
warnings, 0 errors; docs compile produces no docs/guide diff (these
comments do not feed generated pages -- they ship via
GenerateDocumentationFile in Reactor.xml as IntelliSense); the 70
OptionalSentinel + NoOpModifier tests pass, and the filter was confirmed to
match non-zero so the pass is a measurement.

Residual scan: 0 shipped occurrences remain on this branch besides
Optional.cs:57, with a positive control showing 22 correct generic forms
now present.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 11263efd-3cf9-4dc2-b8ed-6b6380193e9a
Reactor validates executable code (the compiler plus REACTOR_DYM_003 /
REACTOR_DYM_005) and validates snippet-backed doc blocks (they compile as
part of the doc apps). It validates nothing else. Four documentation
surfaces are exempt from every existing check, and all four have shipped a
phantom API: bare ```csharp template blocks, example source carried in
string literals inside doc apps, gallery SourceCode strings, and /// doc
comments in src/ — the last of which ship in Reactor.xml as IntelliSense
and have been observed re-seeding the guide templates that quote them.

Mechanism note — this is NOT a `mur check` rule
-----------------------------------------------
`mur check` is a build-diagnostic post-processor: CheckCommand runs a
build, parses MSBuild diagnostics, and only invokes IRulePattern.TryMatch
per parsed diagnostic (CheckCommand.cs:161). On a clean build it prints
"ok" and no rule runs at all (CheckCommand.cs:198). IRulePattern requires a
Diagnostic, a SemanticModel and a CSharpCompilation.

Every target surface compiles clean, by construction:
  - docs/_pipeline/apps/docking/App.cs carried `Text("Hello")` inside a
    string literal and built green in Release/x64 for months;
  - .md.dt templates are not C# and are in no compilation;
  - /// comments are comments.

A rule under Check/Rules/ would therefore be structurally incapable of
firing on any of them. It is implemented as a docs-pipeline lint instead,
alongside the existing REACTOR_DOC_XLINK_001 / _TIER_W001 / _REFMARKER_001
family, which is why there is no AnalyzerReleases row (not a Roslyn
analyzer) and no CLI/analyzer parity mirror (one matcher, two consumers).

Design
------
PhantomSymbolLint is a pure matcher over (path, text, surface); the docs
compile and the test gate both call it, so they cannot drift. Phantoms live
in one table — add an entry and every consumer picks it up. Seeded with the
six needed to cover the audit's real defects: Text, UseTheme,
WithOpacityTransition, WithThemeTransitions, WithImplicitTransition, and
Optional.Of.

Surface gates keep it off executable code: Markdown lints only inside
fences, C# lints only /// lines, and <see cref="..."/> is masked because
crefs are compiler-validated (CS1574) and so are not an unvalidated
surface. A scoped `<!-- phantom:skip "Name" -->` marker — same spelling
convention as the pipeline's existing `<!-- xlink:skip -->`, and valid in
both Markdown and XML doc comments — covers the case where a doc must name
a phantom in order to warn against it.

False-positive classes found by the spike, each now a test
---------------------------------------------------------
- Case-insensitive matching flags the English phrase "…text (for…"
  (AccessibilityScanner.cs:94). Patterns are case-sensitive; C# is.
- A lookbehind excluding '>' silently misses `<c>Optional.Of(`, the form
  that phantom almost always takes — a self-inflicted zero.
- Receiver-qualified members named Text are all real (CellRenderers.Text,
  Editors.Text, DragData.Text, D3Charts.Text) and must not fire.
- D3Charts.Text is positional, so even unqualified under `using static` its
  first argument is numeric; the string-literal-first-arg rule excludes it.
- Example code inside a C# string literal escapes its quotes, so the
  opening quote must be matched as `"`, `\"` and `""`. The first
  implementation matched only the plain form and was blind to the exact
  surface the rule was written for; the docking fixture caught it.

Two-direction evidence
----------------------
Fires on real historical defects, not synthetic fixtures: the pre-6e80de46
docking string literals, layout.md.dt's ai:lock'd fences (×3), and the
shipped doc comments in Element.cs, ElementExtensions.cs, UseMemoCells.cs,
Animation.cs and Optional.cs.

Silent on the current tree where it should be: zero findings across all six
pages of this branch's slice, including charting.md.dt, whose prose names
the phantom in order to inoculate the reader.

Current triage: 25 template findings across 15 templates (layout ×3 is the
cited defect; the other 22 are new finds owned by other slices) and 35 ///
findings across 11 files in src/. Severity is Warning on roll-out, mirroring
CrossLinkLint's staging, so the docset does not break while those clear.

SrcDocComments_ContainOnlyTheKnownPhantomBacklog pins the src/ backlog as an
explicit per-file allow-list that fails in both directions: new rot fails,
and clearing a known occurrence also fails, so the list shrinks deliberately
rather than drifting.

Verified: dotnet test tests/Reactor.Tests --filter
"FullyQualifiedName~PhantomSymbolLintTests" → 26/26 passed;
Reactor.DocPipeline.Tests → 331/331 passed; `mur docs compile` produces no
docs/guide churn (the lint is read-only).

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

Follow-on to 974e5f5 (AGENTS.md). The under-the-hood audit session
corrected its own "PoolableWireFlags has 0 hits repo-wide" claim -- its scan
was src/**/*.cs, so it could not see .md files. A full-corpus sweep (3061
files, all types) found the remaining live instances.

Fixed:

- .github/skills/pr-review/dimensions/correctness.md -- the Element pooling
  bullet was built entirely on the non-existent PoolableWireFlags type and
  told reviewers to flag unguarded event wiring on poolable controls. V1
  pools only non-interactive controls, so that hazard does not exist.
  Rewritten around the real one: ElementPool's CWT is
  ConditionalWeakTable<UIElement, object> _compositorTainted, which EXCLUDES
  elements that had GetElementVisual() called (they permanently lose the
  XAML implicit-transition APIs). Kept the two sub-points that were correct.

- .github/skills/pr-review/dimensions/alternative-solution.md -- said a new
  ControlDescriptor is "registered in RegisterV1BuiltInHandlers". That
  bootstrap was deleted by spec-048 SS3.4 for trimming; registration is lazy
  via the factory's Reg<>.Done touch, explicit via ControlRegistry.Register,
  bulk via ReactorApp.RegisterAllBuiltIns().

- src/Reactor/Core/Reconciler.Mount.cs AI-HINT -- described "Mount() is a big
  switch over all Element subtypes -> MountXxx() methods", the exact legacy
  model spec 048 removed. No MountXxx() methods remain. This comment sits at
  the top of the file anyone greps for the extension point.

Deliberately NOT changed, having checked each rather than pattern-matching:

- src/Reactor/Hosting/ReactorApp.BuiltIns.cs:5 and two test files name
  RegisterV1BuiltInHandlers *correctly* -- they say it "was removed"/"was
  deleted", explaining why today's design exists. Editing them would have
  broken accurate text.
- docs/specs/** (~48 mentions) and docs/specs/tasks/039-...:304
  (PoolableWireFlags) are historical spec and audit records, not guidance.

Verified: src/Reactor builds Release/x64 with 0 warnings, 0 errors. The
corpus sweep used a positive control (22 files mention ReactorAttached) so
the counts are measurements, not silent misses -- the failure that produced
the original "0 repo-wide" claim.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 11263efd-3cf9-4dc2-b8ed-6b6380193e9a
The gate landed in 8ab509c asserted exact equality against the known
src/ backlog. That fails in both directions by design — but ~32 of the 36
occurrences are already fixed on branches queued for the same PR, so the
moment they merge the backlog shrinks and the gate goes red on whoever
merges, with no context. Anti-drift bookkeeping is not worth a red build
landing on an innocent party.

Ceiling semantics instead: a new file, a new phantom in a known file, or an
increased count fails; a decrease passes and is reported as a stale entry to
trim. The property that matters — the count can never silently grow — is
preserved, and the gate now survives the merge without coordination.

Exact-match pre-shrunk to the predicted post-integration state was the other
option offered. Rejected: those commits are not visible from this branch, so
the expected values would be transcribed from a description rather than
measured. A wrong transcription reintroduces exactly the red-at-merge
failure, except for a reason nobody can diagnose — and the test would be red
on this branch today, which means committing a knowingly-failing test.

Also keyed per (repo-relative path, phantom) rather than per file. A
per-file total would let five fixed Text occurrences mask five newly
introduced Optional.Of ones in the same file and still pass — a blind spot
in the original that only showed up on re-reading. Each phantom class is now
bounded independently, and the budget carries the owning branch per entry.

Mutation-verified in all four directions, with a guard asserting the
mutation actually applied — the first attempt silently changed nothing
(dictionary entries end in ',' not ';') and all four cases passed, including
the two that must fail:

  budget raised above actual  -> PASS  (simulates the merge landing)
  budget lowered below actual -> FAIL  "GREW … = 6 (budget 3)"
  budget entry removed        -> FAIL  "NEW … (no budget entry)"
  current tree                -> PASS

Verified: PhantomSymbolLintTests 26/26; Reactor.DocPipeline.Tests 331/331.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Follow-up to a38a6b4. That sweep used a probe requiring a quote after the
paren (`///.*[^A-Za-z]Text\("`), so it could not see a call passing a
variable or an ellipsis. Two shipped doc comments survived it:

  ElementExtensions.cs:2941
    <example>Text(statusMessage).LiveRegion(...)</example>
  Reactor.Analyzers/UnusedGridTrackAnalyzer.cs:46
    "a known Microsoft.UI.Reactor.Factories factory (Text(...), Button(...))"

The second is the more pointed one: an analyzer that reasons about which
calls are Factories factories was itself naming a non-factory as the
example. Its behaviour is correct -- IsReactorFactory resolves semantically
via ContainingType == "Microsoft.UI.Reactor.Factories", not a name list --
so this is prose only, verified before editing rather than assumed.

Found by the REACTOR_DOC_PHANTOM_001 lint authored on the rich-controls
branch, whose matcher does not assume a string-literal argument. Exactly the
argument for a standing rule over a one-off grep: my probe was shape-blind
in a way I could not see from inside it.

Occurrence counts reconciled while here: main carries 16 Text( occurrences
in /// comments, not the 12 previously reported -- Element.cs:340 holds two
on a single line (`VStack(Text("Hello"), Text("World"))`), so counting edits
undercounts occurrences.

Residual sweep on this branch, broadened patterns: Text( = 0, UseTheme( = 0,
With*Transition = 0, Optional.Of( = 1 (Optional.cs:57, deliberately left to
azchohfi-guide-under-the-hood-audit). Positive control: 35 /// comments cite
TextBlock(, so the zeros are measurements.

Verified: src/Reactor and src/Reactor.Analyzers both build Release with 0
warnings, 0 errors; docs compile produces no docs/guide diff.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 11263efd-3cf9-4dc2-b8ed-6b6380193e9a
Round 24: two suppressed findings, both right.

The inline-span parser only understood one backtick

  CommonMark delimits a span with a RUN of backticks and closes it on a run of
  equal length. Toggling one tick at a time mis-parses ``Optional.Of(`x`)``: the
  span is read as the empty string between the first two ticks, and the phantom
  inside is masked away as prose. Now matches runs of arbitrary length, with the
  multi-backtick case and an unterminated run pinned by tests.

A doc taught a test that cannot fail

  theming-tokens.md.dt offered a [Theory] over Light and Dark whose only
  assertion was Assert.Empty(AccessibilityScanner.Scan(tree)) -- and the scanner
  never resolves a brush (0 occurrences of RequestedTheme in
  AccessibilityScanner.cs, against 28 of AutomationName). Both InlineData cases
  return identical findings, so a banner hardcoded to a light-only colour passes
  exactly like a correct one. The theme parameter reaches nothing the assertion
  reads.

  That is the worst shape a documented test can take: it was presented as the
  remedy for the dark-mode regression it structurally cannot detect, in the
  section named after that regression.

  The paragraph above it was also wrong -- it said the testing page covers a
  fixture that mounts the component under both themes and snapshots both.
  testing.md.dt contains zero occurrences of theme, Theme, Dark or
  RequestedTheme, so there is no such pattern to point at.

  Rewritten: the block is now labelled as the trap, with a comment explaining
  precisely why it is vacuous, and the guidance says a colour regression is only
  observable once the tree is materialized and the resource lookup runs -- so it
  belongs in a selftest, which is also the only tier that can construct WinUI
  controls at all.

Verified: docs compile --ci exit 0, Reactor.Tests and DocPipeline.Tests green,
Release build of src/Reactor.Cli clean.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50199c9b-2da9-46a5-9afd-857958642219

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 256 out of 294 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/Reactor.Cli/Docs/PhantomSymbolLint.cs:124

  • The new Text guard still misses valid single-argument phantom calls whose argument starts with an unlisted expression shape, such as Text(enabled ? "On" : "Off"), Text(prefix + value), or Text((string)value). Those examples remain uncompiled and pass this lint, so the durability gate can silently regress. Detect an unqualified Text invocation with exactly one top-level argument (using balanced-token scanning) instead of enumerating selected argument prefixes, and add regression cases for these shapes.

Round 25 was right that the prefix enumeration could not converge: each round
added an arm and the next found another shape. Ternary, concatenation and cast
were still missing.

Balanced scanning asks the structural question instead: an unqualified Text
invoked with exactly one top-level argument. Two exclusions carry it.

  Multi-argument calls are not this phantom -- that keeps the positional
  D3Charts.Text(x, y, text) shape out even spelled unqualified.

  English prose is not an expression. A C# argument never contains two
  identifier tokens separated by nothing but whitespace: "prefix + value" has an
  operator between them, "the element" does not. That single test admits every
  expression shape without enumerating any, and it is what keeps sentences like
  "Text (the element) is set separately" silent.

Two things the scan surfaced that the regex had hidden

  Text(\"Hello\") stopped matching, because I had SkipLiteral honour backslash
  as an escape. On these surfaces a quote is spelled three ways -- ", \" (code
  embedded in a C# string literal, the original docking defect) and "" (the same
  verbatim) -- so a backslash before a quote is part of the quote's spelling,
  not an escape of it. Honouring it swallowed the closing quote and the call
  scanned as unterminated. This is the exact case the historical-defect theory
  exists to pin, and it caught it.

  Text(...) began firing across the docset. The ellipsis placeholder is how
  prose names a signature, not a call -- including in charting.md.dt's own
  sentence warning that there is no core Text(...) factory. Excluded
  structurally rather than papered over with a skip marker, since otherwise
  every signature mention would need one.

PhantomSymbol gains an optional Scanner; the other six phantoms are unambiguous
names and stay on their regexes.

Verified: docs compile --ci exit 0 (0 findings), Reactor.Tests and
DocPipeline.Tests green, Release build of src/Reactor.Cli clean.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50199c9b-2da9-46a5-9afd-857958642219
Comment thread src/Reactor.Cli/Docs/PhantomSymbolLint.cs Fixed
Comment thread src/Reactor.Cli/Docs/PhantomSymbolLint.cs Fixed
Comment thread src/Reactor.Cli/Docs/PhantomSymbolLint.cs Fixed

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 255 out of 294 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

src/Reactor.Cli/Docs/PhantomSymbolLint.cs:517

  • This prose discriminator also rejects valid C# expressions that use word operators. For example, both Text(value is null ? "" : value) and Text(value as string) contain adjacent identifier tokens, so HasAdjacentIdentifiers returns true and the phantom call silently passes. The matcher needs to recognize C# operator/token shapes rather than treating every adjacent identifier pair as prose.

Comment thread src/Reactor.Cli/Docs/PhantomSymbolLint.cs
Round 26: one suppressed finding, one real inline finding, and three CodeQL
style notes.

Word-shaped operators were being read as English

  The prose discriminator asked whether two identifier tokens sit adjacent with
  only whitespace between them. C# spells several operators with letters, so
  `Text(value is null ? "" : value)` and `Text(value as string)` looked exactly
  like prose and were dismissed.

  Adjacency now only counts when BOTH sides are ordinary identifiers. "the
  element" has no operator between the words; an expression always does, even
  when that operator is spelled with letters.

A wrapped call was never reported at all

  The scanner received one physical line, so it could only close a paren on that
  line. A doc example routinely writes

      Text(
          BuildLabel(item))

  and the whole invocation went unseen -- in fenced blocks and XML examples
  alike, the surfaces the gate exists for.

  It now gets a bounded lookahead window (8 lines), built only where every line
  is genuinely code: inside a fence, an all-code surface, or a contiguous ///
  block. Joining prose would let an unbalanced "Text (" in one sentence close
  against a ')' in the next and manufacture a false positive.

  The window creates a misattribution trap worth naming: an earlier line's
  window also reaches the call, so it would report it too. The scanner therefore
  only accepts invocations that START within the first line. The regression test
  asserts Single(findings) at the opening line, not merely that something fired
  -- an Any() assertion would have passed with the duplicate bug present.

Also applied the three CodeQL notes (Where on the phantom loop, Select on the
opener projection, Any in IsElidedArgument).

Verified: docs compile --ci exit 0 (0 findings), Reactor.Tests and
DocPipeline.Tests green, Release build of src/Reactor.Cli clean.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50199c9b-2da9-46a5-9afd-857958642219
Comment thread src/Reactor.Cli/Docs/PhantomSymbolLint.cs Fixed
Comment thread src/Reactor.Cli/Docs/PhantomSymbolLint.cs Fixed
Comment thread docs/_pipeline/apps/hooks/App.cs Fixed

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 255 out of 294 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/Reactor.Cli/Docs/ReferenceGen/CrefSignature.cs:151

  • A complete <typeparam> set is not necessarily in declaration order—XML documentation preserves the tags in authoring order. For a valid method declared <TKey, TValue> but documented with the TValue tag first, this branch emits Method<TValue, TKey> and substitutes those names into the wrong parameter slots and anchors. Either obtain ordinals from assembly metadata, or use placeholders for multi-parameter methods when order cannot be proven.

Round 27 reported no new comments on b8ec120, with one suppressed finding and
three CodeQL notes.

A complete <typeparam> set is not an ordered one

  My own comment said these tags are "in XML comment order" and then concluded
  that a complete set could be indexed positionally. Those two statements
  contradict each other. XML preserves authoring order, so a method declared
  <TKey, TValue> whose TValue tag is written first renders Method<TValue, TKey>
  -- a complete set of real names in the wrong slots, which then propagates into
  the parameter types and the anchor.

  Nothing in a cref carries declaration ordinals, so the order cannot be
  recovered here. Names are now used only at arity 1, where there is nothing to
  order; placeholders stand in beyond that. A placeholder is less informative, a
  swapped name is wrong and wrong silently.

  The new order-insensitivity test asserts the in-order and swapped inputs
  render identically, which fails if order ever leaks back in. The old test
  asserted the unsafe behaviour and was replaced rather than relaxed.

Empty catch blocks

  A comment does not satisfy the rule -- it wants a statement -- so the three
  catches that end their lambda now `return;`.

  ScreenshotCapture is deliberately not among them: its catch is followed by the
  `port >= 0 && token is not null` handshake check, so returning early would
  skip it and change behaviour on a partially-completed handshake. Left as a
  commented empty block; the note there explains why cancellation is ignored.

Also applied the two remaining CodeQL notes (Where in HasAdjacentIdentifiers, a
named projected sequence for the opener indices).

Verified: docs compile --ci exit 0, Reactor.Tests and DocPipeline.Tests green,
Release builds of src/Reactor.Cli plus the hooks, regedit and demo-script-tool
apps clean.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50199c9b-2da9-46a5-9afd-857958642219

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 255 out of 294 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

samples/apps/demo-script-tool/App/DemoScriptShell.cs:176

  • This explanation no longer matches the implementation: cts.Token is captured synchronously before Task.Run, so the previous worker cannot still be reading that property. The remaining hazard is disposing the source while Task.Delay or the file write registers the captured token; likewise, the worker still indirectly uses the source through CancellationToken. Update the comment to describe that registration race.

Comment thread src/Reactor.Cli/Docs/ReferenceGen/XmlDocReader.cs
Comment thread src/Reactor.Cli/Docs/PhantomSymbolLint.cs Fixed
Round 28 found the sibling of the bug fixed last round: I corrected method type
parameters in CrefSignature but left declaring-type parameters, which
ReferenceGenerator builds the same way and indexes the same way.

  typeParamsByType projected <typeparam> tags straight into a positional list,
  then FormatType resolved `0/`1 against it. A type documented <TResult> before
  <TInput> renames every parameter into the wrong slot, and the signature it
  produces feeds the anchor, so the damage outlives the heading.

  Same rule as the method side: a single documented name has no order to get
  wrong and is used; from two upward, placeholders. Arity comes from the cref,
  not from the tag count, so an under-documented type still gets the right
  number of slots.

  No generated-docs drift, so this is a guard against a latent defect rather
  than a correction of shipped output.

The regression test needed two attempts, which is the point

  The first version generated one member and compared headings. It passed --
  and still passed when I reverted the fix, because a page with a single member
  renders only "# Swap": the per-member signature heading, the only place
  declaring type parameters appear, is never emitted. Both sides were empty and
  the assertion compared nothing.

  The test now declares two overloads so the collision page renders signatures,
  and asserts a non-vacuity floor (a "Swap(" heading exists) before comparing.
  It fails when the fix is reverted, which the first version did not.

Also corrected the DemoScriptShell comment, which described a hazard my own fix
had already removed: the token is captured synchronously before Task.Run, so
the worker never reads cts.Token late. The real remaining hazard is disposing
the source while Task.Delay or the file write holds a registration on it.

Plus the CodeQL Select note on the opener projection.

Verified: docs compile --ci exit 0, Reactor.Tests and DocPipeline.Tests green,
Release builds of src/Reactor.Cli and demo-script-tool clean.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50199c9b-2da9-46a5-9afd-857958642219

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 255 out of 294 changed files in this pull request and generated no new comments.

Comment thread src/Reactor.Cli/Docs/PhantomSymbolLint.cs Fixed
The last open code-quality thread. LooksLikeSingleArgTextCall now reads as one
chain -- project each opener to its argument, drop the empty and elided ones,
and return whether any survivor is not prose -- instead of a foreach with three
continue guards.

Behaviour is unchanged and the phantom-lint theories (historical defects,
expression shapes, word operators, multiline, prose, elided placeholders) all
still pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50199c9b-2da9-46a5-9afd-857958642219

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 255 out of 294 changed files in this pull request and generated no new comments.

@azchohfi
Alexandre Zollinger Chohfi (azchohfi) merged commit ffe1de4 into main Aug 26, 2026
23 checks passed
@azchohfi
Alexandre Zollinger Chohfi (azchohfi) deleted the azchohfi-guide-sample-audit branch August 26, 2026 20:58
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