All notable changes to Reactor will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning
once a 1.0.0 release is cut. While the project is pre-1.0 and labeled experimental,
the public API surface may change between releases without notice.
TextField→TextBoxrename. The record typeTextFieldElementis renamed toTextBoxElementfor parity with WinUI'sMicrosoft.UI.Xaml.Controls.TextBox. TheTextField(...)factory remains as a non-erroring[Obsolete]forwarding alias for one release; call sites should migrate toTextBox(...). The alias will be removed in a future release.
-
Factories.TextField(...)— useFactories.TextBox(...)instead. EmitsCS0618warning; will be removed in a future release. -
Spec 045 Phase 2 — §2.29 ready for human review gate. Every Phase-2 implementation item in
docs/specs/tasks/045-docking-windows-implementation.mdis now[x]complete or[~]partial with an enumerated upstream-spec blocker (spec 027 / 031 / 036 / docking analyzer pack). The ten human-review items (§2.29 items 9–18) are gated on the visual sit-down review itself. Showcase + TestApp both build clean; 324 docking unit tests pass + 409 devtools tests pass. -
Spec 045 Phase 2 — §2.22 high-contrast chrome brushes.
DockSplitterControlandDockDropTargetOverlayControlswap their hard-coded ARGB literals forThemedBrush(key, fallback)lookups againstApplication.Current.Resources. HC themes now retheme the docking chrome via the same dictionary path WinUI defaults rely on. ARGB fallbacks remain for the headless-harness no-Application case. System brushes: splitter handle →SystemControlForegroundBaseMediumLowBrush; splitter hover + drop-target Stroke + preview Border + indicator fill →SystemControlHighlightAccentBrush; drop-target outer Fill →SystemControlBackgroundChromeMediumLowBrush; preview body + Center fill →SystemControlBackgroundAccentBrush(the preview Border carries an explicitOpacity=0.30for the transparent overlay). -
Spec 045 Phase 2 — §2.19 XAML wrapper unhooked from the solution. The Phase-1
Reactor.Docking.Xamlwrapper assembly was removed fromReactor.slnxahead of the §2.29 human-review gate: ProjectReferences dropped fromDockShowcase.csproj,Reactor.AppTests.Host.csproj,Reactor.Tests.csproj;InternalsVisibleTo("Microsoft.UI.Reactor.Docking.Xaml")dropped fromReactor.csproj; showcase entry point dropped theREACTOR_DOCK_XAML=1A/B flip — the native renderer (§5.1) is the only path. Phase-1-specificDockingSmokeFixtures+BehaviorBridgeMappingTestsretire alongside;NativeDockingSmokeFixturescover the same surface. Source undersrc/Reactor.Docking.Xaml/stays on disk per the §5.6 reference contract; a follow-up commit removes the directory. -
Spec 045 Phase 2 — §2.30 shape-only
layoutOverride. The docking host's internallayoutOverridepreviously stored the full user-mutatedDockNodetree (with each leaf'sContent/Title/CanClosesnapshotted at drag-end). That broke the controlled-input contract: app re-renders couldn't push fresh pane bodies because the override always won. Apps had to walk the override and refresh leaf bodies manually. Now the host stores SHAPE-only (leaf records stripped to justKey) and resolves pane content per render viaDockLayoutMutator.ResolveContents(shape, manager.Layout), matching shape leaves to the app's full records by Key. Apps declare the full tree idiomatically inRender(); state flows naturally even after a user drag. New helpersDockLayoutMutator.StripContent+ResolveContents. 6 new unit tests. M19/M20 drag matrix selftests still pass.samples/Reactor.TestApp/Demos/DockingDemo.csdrops itsRefreshContentswalker /OnLiveLayoutChangedplumbing — the demo is now plain Reactor.
- Spec 045 Phase 2 — §2.26
docking.list/docking.snapshot/docking.dockMCP tools. NewDevtoolsDockingTools.Register(server)wires three tools onto the liveDevtoolsMcpServer.docking.listenumerates hosts viaDockHostRegistry.Snapshot()(pane count + active key + side counts per host).docking.snapshotreturns theDockSnapshotfor a single host through the existingDockSnapshotBuilder.FromRecordpath.docking.dockaccepts{ hostId, paneKey, action, target?, side? }and routes throughDockHostModelBridge.Get(manager)to call the matchingDockHostModelmutator (dock / float / hide / show / close / activate / pinToSide). All tools execute on the UI dispatcher viaserver.OnDispatcher<T>(...). Wired intoReactorApp.cs:1012next to the existing devtools tools. 11 new unit tests inDevtoolsDockingToolsTests. - Spec 045 Phase 2 — §2.24 / §2.9 per-pane WindowPersistedScope.
New
DockHooks.UseDockPanePersisted<T>(string key, T initial)extension onRenderContext. Auto-prefixes the supplied key withpane:<paneKey>:and forwards toRenderContext.UsePersisted<T>(key, initial, PersistedScope.Window)so two panes sharing the same unprefixed key get independent WindowPersistedScope slots. Throws when called outside any pane subtree (same contract asUsePane). XML docstring carries the cross-user-secret caveat (apps must clear sensitive per-pane data on logout / scope change). 3 newDockHooksTestscases. - Spec 045 Phase 2 — §2.2 per-tab pin button on ToolWindow tabs.
TabViewItemDatagains optionalIsPinnable/IsPinned/PinAutomationName/PinAutomationId/OnPinRequestedfields. TheTabViewElementreconciler builds the tab Header as a StackPanel (title TextBlock + Segoe Fluent Icons pin Button) whenIsPinnable=true; otherwise the cheap string-header path is preserved verbatim.DockTabGroupRenderer.Renderaccepts a newonPinRequested: Action<ToolWindow>?callback and auto-enables the affordance on ToolWindow tabs whoseCanAutoHide=true.DockHostNativeComponent.PinToSideViaTabButtonroutes clicks throughDockHostModel.PinToSide(tw, DockSide.Left)so the §2.16 drain + live-region announcement contract fires identically to a programmatic pin. AT name + tooltip use the localizedDocking.Menu.PinToSidestring; AutomationId ispin:<paneKey>. 3 newDockTabGroupRendererTestscases. - Spec 045 Phase 2 — §2.26
docking.snapshotbuilding blocks. NewDockHostRegistry(process-wideWeakReference<DockManager>registry with stabledh:{n}ids) wired throughDockingNativeInterop's mount/update/unmount handlers alongside the existing bridges.DockSnapshotshape +DockSnapshotBuilderpure transform surface a content-ref-free layout tree (DockSnapshotSplit/DockSnapshotTabGroup/DockSnapshotLeafDockSnapshotPanefor identity + role + permissions). 13 new unit tests inDockSnapshotBuilderTests. The JSON-RPC tool registration on the internalDevtoolsMcpServerrides on the shape being stable.
- Spec 045 Phase 2 — §2.10 Alt+F7 hidden-pane picker. Re-uses
the
DockNavigatorPopupprimitive to pick a side-stripped tool window for re-show. EnumerateseffLeftSide/effTopSide/effRightSide/effBottomSide(where hidden ToolWindows end up afterHide/CanHide=trueclose via the §2.16 drain). Commit callsmodel.Show(pane)which routes throughDockLayoutMutator.ShowFromHistory(§2.15) — and now consultsIDockLayoutStrategy.BeforeInsertToolWindowfirst so apps can override the remembered-container route. No-ops on an empty side-strip set. - Spec 045 Phase 2 — §2.23 RTL selftest fixture.
NativeDocking_Rtl_FlowDirectionAndSplitterSignmounts a two-pane docking host, appliesFlowDirection.RightToLefton the realized host Border, and asserts every realizedDockSplitterControl+TabViewinherits RTL + the pointer- drag remains RTL-correct (WinUI coord-space transform). Validates the "WinUI FlowDirection inheritance handles it" claim across §2.23's sidebar / tab-order / splitter / glyph bullets. - Spec 045 Phase 2 — §2.27 composition fixtures. Three new
host-mounted selftests:
Composition_ContentMutationFlowsToActivePane(in-pane state flows into pane body),Composition_SiblingMutation_PreservesActivePaneIdentity(sibling state change preserves pane wrapper Border identity), andComposition_Rehydration_ContentMatchesByKey(save / load round-trip + app-supplied content lands in restored slots by Key, AutomationId survives). - Spec 045 Phase 2 — §2.22 keyboard-only docking cycle fixture.
NativeDocking_A11y_KeyboardCycle_NavigatorCommitsActivedrives the navigator commit + cancel paths via test hooks and asserts the host-side wiring contract (chord → popup → active pane →OnActiveContentChanged) end to end. - Spec 045 Phase 2 — §2.10 Ctrl+Tab pane navigator overlay.
VS-style navigator: Ctrl+Tab opens a Popup over the host Border
listing all open panes (depth-first leaf enumeration via the new
DockHostKeyboard.EnumerateLeaves); subsequent Ctrl+Tab / Ctrl+Shift+Tab presses cycle the selection ±1; Ctrl release commits and switchesactivePaneKey+ firesOnActiveContentChanged; Esc cancels. The newDockNavigatorPopupprimitive lives outside the Reactor reconciler (per-hostPopupinstance keyed via aConditionalWeakTable<FrameworkElement, DockNavigatorPopup>) so opening it doesn't perturb the render tree (M19 / M20 control- identity assertions stay green). The chord bridge gains an optionalOpenNavigator(int delta)slot;DockingNativeInteropattaches the two new accelerators alongside the existing chord set. Unit coverage inDockNavigatorTests(9 cases). - Spec 045 Phase 2 — §2.10 UIA live-region announcements. Layout-
state transitions (close, tear-out / float, dock-confirm, pin-to-
side, hide, show) now raise polite UIA notifications against the
dock-host element via WinUI's
RaiseNotificationEventAPI. The newDockHostLiveAnnounceris aConditionalWeakTable<DockManager, FrameworkElement>bridge parallelingDockChordBridge/DockHostModelBridge;DockingNativeInteropregisters the host Border at mount and clears it on unmount. Announcement templates live underDocking.LiveRegion.*(LiveDocked,LiveFloated,LivePinned,LiveClosed,LiveHidden,LiveShown), parameterized by{paneTitle}viaDockingStrings.LiveAnnouncement. Notification API is used in place of a visibleTextBlock+LiveSetting=Politeregion so the visual tree is unchanged (M19 / M20 control-identity assertions stay green). - Spec 045 Phase 2 — §2.22 splitter keyboard resizable + RTL.
DockSplitterControlis now tab-focusable (IsTabStop=true,UseSystemFocusVisuals=true) with arrow-key resize through the same direct-mutation path the pointer drag uses; defaultKeyboardStep= 16 DIP. UnderFlowDirection.RightToLeftthe Columns-direction Left/Right mapping inverts so a Right press grows the visually-right pane. - Spec 045 Phase 2 — §2.23 RTL drop-target glyph mirror.
DockDropTargetOverlayControlnow emits a directional side- stripe overlay on each Split / Dock target (filled rectangle pinned to the matching edge viaHorizontalAlignment/VerticalAlignment). FlowDirection inheritance auto-mirrors the Left-anchored indicators to the right edge under RTL, so DockLeft / SplitLeft glyphs visually flip to match the also-mirrored button positions. - Spec 045 Phase 2 — §2.22 focus invariants on close.
DockHostLiveAnnouncer.FocusHostFallback(manager)programmatically hands focus to the host element when a close removes the last pane (no group with documents remains). Sibling-pane focus carry on partial close is inherited from TabView's selection-change focus shift on the next render. Tear-out path keeps WinUI's default focus shift to the new floating window. SelftestNativeDocking_A11y_FocusFallback_OnLastPaneClosedrives a model-mutator close through the §2.16 drain, verifies the bridge registration survives the re-render cycle, and gates theFocusHostFallbackcall site against the no-group-left layout state. - Spec 045 Phase 2 — §2.6 floating-window lifecycle events.
DockFloatingWindow.Opennow firesDockManager.OnFloatingWindowCreatedimmediately after window registration (carrying the dragged source pane) andOnFloatingWindowClosedfrom the underlyingReactorWindow.Closedevent (carrying the best-effort pane reference; may be stale after a cross-window dock-back already migrated it). Subscriber exceptions are swallowed so a buggy observer cannot break tear-out or cleanup paths. - Spec 045 Phase 2 — §2.20 perf budget tests. Three new
unit tests in
DockPerfBudgetTestsenforce the §2.20 spec budgets: layout JSON load (200 panes, median < 200ms CI ceiling / 50ms spec budget), drop-target hit-test hot path (no measurable allocation across 100kComputePreviewBoundsiterations — 1B/iter cap), and 50-pane mutator shape change (RemovePane + InsertPaneAtTarget, median ≤ 25ms CI ceiling / 1ms spec budget). Pattern follows spec 034 (allocation counter)- spec 031 (median-of-N sampling). Remaining §2.20 items (frame-aligned hover, tear-out frame budget, cold-start, DPI re-layout) ride on the spec 031 frame-aligned sampler harness.
- Spec 045 Phase 2 — §2.22 accessibility baseline. Every docked
pane's wrapper Border now carries a stable
AutomationProperties.AutomationId = "pane:<key>"derived fromDockableContent.Key, plusAutomationProperties.Namefrom the pane's app-owned Title — so screen readers and selftests address panes deterministically across re-renders. The DockHost root Border exposesAutomationLandmarkType.Custom+ aLocalizedLandmarkType+Namesourced fromDockingStrings(keyDocking.DockHost.Landmark; default "Docking area"). New unit suiteDockA11yTests(5 cases) + selftest fixtureNativeDocking_A11y_HostLandmarkAndPaneAutomationIdswalking the realized control tree. - Spec 045 Phase 2 — §2.21 localization routing. Every
docking user-facing string now flows through a static
DockingStrings.Get(key)router with an optionalFunc<string, string?>? Resolverapps wire at startup to forward keys to theirIntlAccessor. The drop-target overlay (DockDropTargetOverlayControl.GetLocalizedName+ landmarkAutomationProperties.Name), the side-pin tooltip (DockSideStripRenderer), and the floating-window default title (DockFloatingWindow.Open) now consult the router. Keys are constants onDockingStringKeys, mirrored 1:1 againstsrc/Reactor.Docking.Xaml/Resources/Reactor.Docking.resw— drop-target tooltips, navigator headings, per-pane context-menu items (Close/Hide/Float/PinToSide/AutoHide/MoveToNextGroup), side-pin tooltip prefix, floating-window default title, layout-restore error, host landmark.DockingStrings.SidePinTooltip(title)performs the placeholder substitution after lookup so translators can rearrange the surrounding text. Unit coverage inDockingStringsTests(9 scenarios). - Spec 045 Phase 2 — §2.25 reliability close-out. Three new
host-mounted fixtures under
NativeDockingReliabilityFixture:CrashMidDrag_LeavesPersistedLayoutClean(mid-drag layout save contains no drag-session state — drag state is in-memory only and doesn't leak into persisted JSON; restart drops the in-flight drag, reloaded layout is shape-identical to the pre-drag tree),FloatingWindowClosesOnHostUnmount(floating windows opened from aDockManagerare tracked per-host; the host'sDockingNativeInteropunmount handler closes them so they don't outlive the host), andEventSubscriptionLeakBaseline(100-pane open/close cycle stays within a 32 MB allocation delta — smoke test against catastrophic retention; precise budget tracked via §2.20 perf benchmarks). NewDockFloatingClamp+DockDisplay+DockFloatingBoundstypes (insrc/Reactor/Docking/Native/) implement the §2.6 / §2.25 multi-display restore clamp: saved floating bounds with < 200 × 100 DIP overlap against any display recenter on the primary; sizes clamp to the primary work area minus a 32 DIP margin. Pure math covered by 9 new unit tests inDockFloatingClampTests.DockFloatingWindow.Opentakes optionalsavedBounds+displays+managerparameters; tear-out and Float-mutation call sites pass the live manager so the per-host tracker (ConditionalWeakTable-backed) sees each open. - Spec 045 Phase 2 — Reliability + security selftests (§2.24,
§2.25). Four new host-mounted fixtures under
NativeDockingReliabilityFixture:CorruptLayoutFallback_HostMounted(corrupt JSON → fallback + event),OffThreadMutation_ThrowsAndDoesNotQueue(off-dispatcher mutators throw and don't dirty the queue),UseEffectCleanup_RunsOnPaneClose(programmatic close drains through the §2.16 mutator queue and the body unmounts from the visual tree), andDragSessionPayload_ObjectRefsOnly(DockDragSessionholds object refs, refuses a second concurrent drag, clears on End). Cross-ref §2.24 / §2.25 close-out items. The cleanup test surfaced a Reactor-side gap whereComponentElementinstances embedded insideDockableContent.Contentdon't run theirUseEffectcleanups when the leaf disappears — tracked as a known limitation in the fixture's docstring. - Spec 045 Phase 2 — Closed-to-empty layout fix. A subtle bug
in the drag/close/drain paths caused
setLayoutOverride(null)to revert to the controlled-input prop, resurrecting a closed pane. The internal state is now aLayoutOverride(DockNode?)wrapper so the renderer can distinguish "no override" from "override is intentionally empty". All fivesetLayoutOverridecall sites (drain, tear-out, tab close, drag confirm, chord close) updated. Caught by theReliability_Effect_BodyGoneFromTreeassertion in the new reliability fixture. - Spec 045 Phase 2 — Corrupt-JSON ETW emission (§2.7).
DockLayoutSerializer.Loadnow classifies every fallback path into a PII-safe category (empty/oversize/json-parse/unsupported-schema/null-document/schema-missing/validation) and emits the newReactorEventSource.DockingLayoutLoadFallbackevent (id 16, Warning,Errorskeyword) carrying only the category string — the fullDockLayoutLoadResult.FailureReasoncontinues to surface to in-process callers under app ACL. Closes the last open checklist item on §2.7. (spec 045 §2.7, spec 044) - Spec 045 Phase 2 — Model-mutation drain (§2.16).
DockHostNativeComponent.Rendernow drainsDockHostModel.Pendingon each render pass: every queuedDock/Float/Hide/Show/Close/Activate/PinToSideop translates to a layout override (or side-strip override for the side-affecting ops) and fires the matching lifecycle event (OnContentDocked,OnDocumentClosing+Closed,OnToolWindowHiding+Hidden,OnContentFloating+Floated,OnActiveContentChanged). The model exposes a new internalOnMutationQueuedcallback that the host wires tobumpTickso any mutator wakes the reconciler into a re-render even when called from outside an event handler. A newDockHostModelBridge(mirrorsDockChordBridge) lets tests and devtools grab the live model instance from aDockManagerreference. Lights upIDockLayoutStrategy.AfterInsert*adjustments actually landing on the rendered tree (§2.13),DockHostModel.Showusing the §2.15 PreviousContainer history, and programmatic Dock/Float/Hide/PinToSide mutations affecting the live layout. Verified by new selftestNativeDocking_ModelDrain_DockCloseActivatePinAffectsLiveTree(9 assertions) and unit tests for the queue-wake contract. (spec 045 §2.16) - Spec 045 Phase 2 — Composition-driven docs selftest (§2.18). New
NativeDocking_CompositionDrivenDocumentsRespectKeyedReconciliationfixture mounts a layout where the documents list is held in app state, then runs add / remove cycles to verify thedocuments.Select(d => new DockableContent(Key: d.Id, ...))pattern. Keyed reconciliation preserves the TabView control instance across the structural changes — the fixture assertsReferenceEqualson the TabView between initial mount and the post-add render. Codifies the spec §5.3.7 contract that Reactor's functional composition replacesDocumentsSource/LayoutItemTemplate/ContentResolver. (spec 045 §2.18) - Spec 045 Phase 2 — IDockBehavior obsolete forwarder (§2.12).
IDockBehavior(the P1 interface) andDockManager.Behavior(the property that consumes it) are now marked[Obsolete]with migration pointers to the per-event Action props that landed in Phase 2 (OnContentDocked/OnContentFloating/OnContentFloated). Slated for removal one release after Phase 2 ships. The P1 wrapper assemblies (Reactor.Docking.Xaml) suppress the obsolete warning at file scope while they continue to bridge the interface for source compat. (spec 045 §2.12) - Spec 045 Phase 2 — PreviousContainer routing (§2.15). The close /
tear-out paths in
DockHostNativeComponent(tab close button,Ctrl+F4/Ctrl+Wchord, drag-out tear-out) now record the pane's immediateDockTabGroupcontainer intoPreviousContainerTrackervia the newDockLayoutMutator.FindContainerwalk before removing the pane. A newDockLayoutMutator.ShowFromHistory(root, pane, fallback)pure-function helper folds a remembered pane back into its originalDockTabGroupwhen that group still lives in the tree (matching VS's "show panel where you left it"); falls back toInsertPaneAtTarget(root, pane, fallback)when the remembered group has been torn down. Caller-side wiring (DockManager.Showprogrammatic API, drag-back snap hint) attaches when the §2.16 model-mutation drain materializes theShowOppath. 7 new unit tests cover theFindContainer/ShowFromHistorymatrix. (spec 045 §2.15) - Spec 045 Phase 2 — Document/ToolWindow default tab styling (§2.8).
DockTabGroupRenderer.Rendernow auto-resolves tab styling based on the group's content type: a group whose documents are allToolWindow(and where the user hasn't customizedTabPosition/CompactTabsbeyond the record's defaults) flips to bottom-position + compact tabs (matches Office / VS tool-pane convention). All-Documentand mixed groups stay at the top position + full-width default — a tool window dragged into an editor strip doesn't collapse the whole strip to compact. TheTabPosition.Bottomvisual still renders as top-strip per the §2.2 limitation (no WinUI TabView bottom mode), but the resolved value flows through so future bottom-strip support picks it up. 5 new unit tests lock down the resolution matrix (all-tool / all-doc / mixed / explicit-defaults-on-tool / explicit-compact-on-tool). (spec 045 §2.8) - Spec 045 Phase 2 — Docking permission gating (§2.14). The native
drag pipeline now honors
DockableContent.CanMove/CanFloat/CanClose:HandleTabDragStartingrefuses to begin a session for a pinned pane (logs aNoteop);HandleTabDragCompletedrefuses tear-out whenCanFloat=false(session ends, layout untouched); the drop-targetOnConfirmre-checksCanMoveon the source pane so the keyboard-drivenCtrl+Shift+Mflow can't drop a pinned pane that turned read-only between mode-enter and confirm;EnterKeyboardDropModeskips opening the overlay entirely for a pinned active pane. Tab close button now routes throughCloseTabViaButtonwhich re-checksCanCloseand goes through the cancellableOnDocumentClosing→OnDocumentClosed→OnLiveLayoutChangedevent chain (matching theCtrl+F4chord path from §2.10). UI cues for disabled permissions (cursor / disabled-tab style) ride on the §2.8 tab styling pass. (spec 045 §2.14) - Spec 045 Phase 2 — Layout strategy dispatch (§2.13). The
manager-side dispatch into
IDockLayoutStrategyis now wired inDockHostModel.Dock: when the host component mirrorsDockManager.LayoutStrategyonto the model each render, programmaticDock(content, target)callsBeforeInsertDocumentorBeforeInsertToolWindow(subtype-routed) first; atruereturn short-circuits the default insertion (strategy claims placement via the model surface); afalsereturn queues the defaultPendingMutation.DockOpand then fires the matchingAfterInsert*hook so apps can layer dimensions / pinning / activation on top. BareDockableContent(P1 source-compat shape) bypasses the typed hooks since the strategy contract is defined against the §2.8Document/ToolWindowsubclasses. Six new unit tests underLayoutStrategyTestslock down the no-strategy passthrough, document/tool-window short-circuit, pass-through-then-AfterInsert, subtype routing, and bare-pane bypass. (spec 045 §2.13) - Spec 045 Phase 2 — Docking keyboard chords (§2.10, initial set).
Three chord families land on the Reactor-native dock host:
Ctrl+PageUp/Ctrl+PageDowncycle the active tab group with wrap (VS parity);Ctrl+F4/Ctrl+Wclose the active document via the cancellableOnDocumentClosing→OnDocumentClosedpath;Ctrl+Shift+Mflips the §2.3 drop-target overlay into a keyboard-initiated drag mode (arrow keys + Enter to confirm, Esc / repeat-chord to dismiss) with the active pane as the implicit source. Wiring goes through a newDockChordBridge(ConditionalWeakTable keyed byDockManagerinstance) so the mount-timeKeyboardAcceleratorset on the dock hostBorderpicks up live chord delegates from the component each render — no extra layout layer (aCommandHostGrid wrapper perturbed M19's outer-FlexPanel ActualWidth and was abandoned). Selected- index perDockTabGroupis now host-tracked via a path-keyedselectedIndexStore(mirrors the §2.1 ratio store) so chord cycling sticks across re-renders without breaking the controlled-input shape — apps that pinActiveDocumentstill win forUseIsActivePane/ context propagation. Deferred to a follow-up pass:Ctrl+Tabpane navigator overlay,Alt+F7hidden-pane picker, UIALiveSetting=Politeannouncements, and spec-027-driven configurable binding. 16 new unit tests cover the pure helpers (DockHostKeyboard.{FindGroupContainingKey, FindFirstGroup, CycleIndex, BuildChords}) plus theDockChordBridgeround-trip. (spec 045 §2.10) - Spec 045 Phase 2 — Docking drag pipeline (§2.4). Tab tear-out
- drop-target dock now works end-to-end on the Reactor-native
renderer.
DockDragSessionis the object-ref payload (replaces upstream WinUI.Dock's static GUID→object dict that spec §8.9 flagged as a security/reliability anti-pattern). Tab drag wires through newTabViewElement.OnTabDragStarting/OnTabDragCompletedevents: dragstart begins a session + flips the §2.3 overlay; confirm rebuilds the layout viaDockLayoutMutatorand firesOnContentDocked; drop-outside opens a floating window (OnContentFloated); Esc cancels viaOverlayDismissed. The host component shadowsManager.Layoutwith alayoutOverridestate so drag results stick until the app explicitly syncs throughOnContentDocked. 23 new unit tests (6 forDockDragSession, 17 forDockLayoutMutator) plusNativeDocking_DragSessionConfirmMutatesLayoutsmoke fixture cover the state machine + layout-mutation algebra. Keyboard- initiated move (Ctrl+Shift+M) and the standalone.OnPanrecognizer remain on the §2.10 / follow-up list. (spec 045 §2.4)
- drop-target dock now works end-to-end on the Reactor-native
renderer.
- Spec 045 Phase 2 — Docking drop-target overlay (§2.3).
DockDropTargetOverlayControllands as the Reactor-native replacement for WinUI.Dock'sDockTargetButton+Preview.xaml.cs. Renders 9 drop targets (5 split + 4 edge perDockTarget) at minimum 44×44 DIP (WCAG 2.5.5 / spec §8.7), with a hover preview rectangle keyed offComputePreviewBounds(target, hostW, hostH). Targets are focusable and arrow-key navigable throughNextFocus(cluster cross + edge ring);Enterconfirms,Escdismisses. The overlay readsUISettings.AnimationsEnabledat construction for reduced-motion gating. Mounting is gated by the newDockManager.ShowDropTargetsprop withOnDropTargetHovered/OnDropTargetConfirmed/OnDropTargetsDismissedcallbacks — the §2.4 drag pipeline flips the flag mid-gesture; apps can also drive it directly for keyboard- initiated move (§2.10Ctrl+Shift+M) or testing. AT names are inline English pending the §2.21Docking.*resource pass. 20 unit tests cover preview bounds, focus graph, and AT names; theNativeDocking_DropTargetOverlayShowsAndDismissessmoke fixture exercises the full mount → confirm → unmount cycle. (spec 045 §2.3, §8.7) - Spec 045 Phase 2 — Docking (foundation). Foundation layer of the
Reactor-native rewrite. The Phase-1 public API moves from
src/Reactor.Docking.Xaml/intosrc/Reactor/Docking/(sameMicrosoft.UI.Reactor.Dockingnamespace, so app source references are unchanged); Phase-2 additive surface extensions land on top:DocumentandToolWindowsealed records (Phase-2 §5.3.1) with distinct default permissions (Document.CanClose=true,ToolWindow.CanClose=falsebecause X-button hides per AvalonDock semantic;ToolWindow.CanPin=true,CanAutoHide=true,CanDockAsDocument=true).Document<TState>generic record carrying typed per-pane state;TStateversioning is the app's responsibility (§5.3.2 / §8.11).CanFloat(default true) andCanMove(default true) added to theDockableContentbase (§5.3.8).IDockLayoutStrategyinsertion-policy hook with default-method bodies — apps short-circuit insertion viaBeforeInsertDocument/BeforeInsertToolWindow, or post-process viaAfterInsert*(§5.3.6).- 15 cancellable lifecycle event-arg classes
(
DockLayoutChanging/Changed,DockDocumentClosing/Closed,DockToolWindowHiding/Hidden/Closing/Closed,DockContentFloating/Floated/Docking/Docked,DockActiveContentChanged,DockFloatingWindowCreated/Closed); every*ingcarriesCancel.DockManagernow exposes 15Action<TArgs>?props for each (§5.3.5). IDockLayoutMigrationinterface +DockLayoutMigrationRegistryladder with built-in v1→v2 step (synthesizes keys from titles per §5.4.4). Forward-tolerant for schemas newer than the loader target.DockHostModelinternal source-of-truth class with mutation queue (Dock/Float/Hide/Show/Close/Activate/PinToSide). Mutations are UI-dispatcher-affined; off-thread access throwsInvalidOperationExceptionper spec §8.10. EnumerationsAllContent()/Descendants()over the dock tree.DockContextsslots +DockHooksextension methods onRenderContextfor property hooks:UseDockHost,UseActivePaneKey,UseIsActivePane,UsePane,UseDockState,UseDockLayout. Each slot is a separateContext<T>so consumers only re-render on their specific slice change (selector-style scope per spec §5.3.11).DockPaneInforeadonly struct +DockPaneStateenum (Docked/Floating/AutoHidden/AutoHiddenExpanded/Hidden).DockSideenum,FloatingDockWindowrecord,DockLayoutSnapshotrecord for the wide-netUseDockLayouthook.PreviousContainerTracker—ConditionalWeakTable-backed bookkeeping for the "show panel where you left it" mechanic (§5.3.9). Bookkeeping decays with the pane reference.
- Spec 045 Phase 2 — Layout JSON v2 persistence.
DockLayoutSerializer.Save/Loadround-trips Reactor-native v2 JSON via a source-generatedJsonSerializerContext(AOT-clean: no reflection paths from JSON; no external schema URLs; no type-name instantiation). Security limits per §8.9: 1 MB max input size, depth 32; corruption / oversize / unknown-node-kind / missing-$schemainputs return aDockLayoutLoadResult.Fallback(never throw on the load path per §8.10). Invariant culture for numerics — verified by a save-de-DE/ load-en-USselftest (§8.8). Role-default-aware permission emission keeps the file small. 200-pane load latency regression guard wired in xUnit; the §8.1 50ms perf budget is enforced by the perf bench harness. (spec 045 §5.4, §8.9–8.11) - Spec 045 Phase 1 — Docking (vendor + wrap). First-class docking
surface arrives in Reactor via the new
Microsoft.UI.Reactor.Dockingnamespace, shipped from a separateMicrosoft.UI.Reactor.Docking.XamlNuGet package so apps that don't need docking don't pay for the vendored XAML dependency. Public API committed at P1 exit:DockManager : Element, theDockNodealgebra (DockSplit,DockTabGroup,DockableContent),TabPosition/DockTargetenums, and theIDockAdapter/IDockBehaviorobservation hooks (spec 045 §4.3). Apps register the element type with the reconciler at host construction viaDockingXamlInterop.Register(host.Reconciler)(same pattern asXamlInterop.Register); thereafter, anyDockManagerelement in the Reactor tree reconciles to a vendored WinUI.Dock control. Pane identity is viaDockableContent.Key(spec 042 keyed reconciliation — explicit, no Title-as-key fallback). Showcase sample lands atsamples/apps/dock-showcase/with six scenes mirroring the §4.7 review script. Smoke fixtureDocking_TwoPaneMountUpdateUnmountexercises mount → update → unmount in the AppTests harness; 27 unit tests cover the public API- upstream enum mapping. Phase 2 swaps the vendored implementation for a Reactor-native renderer with the same public surface. (spec 045 §4)
- WinUI.Dock vendored under
third_party/WinUI.Dock/. Snapshot ofqian-o/WinUI.Dock@2f5247f1(MIT) with four light edits documented inVENDORED.md: Uno code paths stripped, formatting normalized,[InternalsVisibleTo]added for the wrapper + tests, and the cross-window DnD bug sidestepped by restricting drag-out to a single manager in Phase 1 per spec §4.6. The runtime reference is removed at Phase 2 exit (§5.6); the source stays in the tree for license compliance and A/B regression checks against the native rewrite. (spec 045 §4.1, §4.2) Microsoft.UI.Reactor.Docking.Xamlis granted internal access toReactor.dll. The wrapper is a first-party Microsoft assembly that ships alongside the framework and callsReconciler.SetElementTag/DetachReactorState— same level of trust as the in-assemblyMicrosoft.UI.Reactor.Hosting.XamlInterop. (spec 045 §4.4)- Spec 042 Phase 1 — keyed-list reconciliation & ListView animation
groundwork. New internal
Microsoft.UI.Reactor.Core.Internal.ReactorRow/ReactorListStatecarry reference-typed identity rows inside an internally-ownedObservableCollection<ReactorRow>per mounted templated items control. The newKeyedListDiff.Applyhelper produces the React-style structural delta from the user's immutable list — lockstep prefix + suffix walks, single-op fast paths (append / prepend / remove-front / remove-back / insert-in-middle / remove-from-middle), and a bulk-replace bailout (>25% churn with ≥8 absolute ops) that returns to the legacyItemsSourceswap for correctness. (spec 042 §4) - Spec 042 Phase 2 —
IReactorKeyedidentity-on-data convention. 2-argumentwhere T : IReactorKeyedfactory overloads land forListView<T>/GridView<T>/FlipView<T>/LazyVStack<T>/LazyHStack<T>so thekeySelectorparameter can be omitted when the data type owns its identity (it defaults tot => t.Key). A newWithKey<T, TKey>(this T el, TKey item) where TKey : IReactorKeyedextension is the ergonomic peer for hand-built keyed children — both shapes route through the same Phase 1 incremental diff. ExplicitkeySelectorandWithKey(string)are unchanged for interop / third-party POCOs. Thesamples/TodoApp/TodoItemmodel adopts the convention as a worked example. (spec 042 §5) - Spec 042 Phase 4 + 5 — samples, gallery, and agent-kit references.
New
samples/apps/animated-list-demo/mini-app drives the templatedListView<Row>and a hand-builtFlexColumn(items.Select(...).WithKey(item))side-by-side over the same edits so the OC-delta andChildReconcilerpaths animate together.ReactorGallery'sListViewPagegains an "Animated edit (spec 042)"SampleCardwith the same toolbar.TodoApproutes add / delete / clear-completed through anAnimations.Animatewrapper that honoursUseReducedMotion(). NewComponent.UseReducedMotion()delegation exposes the existing context hook so user components can bypassAnimateunder WCAG 2.3.3. New skill references —plugins/reactor/skills/reactor-dsl/references/keyed-lists.mdfor the three keyed-list call sites and three.WithKeyoverloads, andplugins/reactor/skills/reactor-recipes/references/animated-list.mdfor the paste-readyAnimaterecipe + five common-mistakes sections. (spec 042 Phase 4 + 5) - Spec 042 perf gate — paired Reactor vs WinUI vanilla baseline.
New
StressPerf.VirtualList.WinUIis a hand-authored WinUI 3 twin toStressPerf.VirtualList.Reactor—ItemsRepeater+ObservableCollection<ListItem>with a recyclingIElementFactory, same row visual tree, same scroll tween, same edit policy (deterministic seed).tests/stress_perf/run_keyed_list_vs_winui.ps1drives a paired N-rep matrix that interleaves the two apps within each rep to neutralize DRR / thermal drift, computes per-cell medians, and writes a markdown verdict alongside per-rep frames CSVs for forensic re-analysis. First baseline attests/stress_perf/baselines/keyed-list-vs-winui-2026-05-17-104102/pins Reactor inside 0.3 % P50 of WinUI at production-realistic list sizes; the 10k-item P50 spread is unrelated to the diff path (gap doesn't move with edit pressure; Reactor's P95 / P99 tail is tighter than WinUI's). (spec 042 §10 perf gate) - Spec 042 Phase 6.3 — 10k virtualized scroll + edit stress scenario.
StressPerf.VirtualList.Reactorgained--with-edits/--edits-per-second Nflags that interleave deterministic insert / remove ops with the scroll tween (50/50 mix, seeded RNG, default 4 ops/sec). Catches future regressions in theItemsRepeaterkey-indexed factory path (ElementFactory<T>._mountedElements, rekeyed in Phase 1) that the steady-state scroll bench wouldn't see.ListItemSource.GenerateOne(id)added so synthesized items don't collide with the seed range.tests/stress_perf/README.mddocuments the new scenario, the command line, and the analysis rule ("if the gap to the edit-free baseline scales withcount, the rekey path has regressed"). (spec 042 Phase 6.3) - Spec 042 Phase 6.2 —
ReactorDiagnostics+ devtools dialog. New publicMicrosoft.UI.Reactor.Core.Diagnostics.ReactorDiagnosticscollector captures keyed-list bailouts (duplicate / null key) with per-control dedup viaConditionalWeakTableso a torn-down control doesn't leak.RecentKeyedListWarningsreturns a bounded snapshot (64 entries × 8 sample keys each, newest first).KeyedListDiff.Applygained acontrolInstanceparameter and now routes both bailout paths through a shared reporter —ILogger.LogWarningfires only on the first occurrence per (control, kind, sample-set) triple, while subsequent repeats bump an in-placeCount. NewDevtoolsMenuitem "Keyed-list diagnostics (N)" pops aContentDialoglisting each captured entry; behindReactorApp.DevtoolsEnabledso retail apps pay zero cost. Tests: 7 inReactorDiagnosticsTests, all 43 existingKeyedListDiffTestsstill pass. (spec 042 Phase 6.2) - Spec 042 Phase 6.1 —
REACTOR_DSL_001codefix. The existing missing-.WithKeyanalyzer now ships with a code fix that offers three insertion shapes ranked by discovery:.WithKey(item)when the lambda parameter implementsIReactorKeyed,.WithKey(item.Key)when the type has a publicKeyproperty, and.WithKey(item.Id)when the type has a publicIdproperty. The codefix opts out ofFixAllProviderbecause each lambda needs an independent semantic lookup of the parameter type. Covered by 6 new tests undertests/Reactor.Tests/AnalyzerTests/MissingWithKeyAnalyzerTests.cs. (spec 042 Phase 6.1; resolves the Q2 follow-up from spec §9) - Spec 042 Phase 3 — ambient
Animations.Animate(...)transaction. Wrapping a state mutation inAnimations.Animate(AnimationKind.Spring, () => setItems(...))propagates animation intent through anAsyncLocalambient so the resulting structural diff — insert / move / remove onListView<T>/GridView<T>/LazyVStack<T>/LazyHStack<T>and on hand-built keyed children insideFlexColumnetc. — picks up the kind without per-element modifiers. Setters snapshot the ambient synchronously at dispatch time so the eventual render observes the same intent even if the rerender hops a dispatcher;ReactorHost/ReactorHostControlre-push the snapshot around the reconcile pass.KeyedListDiff.Applytags insertedReactorRows with the kind so theContainerContentChangingrealize path can attach a per-container fade-up Composition animation; survivor moves drive an implicitOffsetanimation on the realized container (deferred one dispatcher turn so WinUI has reconciled positions before lookup).ChildReconcilerconsumes the same ambient — insert sites apply the same default enter, move sites attach an implicitOffsetanimation, andRemoveChildWithExitTransitionfabricates a fade-out exit when no per-element.Transition(...)is set. Per-element animation modifiers continue to win when declared; the ambient is purely a default for the transactional case. The two channels (transactional ambient onAsyncLocal, per-element curve scope onThreadStatic) remain independent — a leafTextBlock'sForegroundchange insideAnimate(.Spring)does not animate the foreground. New types:AnimationKind(public enum),Animations.Animate(...)(public),AmbientAnimation/AnimationAmbient/AnimationKindMap(internal glue). (spec 042 §6; matches Phase 3.2 / 3.3 / 3.4 / 3.5 of the task list, including the §9 Q3 / Q4 resolutions.)
- ListView / GridView / LazyVStack / LazyHStack now surface incremental
WinUI deltas for keyed updates. Previously, any
ItemCountchange rebuiltItemsSourcefromEnumerable.Range(...), which caused WinUI to tear down every realized container and replay the entrance theme transition for every visible row — the symptom captured in microsoft-ui-reactor#198. Phase 1 routes structural changes through a per-controlObservableCollection<ReactorRow>delta channel so only the affected containers animate. Hand-builtFlexColumn(items.Select(... .WithKey(item.Id)))already worked correctly and is now pinned by regression tests. (spec 042 §1, §4; closes microsoft-ui-reactor#198) ItemsRepeaterElementFactory<T>._mountedElementsis now keyed by the stableReactorRow.Keyinstead of by realized index. Insert-at-0 used to shift every realized entry's effective index by one, soRefreshRealizedItemslooked up the wrong element after every prepend. Keying by string makes the mapping reorder-stable. (spec 042 §4.4)
- Spec 039 — Property & event API scrub.
- New fluent extensions. Every callback property in the inventory has a
matching fluent on its element record — ~60 callbacks across §1–§9 of the
spec. Fluents drop the leading
On(soOnClick→.Click(handler)) because C# binds delegate-property invocation in preference to extension methods. Property names are unchanged; existingnew ButtonElement(…) { OnClick = … }syntax still compiles. Passingnullclears any previously-set handler. (spec 039 §0.1, §14 #1) - Named-style helpers.
.AccentButton(),.SubtleButton(),.TextLink()(overloaded acrossButtonElement,DropDownButtonElement,SplitButtonElement,ToggleSplitButtonElement, andHyperlinkButtonElementwhere applicable); InfoBar severity helpers.Informational()/.Success()/.Warning()/.Error();Card(child)factory with theme-aware background and stroke; type-ramp factoriesTitle/Subtitle/Body/BodyStrong/BodyLargemapping to the WinUI 3*TextBlockStyleresources. (spec 039 §2, §17) - New events exposed.
CalendarView.OnSelectedDatesChanged;Frame.OnNavigated/OnNavigating/OnNavigationFailed;ScrollView.OnViewChanged;Popup.OnOpened;WebView2.OnWebMessageReceived/OnCoreWebView2Initialized;MediaPlayerElement.OnMediaOpened/OnMediaEnded/OnMediaFailed;ContentDialog.OnOpened;Image.OnImageOpened/OnImageFailed;ComboBox.OnDropDownOpened/OnDropDownClosed;DataGrid.OnSelectionChanged; universal multi-selectOnSelectionChangedonListView/GridView/ListBox(withIReadOnlyList<int>snapshot) and the typed peersItemsView<T>/TemplatedListView<T>/TemplatedGridView<T>(withIReadOnlyList<T>snapshot). TreeView multi-select is intentionally deferred. (spec 039 §3, §5.8, §14 #3) - New init properties. Common-property gaps closed across the text, input, date/time, progress/layout/navigation, collection/dialog, and media/shape families (Phase 4 / Phase 5 of the implementation task list). See spec 039 §14 #4 for the inventory.
- New fluent extensions. Every callback property in the inventory has a
matching fluent on its element record — ~60 callbacks across §1–§9 of the
spec. Fluents drop the leading
-
ScrollView()factory now mounts the modernMicrosoft.UI.Xaml.Controls.ScrollView; the legacyMicrosoft.UI.Xaml.Controls.ScrollViewermapping moved to a newScrollViewer()factory. Reactor'sScrollView()previously named the ergonomic Reactor wrapper but mounted the classicScrollViewer, leaving the new control's capabilities (ContentOrientation, anchor ratios, theScrolling*enum surface) unreachable from the DSL. Migration: rename existingScrollView(...)call sites toScrollViewer(...)when you want to keep the classic control (and the existingOnViewChanged/IsIntermediateevent shape, the parallax animation infrastructure, andScrollViewer.SetXxxattached-property patterns). Reach for the newScrollView(...)factory when you want the modern control. The element records follow the same rename:ScrollViewElement→ScrollViewerElementfor the legacy element;ScrollViewElementis now the new control's record. (Issue #348) -
.Margin(double, double)and.Padding(double, double)parameter order swapped to match CSS shorthand convention. Was(horizontal, vertical); now(vertical, horizontal). This aligns with CSS —padding: 16px 14px;means top/bottom = 16, left/right = 14, vertical first. Any existing positional 2-arg call site in the repo has been migrated to the named-arg form (.Margin(horizontal: 16, vertical: 8)) which preserves layout regardless of parameter order; recommend the same for external callers. Pre-1.0 breaking change is intentional — the original ordering was a layout-rotation footgun for agents and humans with CSS muscle memory. (spec 038 §3 — feedback from 525-run corpus / WPF-vs-CSS mental model)
mur check --trace <path>— append one JSONL row per parsed diagnostic to<path>(in addition to stdout) for offline mining. Schema:{ts, code, severity, file, line, col, msg, receiver_type?, member?, mode}. Source code text is never written; absolute paths outside the project root are redacted to<external>. (spec 038 §0.3)- Tier-2 Roslyn semantic suggester for
mur check. Covers CS1061, CS0103, CS0117, CS1503, CS7036 againstMicrosoft.UI.Reactor.*symbols; emits→ try: <text> // [<evidence>]on the diagnostic line above the per-code confidence threshold (default 0.75). Tier-1 analyzer-ID hints still win ties. (spec 038 §5, §1.1–§1.6) - Per-code emit thresholds for the Tier-2 SymbolSuggester
(
src/Reactor.Cli/Check/Suggesters/Thresholds.cs) calibrated against the spec-037 50-run corpus. CS1061 raised to 0.80 (the structural-rewrite fixes in the corpus would otherwise risk false positives); CS0103 / CS0117 / CS1503 / CS7036 held at 0.75 default. Tuning harness lives intests/Reactor.Tests/CheckCommandTests/Tuning/; first run snapshot atdocs/specs/tasks/038-tuning-reports/2026-05-10-50run.md. (spec 038 §1.8, Data Checkpoint B) - EC1 5×N eval (2026-05-10):
reactor-kanban-mur-checkbeats baseline on cost mean (−24%), cost median (−33%), and wall-time variance (CV 24% vs 81%); paired analysis wins 4 of 5 rounds.reactor-calc-mur-checkregresses (+21% cost) because the suggester's per-invocation overhead (~5–8s) does not amortize on ~150 LoC projects with no API exploration surface to skip. Finding captured as a new spec 038 §11 risk + §14 open question on a project-size / diagnostic-count gate; merge tomainpending product decision on path. No code change in this entry — eval result + spec doc updates only. MUR_TELEMETRY=1opt-in: appends(code, suggester, confidence, evidence_short)per emitted suggestion to~/.mur/telemetry/<yyyy-mm-dd>.jsonl. Local-first, scoped to the active project; no source code, file paths, or machine identifiers logged. (spec 038 §10, §1.7)mur check --suggest-threshold <N>— gate Tier-2 suggestions by per-invocation unique CS-prefixed diagnostic count. Default 3, set 0 to always emit. Resolution of the EC1 calc-vs-kanban split: small builds (1–2 errors) skip the ~5–8 s Tier-2 setup the agent doesn't need; larger structural failures still get suggestions. Counts the same dedup keyEmitDiagnosticsuses. (spec 038 §11 risk row, §14 #8)- Data Checkpoint C (spec 038 / spec 037): 525-pair mining corpus mirrored
into
docs/specs/tasks/038-tuning-reports/2026-05-11-525run-source/(1,027 fixes / 1,233 ranker rows / 104 clusters fromgpt-5.5). Analysis in2026-05-11-525run.md. Cross-agent reproducibility bar still open — a second-agent drop is required before Phase-3 rule PRs. Top Phase-3 targets surfaced: CS0117/Theme*Background → SolidBackground, CS1061/*ElementWinUI-name → Reactor-shortcut family, CS1955/GridSize missing-parens-on-factory. Tier-2 per-code thresholds held at current values; gate threshold (3) empirically defensible at 28.7% emit rate. No code change in this entry — calibration + docs only. (spec 038 §1.8, Data Checkpoint C) mur checkPhase 2 — MSBuild passthrough + deterministic pre-emit ranker.mur check [<path>] [mur-flags] [-- <msbuild-args>]— anything after a bare--is forwarded verbatim todotnet build.murinjects--nologo,-v:m, and-p:Platform={host arch}only if the same flag is not named in the passthrough section (detection by flag name, not value). When--traceis on, the trace records the effectivedotnet buildargv as akind: "command"header row so replays are bit-faithful. New mode flags:--strict(promote warnings to errors),--final(emit every diagnostic — pre-merge sweep),--quiet(errors only).--emit-threshold <float>overrides the per-mode ranker default (0.6 iteration / 0.0 final). Pre-emit ranker (src/Reactor.Cli/Check/Ranker/PolicyTable.cs) suppresses noise mid-iteration (CS1591, CS0168, IDE0xxx, NU1701/NU1605, MSB3245/MSB3270/MSB3277, CS8600–CS8625 nullable warnings) while always emitting errors. (spec 038 §8, Phase 2.1–2.3)tools/Reactor.MurCheckGuardrail— offline guardrail that audits a pair of--tracefiles (one iteration, one--final) against PolicyTable's universal-error floor invariant. Fails CI if a future policy-table edit would let a real build error get suppressed mid-iteration. The "universal floor" rule (Error severity always scores 1.0 regardless of code family) makes the invariant hold by construction today; the guardrail is the regression test that catches accidental violations. (spec 038 §8 Phase 2.4)plugins/reactor/skills/reactor-build-and-check/SKILL.mdupdated for the iteration /--finalworkflow. EC2 measured 0/10 production value on the strong "explicit done gate" framing across 6 variant runs, so the framing was softened post-batch:--finalis now documented as an optional pre-merge sweep (for human review / CI ship-readiness gates), explicitly NOT a task-completion requirement. SKILL anchor wording: "Whenmur checkexits 0, you are done." Same wording in the legacy rootSKILL.md. (spec 038 §8 Phase 2.5)- Phase-2.x — gate-input regression fix in
CheckCommand.ShouldEmitSuggestions. The initial Phase-2 implementation counted the post-rankeremittablelist when deciding whether to run the Tier-2 suggester. EC2 (3-round preview) measured Tier-2 firing collapse from EC1's 80% to 0% on kanban-mur because nullable warnings (CS8602/etc) were filtered out of the emittable list before the gate-count, closing the gate on builds EC1 had left open. Fixed by counting the full parseddiagnosticslist — the gate measures build complexity, not stdout visibility. Regression testRankerTests.Suggest_gate_counts_full_parsed_list_not_post_ranker_emittablelocks the behavior; fails the build if the bug is reintroduced. (spec 038 §14 #8) - Phase-2.x — EC2 5×N PASS by median (2026-05-11).
reactor-calc-mur-checkbeats base on every metric (cost −5.1%, tokens −5.8%, turns −5.1%, wall −7.9%; variance 1.9× tighter).reactor-kanban-mur-checkat cost median parity ($3.30 = $3.30); mean dragged to +5.7% by R2 outlier (n=5, R2-excluded mean is −3.3%). First-build OK 5/5 on both variant arms.--finalinvocation 0/10 across both projects (SKILL framing doing its job). Tier-2 firing 0/10 — gate correctly inhibits on small-batch iteration patterns; closing the kanban token gap is Phase-3's scope (rules > fuzzy match). Criterion-2 guardrail audit deferred to a harness retrofit (post-runmur check --finalagainst the final workspace state to generate the iter+final trace pair the guardrail tool audits). Phase 2 cleared to merge tomain. (spec 038 §1.8 EC2 acceptance, §8, §11) - Phase 3.1 / 3.1a — Tier-3 rule infrastructure scaffolded. New surface
under
src/Reactor.Cli/Check/Rules/:IRulePatterncontract (Name,Provenance,DiagnosticCodes,DeclaredTargets,TryMatch),RuleContext+RuleSuggestionrecords,RuleRegistry(reflection discovery ofIRulePatternimplementations inReactor.Cli,Defaultsingleton, dedup on Name collisions,BestMatchwith disable list and self-disable-on-unresolved-target reporting,Statusesfor--list-rules), andRuleSymbolResolver(per-CSharpCompilationcached symbol lookup viaConditionalWeakTable— spec §3.1a's contract that rules never string-matchMemberAccess.Name.ValueText). New CLI flags--disable-rule <Name>(repeatable, warns on unknown names) and--list-rules(short-circuitsdotnet build, prints the name/provenance/status table, exits 0).SuggesterOrchestratorruns rules alongside Tier-2; spec §6 "rule wins over Tier-2 fuzzy match" preserved; rules can match diagnostic codes outside Tier-2'sSupportedCodesso CS1955 / Theme-lookup rules are unblocked.tests/Reactor.Tests/CheckCommandTests/Rules/RuleTargetResolutionTests.csis the §3.1a CI gate — instantiates every registered rule against a live ReactorCompilation(full assembly references, the inverse ofTestCompilation.Create) and asserts every declared target resolves. Passes vacuously today; becomes load-bearing the moment the first rule lands. 35 new unit tests covering contract shape, registry discovery and edge cases (duplicates, throwing rules, self-disable), resolver cache identity, orchestrator rule-vs-Tier-2 precedence, and ArgsParser round-trip. Phase-3 rule PRs themselves remain blocked on the second-agent corpus drop (cross-agent reproducibility bar #2 of the Validation Gate). (spec 038 §3.1 + §3.1a) - Spec 038 Phase-3 vocab table at
docs/specs/tasks/038-vocab-table.csv(§3.0 prerequisite for any Class-B rule PR). 20 rows covering WPF / Silverlight / WinUI 2 / WinUI 3 → Reactor vocabulary translations, seeded from the 525-run report's Phase-3 priority targets plus desk research againstskills/reactor.api.txt. (spec 038 §3.0) GridSizePxRenameRule(Class-A induced): CS0117 onMicrosoft.UI.Reactor.GridSizewhere the missing member isPixel,Pixels, orFixed— the WPF / WinUI / legacy-XAML names — suggestsGridSize.Px(...)with the same numeric argument. Cross-agent reproducibility STRONG: 5 events in gpt-5.5 + 4 events in sonnet-4.6 = 9 events combined, 100% rewrite target isPx(...)on every row. 5 unit tests (3 positive covering all three legacy names, 2 negative). Bar #5 (independent reviewer signoff) pending. (spec 038 §3.2, §6 Class A, Validation Gate bar #2)TextBlockStyleHintRule(Class-A induced): CS1061 or CS0117 onMicrosoft.UI.Reactor.Core.TextBlockElementwhere the missing-member name isStyle— suggests Reactor's fluent text helpers (.FontSize, .Bold, .SemiBold, .Italic, .Foreground) directly on the element. Reactor doesn't expose a Style member; the WPF/WinUI mental model reaches forTextBlock.Style = SomeTitleStylestyle-resource attachment. Cross-agent reproducibility STRONG-after-collapse: 2 events gpt-5.5 (fluent.Style(...)shape) + 3 events sonnet-4.6 (recordwith { Style = ... }shape) = 5 events combined; the rule covers BOTH syntactic shapes in one rewrite. 5 unit tests. (spec 038 §3.2, §6 Class A)ThemeBackgroundSuffixRule(previously Class-B, promoted to Class-A by the cross-agent audit). Rule shape unchanged; the file-header comment now records the audit's bar #2 evidence (16+11=27 events across both corpora on the (CS0117, Theme, other) key) alongside the original vocab-table-citation justification. The Class-A / Class-B distinction is about evidence type, not rule shape; this rule was authored from the vocab table first and the corpus later confirmed it. (spec 038 §6 Class A re-classification)- Critical fix —
CompilationLoadernow resolvesProjectReferenceoutputs. Without this, every Tier-3 rule self-disabled on realmur checkinvocations against Reactor apps: the loader only parsedproject.assets.json'stargetssection (NuGet packages), so Reactor itself (a project reference for every sample app) was invisible. New code walkslibraries.<id>entries withtype=project, reads the referenced csproj's<AssemblyName>(or falls back to the basename), and locates the most-recently-built matching.dllunder that project'sbin/subtree. Regression locked byCompilationLoaderTests.Resolves_ProjectReference_built_dll_from_project_assets_json. Unit tests passed before this fix because they use synthetic in-memory compilations; end-to-end smoke againstsamples/apps/wordpuzzlewas what surfaced the silent failure mode. (spec 038 §5 + §6 + §3.1a) - Suggest-gate carve-out for Tier-3 rules. The gate
(
--suggest-threshold, default 3 unique CS-prefixed diagnostics) was wrapping the entire suggester block — when closed, neither Tier-2 nor rules ran. The gate exists to suppress Tier-2 fuzzy match's noise on small builds; rules are precision-anchored (Roslyn ISymbol binding) and shouldn't be subject to that calibration.SuggesterOrchestratornow takes atier2Enabledbool;CheckCommand.Runalways builds the orchestrator (when the compilation loads) and passes the gate result in. Tier-3 rules always run when their diagnostic code surfaces; Tier-2 stays gated. Two new tests lock this down. This is the EC2 watch-item ("Phase-3 rules are the right lever — not Phase-2.x gate tuning") finally addressed in code. (spec 038 §11 + §14 #8, EC2 watch-item) - First Class-A induced rule:
GridSizeFactoryParensRule(CS1955 onMicrosoft.UI.Reactor.GridSize.Auto()→ suggestGridSize.Auto— i.e. drop the parens, sinceAutois a static property and onlyStar(double)/Px(double)are methods). Cross-agent reproducibility STRONG: 146 events combined across the gpt-5.5 525-run (110 events) and claude-sonnet-4.6 525-run (36 events) corpora, top-frequency cluster in both at 10.7% / 9.8% of fixes respectively. Both corpora are unanimously aboutAuto— every captured row'sdiag.memberfield is exactly "Auto". First cross-tier rule: CS1955 is outside Tier-2'sSupportedCodes, so the orchestrator'sRulesCoverCodepath is now load-bearing for at least one diagnostic code. 5 unit tests (3 positive fixtures from distinct cross-corpusrun_ids, 2 negative — lookalikeAcme.GridSizein a user namespace plus a synthetic non-CS1955 diag gate test). Validation Gate cleared on bars #1–#4 + #6; bar #5 (independent reviewer signoff) pending. Cross-agent audit recorded atdocs/specs/tasks/038-tuning-reports/2026-05-11-cross-agent-audit.md. (spec 038 §3.2, §6 Class A, Validation Gate bar #2) - §3.1a per-rule performance bound test:
RulePerformanceTests.BestMatch_median_under_per_rule_budget([Trait("Category","Perf")]) assertsRuleRegistry.Default.BestMatchmedian across 1000 iters on the canonical CS1061-on-ButtonElement.OnClickfixture stays under0.5 × rule_count × 4ms (4× CI slack matchesCompilationLoaderTestsconvention). Was deferred until the first rule landed; now load-bearing for the rule set. (spec 038 §3.1a) - Cross-agent reproducibility audit at
docs/specs/tasks/038-tuning-reports/2026-05-11-cross-agent-audit.mdcomparing gpt-5.5 and claude-sonnet-4.6 525-run corpora on receiver-typed clusters. Verdicts: three Class-A targets STRONG (CS1955/GridSize/other 146 events; CS0117/Theme/other 27; CS0117/GridSize/renamed_member 7); two more strong after rule-design collapse (TextBlockElement member rename- TemplatedListViewElement generalized over
<T>); one striking gpt-5.5-only signal (CS1955/GridElement, 29 events, zero in sonnet — deferred to a third corpus drop). Closes Data Checkpoint C's cross-agent-reproducibility gap. (spec 038 §3.0, Validation Gate bar #2)
- TemplatedListViewElement generalized over
- First three Class-B vocabulary-translation rules:
ThemeBackgroundSuffixRule(CS0117 onThemewith member ending inBackground→Theme.SolidBackground, cluster C0019, 16 events);AlignmentShortcutRule(CS1061 on Reactor*Elementreceivers forHorizontalAlignment/VerticalAlignment→.HAlign(...)/.VAlign(...), cluster C0017 + adjacent ≈ 22 events); andButtonOnClickFactoryMoveRule(CS1061 onButtonElement.OnClick→Button(..., onClick: ...)factory named-arg, explicitly naming.OnTappedas the wrong sibling to keep agents from reaching for the gesture event). Both bind target types viaRuleSymbolResolver(no string matching); the rule-target resolution CI gate is now load-bearing. 10 new unit tests (positive fixtures cite their sourcerun_ids from the 525-run corpus; hand-authored extensions are tagged[Trait("Origin", "VocabHandAuthored")]). PRs remain blocked on Validation Gate bar #5 (independent reviewer signoff) — the artifacts are "ready for review", not "ready to merge". (spec 038 §3.2, §6 Class B) .Margin(...)and.Padding(...)per-side overloads now default unspecified sides to0.0. Enables agent-intuitive call shapes like.Margin(top: 12),.Padding(left: 8, right: 8)that previously failed to compile (CS7036: no matching overload). 525-run corpus shows 198 build failures from agents writing this exact shape against the prior all-required signature — far and away the highest-frequency failure-driver in the drop. Eliminating it is a single-line code edit per overload but a large agent-productivity unlock.Reactor.Testsadds CSS-ordering + per-side + positional-overload regression tests. (spec 038 §3 follow-up — surfaced during Phase-3 rule authoring)- Cheatsheet in
plugins/reactor/skills/reactor-getting-started/SKILL.mdnow shows the named-argButton("Save", onClick: handler)form alongside the positional one, with an explicit anti-pattern comment naming.OnClick(...)and.OnTapped(...)as the wrong fixes for click intent. The cheatsheet's.OnTapped((s, e) => ...)example is now anchored to non-Button surfaces (Border / Image / ScrollView) with a back-reference to the Controls section — the prior parenthetical Button carve-out was easy to miss mid-build. (spec 038 §3 — agent-facing skill updates) - Spec 038 EC3-final watch-item:
rule_firedtrace event. When a Tier-3 rule attaches a suggestion to a diagnostic,mur check --tracenow writes one structured row per fire:{kind: "rule_fired", rule, code, confidence, evidence, file, line, mode}. Per-rule firing-rate audits collapse from multi-step content scans againstevents.jsonlagent tool outputs to a 1-linejqover the trace file. Tier-2 suggestions deliberately do not emit this row — Tier-2 firing rates are visible via the opt-inMUR_TELEMETRY=1channel. (spec 038 §0.3, EC3-final watch-item) - Spec 038 §3.1a residual: trace-channel structured warning hook for
self-disabled rules.
TraceWriter.WriteRuleSelfDisabled(rule, target)emits{kind: "rule_self_disabled", rule, unresolved_target, mode}.SuggesterOrchestratorthreads an optionalonRuleSelfDisabledcallback through toRuleRegistry.BestMatch;CheckCommand.Runwires it to the active trace writer when--trace <path>is set, dedup'd per-invocation per-rule. Stdout stays clean — agents don't read trace files, but maintainers see "rule X disabled because target Y didn't resolve" the moment a Reactor minor release breaks something. (spec 038 §3.1a) - EC1 re-run with the diagnostic-count gate (2026-05-11): both arms PASS.
reactor-calc-mur-checkcost −4% mean (was +21% in the prior batch);reactor-kanban-mur-checkcost −33% mean / −39% median (was −24% mean — preserved and grew). First-build OK 5/5 both variant arms. Phase 1 acceptance bar met; Phase 1 cleared to merge tomain. Watch-item carried into Phase 2: kanban CV widened (24% prior → 54%) because one of five runs hit 0 firings and took the long-tail base path — gate behavior is path-dependent on the agent's exploration order. Below the resolution threshold for a Phase-1 blocker; Phase 2 telemetry should track per-run firing counts. (spec 038 §1.8 EC1 acceptance, §11 risk row, §14 #8) WindowSpec,ReactorWindow,WindowKey,WindowStartPosition,PresenterKind,WindowState,WindowIcon,WindowDipSizeChangedEventArgs,WindowClosingEventArgs,ReactorAppContext— first-class Window primitive promoted out of internal hosting wiring.ReactorApp.Run(Action<ReactorAppContext>)is the new multi-window startup surface; the existingRun<TRoot>overload is preserved as a thin wrapper. (spec 036 §3, §4)ReactorApp.OpenWindow,Windows,PrimaryWindow,FindWindow,WindowOpened/WindowClosed,Exit,ShutdownPolicy,UIDispatcher— process-wide window topology. (spec 036 §4.3, §6)- Per-window DPI awareness —
ReactorWindow.Dpi,DipScale,DpiChanged; WindowMessageMonitor (SetWindowSubclass) for WM_DPICHANGED and WM_GETMINMAXINFO; DIP→physical conversion in initial size,SetSize,SetPosition. Min/max constraints flow through WM_GETMINMAXINFO so dragging across a DPI boundary respects spec'd minimums. (spec 036 §5) RenderContext.UseDpi(), parameterlessUseWindowSize(),UseBreakpoint(double). (spec 036 §5.2)ReactorWindow.Activated,Deactivated,SizeChanged,StateChanged,Closing,Closedevents with UI-thread synchronous dispatch.ClosingrunsUseClosingGuardpredicates first then subscribers; any false cancels. (spec 036 §6.3, §7)RenderContext.UseWindow(),UseWindowState(),UseIsActive(),UseClosingGuard(Func<bool>). Tray-flyout fallback semantics match spec §7.1 (null/Normal/true/no-op). (spec 036 §7)RenderContext.UseOpenWindow(WindowKey, WindowSpec, Func<Component>)Component.UseOpenWindowmirror — open or reuse a secondary window keyed byWindowKey. Identity-stable across re-renders; spec changes flow throughReactorWindow.Update; parent unmount does not auto-close the child. (spec 036 §4.3 / §15.6)
ReactorWindow.PersistedScope— per-windowCore.WindowPersistedScope, disposed when the window closes.RenderContext.UsePersisted(_, _, PersistedScope.Window)now resolves to this per-window store, so two windows of the same component class hold independent persisted state. (spec 036 §3.4 / §4.4 — closes spec 033 §7.5.)ShutdownPolicy.OnPrimaryWindowClosedexits when the primary window closes (not just when the snapshot empties);OnLastSurfaceClosedconsiders tray icons (Phase 8 fills the registry). The default zero-window startup-callback path now exits underOnLastSurfaceClosedtoo when no tray icons were opened. (spec 036 §6.2)IWindowPersistenceStore,PackagedSettingsStore,JsonFileStore, andReactorApp.WindowPersistenceStore— pluggable per-window placement persistence. Default auto-detect picks the WinRT settings store for packaged apps and a hand-rolled, AOT-safe JSON file store (1 MB cap, atomic write-then-rename, base64-per-id) for unpackaged apps.WindowSpec.PersistenceIdopts in; placement saves on close and restores on first show viaWindowPlacementCodecwith a monitor- layout fingerprint borrowed fromWinUIEx.WindowManager. (spec 036 §8)WindowSpec.Backdropis now seeded as a window-level default throughBackdropApplier.SetWindowDefault, so the first frame paints the declared material even when the root component tree carries noBackdropChoicemodifier. Tree-level modifiers still win on subsequent renders. (spec 036 §3.3)- Owned-window relationship via
WindowSpec.Owner— applies the Win32GWLP_HWNDPARENTslot at construction time and force-hides the owned window from the taskbar / Alt-Tab. Owner-close cascades to owned children withWindowCloseReason.OwnerClosed; if any owned guard cancels, the owner-close cancels too. (spec 036 §9) ReactorWindow.Progress(TaskbarProgress, withTaskbarProgressStateenum: None / Indeterminate / Normal / Paused / Error) andReactorWindow.Overlay(TaskbarOverlaywithIcon/AccessibleDescription). Both lazy-initialize theITaskbarList3COM wrapper throughTaskbarComSingletonso apps that never touch the shell surface pay no startup cost. (spec 036 §11.1 / §11.2)ReactorWindow.SetThumbnailToolbar(IReadOnlyList<ThumbnailToolbarButton>)/ClearThumbnailToolbar()— up to seven buttons; first call usesThumbBarAddButtons, later calls useThumbBarUpdateButtons. Validation rejects > 7, duplicate Ids, empty Ids, null OnClick. Click dispatch hooks WM_COMMAND inWindowMessageMonitor. HICONs are released onReactorWindow.Dispose. (spec 036 §11.5)JumpList,JumpListItem,JumpListItemKind— process-scoped jump list. Packaged path usesWindows.UI.StartScreen.JumpList; unpackaged falls back to a hand-rolledICustomDestinationListwrapper (JumpListComInterop) gated by runtimePackage.Currentdetection through the newPackageRuntimehelper.AppUserModelId,ShowRecent,ShowFrequentare settable.JumpListItem.ForUri(...)factory is the recommended way to build entries — pairs withLaunchActivation.TryResolve<TRoute>(map)for the navigation handoff. (spec 036 §11.3 / §11.6)LaunchActivationparsing —OnLaunchednow readsMicrosoft.Windows.AppLifecycle.AppInstance.GetActivatedEventArgsfor File / Protocol / Toast activations and falls back to the WinUILaunchActivatedEventArgs.Arguments+Environment.GetCommandLineArgsfor jump-list / tray re-launches.LaunchActivation.TryResolve<TRoute>bridges the launch argument string into the existingDeepLinkMap<TRoute>so jump-list / tray entries become a one-liner navigation handoff. (spec 036 §11.6, implementation-time addition)ReactorTrayIcon+TrayIconSpec— system-tray icon as a peer ofReactorWindow.ReactorApp.OpenTrayIcon,TrayIconssnapshot,FindTrayIcon,TrayIconOpened/TrayIconClosedevents; mirrored onReactorAppContext. Hidden message-only window (TrayHiddenWindow) routesShell_NotifyIconcallbacks back to the UI thread under NOTIFYICON_VERSION_4 semantics.Click,DoubleClick,RightClickevents fire on the UI thread.Update(spec)diffs icon / tooltip / visibility;Close/Disposeremoves the icon and unregisters fromReactorApp.TrayIcons.OnLastSurfaceClosednow reads the realTrayIconCountand re-evaluates on tray close so a tray-only app exits cleanly when the final icon goes away. (spec 036 §11.4)RenderContext.UseTrayIcon(TrayIconSpec)+Component.UseTrayIconmirror — opens (or reuses by key) a tray icon scoped to the calling component. The trailingUseEffectcleanup closes the icon on unmount; spec changes flow throughUpdatevia a record-keyedUseEffect. (spec 036 §11.4)- Seven live-shell selftest fixtures under
tests/Reactor.AppTests.Host/SelfTest/Fixtures/WindowModelFixtures.cs:WindowModel_LifecycleEvents,_ClosingEventCancels,_TaskbarProgressLiveCom,_ThumbnailToolbarLiveCom,_PersistedScopeIsolated,_TrayIconRoundTrip,_UseOpenWindowReusesByKey. They exercise the public surface against real HWND /ITaskbarList3/Shell_NotifyIconCOM, opening secondaryReactorWindows throughReactorApp.OpenWindowand cleaning up underShutdownPolicy.Explicitso they don't kill the host harness. 33/33 assertions pass alongside the full 2314-assert selftest matrix. (spec 036 §0.5 / §0.6 / §11) - Devtools
windows.list / windows.activate / windows.close / windows.openMCP tools (spec 036 §10).windows.listreturns id, key, title, DIP size, DPI, state, isMain — driven by a newWindowRegistry.Attach(ReactorWindow, ...)overload that retains the back-reference.windows.openis gated by the same component allowlist asswitchComponentso loopback callers can't spawn arbitrary types;windows.closehonorsUseClosingGuardand surfacescancelled: trueinstead of hanging. The devtoolsWindowRegistryis now driven fromReactorApp.WindowOpened / WindowClosedevents so secondary windows opened viaOpenWindoware tracked too. CLI andskills/devtools.mdplumbed. Microsoft.UI.Reactor.Hooks.UseMemoCells/UseMemoCellsByKey/UseMemoCellsByIndex— cell-level memoization hooks (extension methods onRenderContext, plus matchingComponentshims) for high-frequency list/grid bodies. Cells whose item value (and declared deps) haven't changed since the previous render are reused by reference; the reconciler short-circuits onReferenceEqualsand skips diffing entirely. (spec 034 §C)REACTOR_HOOKS_007analyzer + codefix — warns when aUseMemoCellsbuilder lambda closes over a value that isn't declared in theparams depslist, which would silently render stale. The codefix appends the missing capture to the deps slot. Indirect captures through helper methods are a documented blind spot. (spec 034 §C)- "Memoizing list cells" section in
docs/guide/advanced.mdcovering the three overloads, when each is the right hammer, the gen2 trade-off, and the analyzer-as-safety-net story. (spec 034 §C) tests/stress_perf/StressPerf.ReactorOptimized— sibling bench variant that demonstrates the spec-034 §B direct-record-initializer idiom for inner-loop cell construction. The naiveStressPerf.Reactorvariant stays unchanged and remains the framework-level baseline; the new optimized sibling is the reference implementation of the perf-tips skill. Wired intorun_stocks_grid_baseline.ps1,run_bench_aot_publish.sh,run_benchmark.sh, andrun_sweep_arm64.ps1. (spec 034 §B)- "Hot loops" section in
docs/guide/advanced.mddocumenting when to reach for direct record initializers, the trade-offs vs the fluent chain, and a side-by-side worked example. Source template atdocs/_pipeline/templates/advanced.md.dt. (spec 034 §B) Expr(Func<Element?>)factory inMicrosoft.UI.Reactor.Factoriesfor inline block-expression bodies inside a DSL tree, removing the((Func<Element?>)(() => …))()cast ceremony. Pure composition — no hooks, no memoization, no reconciler boundary. (spec 033 §5)IPersistedStateScopeinterface,PersistedScopeenum (Window/Application),ApplicationPersistedScope(process-wide singleton atApplicationPersistedScope.Default, capacity 4096), andWindowPersistedScope(per-host instance, capacity 1024). All backed by an internalLruCache<TKey,TValue>. NewRenderContext.UsePersisted<T>(key, initial, PersistedScope)overload makes the scope explicit. (spec 033 §2)Microsoft.UI.Reactor.Factories.RenderEachTime(Func<RenderContext, Element>)— explicit factory for "inline component with own hooks that re-renders every parent render". Replaces the soft-deprecatedFunc(...)for the rare cases that genuinely want always-re-render semantics. (spec 033 §4)Microsoft.UI.Reactor.GridSizevalue type withAuto/Star(weight)/Px(pixels)smart constructors, implicit conversion toMicrosoft.UI.Xaml.GridLength, and a strict invariant-culture string parser (Parse). New typedGrid(GridSize[], GridSize[], …)factory overload. (spec 033 §1)samples/InteropFirst— XAML-window-hosts-Reactor demonstration with sharedObservableCollection<Order>, sharedICommands bridged throughCommandInterop.FromCommand, and sharedApp.xamlbrush resources flowing through props into a ReactorComponent<TProps>. (spec 033 §7)BackdropKindenum and.Backdrop(BackdropKind)/.Backdrop(Func<SystemBackdrop?>)modifier on the root tree for declarative Mica / Acrylic on Reactor-hosted windows.ReactorHostapplies the modifier at the end of each reconcile pass and resets the window's backdrop on dispose;ReactorHostControlthat does not own its window no-ops with a one-shot debug log. (spec 033 §6)ElementRef<T>typed-ref wrapper (Microsoft.UI.Reactor.Input),UseElementRef<T>()hook (Microsoft.UI.Reactor.Hooks), and a strongly-typed.Ref<T,TElement>(...)modifier overload. The typed surface removes the(Button)ref.Currentcast at consumers and adds a DEBUG-only assertion when a typed ref is bound to an element of the wrong concrete type. AOT-safe and reflection-free at the public surface. (spec 033 §3)Component.UsePersisted<T>(key, initial, PersistedScope)three-arg overload so component subclasses can declare the persisted-state scope (Window vs Application) explicitly at the call site, matching theRenderContext.UsePersistedoverload added earlier. (spec 033 §2)
- Spec 034 — Element allocation reduction. Three independent
allocation cuts in one PR: bucketed
ElementModifiers(transparent storage shim, ~−11% bytes/tick on the 4,900-cell stress grid), direct-record-initializer idiom for inner cell loops (~−60% bytes per cell), andUseMemoCellscell-level memoization. Verified at PR-close on ARM64 Release with full ETW Present-tracking across 10/20/50/100% mutation, all eight stress_perf variants: ReactorOptimized at 10% mutation reaches 17.1 Effective Refresh/s — within noise of DirectX (17.2) and Wpf (17.9), and +66% over naive Reactor (10.3). Reconcile-time win on the same A/B: −76% at 10% (32.5 ms → 7.9 ms), −61% at 20%, −31% at 50%, −12% at 100% — memo's win tracks the partial-reuse opportunity exactly as predicted. DirectX runs away at saturation (50%+) — no allocating framework can keep up there. Component A in isolation (naive Reactor pre-shim vs post-shim, same source, no app-code changes) shows renders/sec within run-to-run noise at 20/50/100% — its win is allocation-side, not renders-side, on this hardware. Seedocs/specs/034-element-allocation-reduction.md§ "Verified close-out — 2026-05-03" for the full eight-variant matrix and reads. (spec 034) ElementModifiersnow stores layout and visual fields inLayoutModifiers/VisualModifierssub-records. Existing call sites are unaffected — public properties (Padding,Margin,Foreground,Background, …) shim through to the appropriate bucket on read and write. Perf-critical inner loops may construct buckets directly via the newLayout = …/Visual = …initializer slots to avoid a fatElementModifiersclone per fluent step. (spec 034 §A)PersistedStateCacherewritten over an LRU cache with eviction-on-full semantics. The previous "refuse new keys when 4096 entries are present" policy is replaced — later, hotter keys are no longer starved by the first 4096 keys ever recorded. Application-scope registers anWindows.System.MemoryManager.AppMemoryUsageIncreasedhandler and trims to 25% of capacity when the OS reportsOverLimit/High. Best-effort: hosting models that do not expose the event log a notice and carry on. Key validation now requires non-empty keys ≤ 256 chars. (spec 033 §2)GridDefinitiongains a strongly-typed constructor acceptingGridSize[]for columns and rows. The legacy string-array constructor is preserved for backward compatibility. (spec 033 §1)ApplicationPersistedScopeandWindowPersistedScopenow emit one-lineDebug.WriteLinediagnostics on construction, disposal, and (for the application scope) memory-pressure trim. Logs only counts and capacity — never keys or values, since keys may be derived from user-controlled identifiers in apps. (spec 033 §7.10)samples/Reactor.TestApp/Demos/PersistedDemo,NavigationDemo, andsamples/apps/regeditmigrated to the explicitUsePersisted(key, initial, PersistedScope.Window)overload to document per-window intent at the call site. (spec 033 §2)
Microsoft.UI.Reactor.Factories.Grid(string[], string[], params Element?[])is marked[Obsolete]. Use the strongly-typedGrid(GridSize[], GridSize[], params Element?[])overload withGridSize.Auto/GridSize.Star(weight)/GridSize.Px(pixels)instead. Slated for removal in the next minor release. (spec 033 §1)Microsoft.UI.Reactor.Factories.Func(Func<RenderContext, Element>)is marked[Obsolete]. Replace withMemo(ctx => …)(render once + state changes) orRenderEachTime(ctx => …)(always re-render). Slated for removal in the next minor release. (spec 033 §4)
Naming-alignment renames that introduce an [Obsolete] forwarding alias today
and remove the old name in the next minor release.
Microsoft.UI.Reactor.Factories.RichText(string)andRichText(RichTextParagraph[])renamed toRichTextBlock(...)for parity with WinUI'sMicrosoft.UI.Xaml.Controls.RichTextBlock(record was alreadyRichTextBlockElement). The oldRichTextfactory is preserved as a thin[Obsolete]forwarding alias for one release; slated for removal in the next minor release. (spec 039 §1.3 / §14 #8)- No
Microsoft.UI.Reactor.Factories.ScrollVieweralias. (Originally considered as a discoverability hint for callers reaching for the WPF/WinUI-legacy name, but the alias would shadowMicrosoft.UI.Xaml.Controls.ScrollViewer's attached-property type for callers usingusing static Microsoft.UI.Reactor.Factories;alongsideusing Microsoft.UI.Xaml.Controls;— forcing them to fully-qualifyScrollViewer.SetVerticalScrollMode(...)etc. Discoverability win didn't justify the imposed disambiguation cost on existing consumers. UseScrollViewdirectly.) (spec 039 §6 / §16) Microsoft.UI.Reactor.Factories.ProgressBar(double)andProgressBar()added as[Obsolete]aliases for the existingProgress(double)/ProgressIndeterminate()factories. Reactor'sProgressreconciles to WinUI'sProgressBar; the alias lets agents reaching for the WinUI name discover it. (spec 039 §5 / §16)
ReactorHost.MainDispatcherQueue(internal static, first-host-wins capture). Cross-thread setState marshalling and AutoSuggest'sRaiseStateChangednow route throughReactorApp.UIDispatcher.ReactorHostctor seedsUIDispatcherfor embeddedReactorHostControlscenarios that bypassReactorApp.Run. (spec 036 §4.3)