Repository navigation
Map the experimental WinUI TableView and Chart into Reactor.Advanced - #1357
Draft
Alexandre Zollinger Chohfi (azchohfi) wants to merge 35 commits into
Draft
Alexandre Zollinger Chohfi (azchohfi) wants to merge 35 commits into
Alexandre Zollinger Chohfi (azchohfi) wants to merge 35 commits into
Conversation
The WinUI TableView (Microsoft.UI.Xaml.Controls.Tabular) and Chart (Microsoft.UI.Xaml.Controls.Charts) controls ship only in Microsoft.WindowsAppSDK.WinUI 2.3.10-experimental, which the 2.5.4-experimental metapackage pairs with (its nuspec names WinUI 2.3.10-experimental and pins Runtime [2.5.4-experimental]). Consequences of the bump: - CS8305 is demoted to a warning (WarningsNotAsErrors): the XAML compiler's generated XamlTypeInfo references the SDK's experimental metadata providers. - Disambiguate the IElementFactory cref (the SDK adds Microsoft.UI.Xaml.IElementFactory), which otherwise fails Release with CS0419. - Literal SDK pins in recipes/docs/CLI template/perf script follow the central floor, as WinAppSDKReferenceGuardTests requires. - Motion constants re-verified: the four WinUI transition sources are byte-identical at winui3/release/2.5.4-experimental (blob SHAs unchanged; the tag diff touches 300 other files). - The spec 063 D1 tripwire fired: at Foundation 2.3.13 LocalSettings writes the machine-local hive (measured). The guard is now hive-agnostic and the spec records the measurement; D1 itself is unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
Adds first-class Reactor elements for the experimental platform controls:
TableView (Microsoft.UI.Reactor.Advanced.Tabular)
- TableView(items, columns...) MVU-style: pass immutable record snapshots; the
handler diffs each snapshot into a collection it owns (.KeyRows for identity),
so the table never resets and selection/scroll/edits survive.
- TextColumn<T>(header, read, onEdit): the native text column (accessible
TextBlock cell, TextBox editor, UIA value pattern) fed by a pathless binding
through a C# converter. No reflection or INPC; edits arrive as a callback and
nothing is written into the row.
- TemplateColumn<T>(header, row => Element): Reactor-rendered cells hosted in a
code-built DataTemplate, re-rendered on recycle and unmounted with the table.
- BoundColumn(header, path) for classic {Binding} models.
- Sorting (typed SortBy), FilterRows/GroupRows (TableViewSource), group header
and empty content, frozen columns, density, grid lines, selection.
Chart (Microsoft.UI.Reactor.Advanced.Charts)
- Chart(LineSeries/BarSeries/AreaSeries...) with category, date-time and
linear axes, legend, markers and data labels.
- Axes are deduplicated by record equality per slot and layout (the native
chart rejects sharing across horizontal bars and other series), value
collections are shared per instance as XAML does, and everything updates in
place.
Both handlers merge TabularControlsResources / XamlChartsResources on first use
and explain the self-contained requirement for unpackaged apps instead of
surfacing an opaque COM error.
Includes selftest fixtures, unit tests, samples (TableView.Sample,
WinUIChart.Sample), and skill/README/AOT docs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
Contributor
📦 Build metricsArtifact sizes for Packages (compressed .nupkg)
Assemblies in Microsoft.UI.Reactor
Assemblies in Microsoft.UI.Reactor.Advanced
Assemblies in Microsoft.UI.Reactor.Devtools
✅ smaller / |
Contributor
🧪 Merged coverageCoverage for
✅ higher / |
- Bind snapshot rows through a named partial collection (TableRows). Under
NativeAOT CsWinRT cannot project an anonymous ObservableCollection<object?>
assigned to the object-typed ItemsSource ("items must implement a supported
collection interface"), which failed every TableView mount in the AOT host.
- Split the edit-through-automation check into its own fixture and AOT-skip it:
in-process under NativeAOT CreatePeerForElement returns no peer for a
TableViewRow, so the fixture cannot reach the cell peer. Every other
TableView fixture, including the immutable-snapshot one, runs under AOT.
The fixture now uses the platform's own row/cell peers rather than a
hand-constructed one.
- Doc snippet gate: a command-line WarningsNotAsErrors is a global property that
replaces the props value, so it must exempt CS8305 too.
- Bootstrap: the reactor template floats Microsoft.WindowsAppSDK at "*", which
never resolves a prerelease; when the repo pin is a prerelease, pin the
scaffold to it (NU1107 otherwise).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
- The reactor template's own post-creation restore fails under a prerelease pin (its Version="*" float resolves stable), making `dotnet new` exit non-zero before the pin step ran. Tolerate that exit only when the repo pin is a prerelease and the project was produced; the explicit restore after pinning remains the real check. - LT_OnMountUnmountBalanced failed intermittently in CI (0/25 locally) with no diagnostics; its checks now report the observed mount/unmount counts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
Get-PinnedWindowsAppSdkVersion reads with [IO.File], which resolves a relative path against the process directory, not the pwsh location pushed into TestProjects. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
The native control can release (and re-context) realized shells after the table is gone; a late DataContextChanged must not re-enter the reconciler on the unmounted table's behalf. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
Per-control .OnUnmount actions lived in a ConditionalWeakTable keyed by the FrameworkElement wrapper. Nothing keeps a plain control's RCW alive, so after a GC between mount and unmount the unmount walk sees a fresh wrapper over the same native control, the lookup misses, and the callback silently never runs. Move the action onto the native element's ReactorState, as was already done for the echo counter, validation bindings and managed resource keys for the same reason. This is what LT_OnMountUnmountBalanced hit intermittently in CI (exactly the last batch's 10 unmounts missing); GC timing in shard 2 shifted with this branch's added fixtures. LT_OnUnmountSurvivesWrapperCollection forces the GC and is deterministic: 0/10 unmounts before the fix, 10/10 after. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…tity hash PDM_Stack/WrapGrid KeyedSwap compared RuntimeHelpers.GetHashCode of the Border's managed wrapper. A GC between the two reads collects and re-projects the wrapper, changing its identity hash with no change to the native tree, so the check failed spuriously (twice in the AOT shard). Hold the original wrappers and compare by reference, as the sibling fixtures in this file already do; a live wrapper keeps CsWinRT returning that same instance for the native Border. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
Resolves the ReactorState conflict by keeping both new fields: this branch's OnUnmountAction and main's ComponentDiagnosticId. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
- Compare layout doubles/floats within a tolerance instead of ==. - Make the TableView unmount fixture's effect counters instance-owned, handed to cells through props, instead of static fields written per run. - !table.IsReadOnly instead of == false. - AddDays(7.0 * i) in the chart sample. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…brid) The 8 scalar TableView properties (SelectionMode, Density, GridLinesVisibility, HeadersVisibility, CanUserSortColumns, CanUserResizeColumns, RowBackground, AlternatingRowBackground) now come from [GenerateReactorDescriptor] with ClearValueOnUnset; the generated static constructor registers the descriptor. The rows, columns, templates, effective IsReadOnly, controlled selection and all events stay hand-written in the Customize hook (TableViewSync). Per-control state for TableView and Chart moves from a ConditionalWeakTable keyed by the managed wrapper to an attached DependencyProperty on the native control, so a fresh CsWinRT wrapper for the same control still finds it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…mental-tableview-charts # Conflicts: # src/Reactor/Core/Reconciler.cs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…mental-tableview-charts # Conflicts: # src/Reactor/Core/Reconciler.cs
…mental-tableview-charts # Conflicts: # src/Reactor/Core/Element.cs # src/Reactor/Core/Reconciler.cs # tests/Reactor.AppTests.Host/SelfTest/Fixtures/PanelDescriptorMigrationFixtures.cs
…mental-tableview-charts
…fterDisposal ReactorHostControl mounts on Loaded. On WinUI 2.3.10-experimental, Loaded sometimes lands after the single render pass the fixture waited for, so the dialog had not mounted yet and the effect/cleanup/detach checks read 0/0/null. Measured locally: the ContentDialogDispose_ group failed in 6 of 14 runs on the experimental SDK (main's code and this branch alike) and in 0 of 7 on main's stable SDK. Once loaded the product result is correct (1 effect, 1 cleanup, content detached). Gate on host.IsLoaded like the sibling fixtures do; 6/6 clean. Also give TableView_Empty_ContentRendered a 2s budget and a detail string naming which half of its condition is missing, after one CI miss that could not be reproduced locally. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…mental-tableview-charts
main's new ContextReplayingElementFactory doc comment cites a bare IElementFactory, which is ambiguous once Microsoft.UI.Xaml also exposes one (WinUI 2.3.10-experimental); the warning is an error in Release. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…mental-tableview-charts
2.6.6-experimental (released 2026-11-06) carries the TableView and Chart controls forward with additive API only: hierarchical rows (ParentBy, Expand/CollapseAllRows), live shaping, column context flyouts, and a ChartAutomationPeer. The WinUI sub-package is now versioned in lockstep with the metapackage (its nuspec names WinUI 2.6.6-experimental and pins Runtime [2.6.6-experimental]). - Directory.Build.props: both central pins. - Literal pins swept by No_literal_SDK_pin_sits_below_the_central_pinned_version (CLI scaffold, agent-kit recipes, SKILL.md, WinForms README, demo-script prompt, perf harness runtime). - WinUiNavigationMotionParityTests re-verified: no 2.6.6-experimental source tag is published yet; the four motion source files have unchanged blob SHAs at winui3/release/2.6.4 and microsoft-ui-xaml main (positive control: 524 commits, 300+ files changed since the 2.5.4-experimental tag). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…turally Missed once in the JIT selftest shard on CI right after the 2.6.6-experimental bump (columns=2 after 1s); it passed in the AOT shard of the same run, 4/4 alone and in two full local shard-2 runs on 2.6.6-experimental. Give it the same 2s budget as its sibling and report which half is missing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…_ExpiresAfterHoldDuration The fixture sampled once at 180ms against a 100ms hold. That 80ms margin missed in two CI jobs of one run (coverage-instrumented shard 1 and MSIX shard 1) while passing in the plain shard and 9/9 locally on 2.6.6-experimental. Poll up to 1s after the 180ms wait instead; a sprite that never expires still fails. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…ay sample RapidClicksThenWait and Refresh_ExpiresAfterFinalWindow sample the overlay once at a fixed delay past its DispatcherQueueTimer hold (100-250ms margins). RapidClearWait missed on the coverage-instrumented CI lane on the next run after the previous fix, so poll after the existing wait in both; overlays that never clear still fail, and the timer-reset property stays proven by AliveAfterRefresh. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…tail The structural-add check missed twice on CI on 2.6.6-experimental (JIT shard 2, then AOT shard 2) with the phase-2 cells never rendered, but passes 25/25 alone, 2/2 full JIT shard-2 and 3/3 full AOT shard-2 runs locally on the same SDK. A phase:N text now tells a lost/starved re-render apart from a failed TableView update the next time it misses. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…sInPlace The last CI miss showed the component never re-rendered for the second click (componentAtPhase2=False). Log which phase the handler ran at and which phase is on screen, to tell a lost click from a stale handler. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…mental-tableview-charts
…CollectedStateTick Since the 2.6.6-experimental bump this fixture's positive-control legs missed three times on CI (AOT, then MSIX twice): the box's 150 ms TextChanged timer ticked after the fixed 250/400 ms windows. Poll for the expected tick for up to 1.5 s more on those legs, and lengthen the out-of-tree window to cover that, so its zero-ticks claim is at least as strong as before. Passes 4/4 locally. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…mental-tableview-charts
…face main now indexes Reactor.Advanced (ApiIndexGeneratorTests.Index_IsUpToDate), so this branch's TableView/Chart factories and modifiers join both byte-identical copies. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…mental-tableview-charts
The latest CI miss reported clicksSeenAtPhase=[0,1] and shownPhase=-1: both clicks reached the handler, then the whole subtree vanished, which is the render-error fallback replacing the host content. Record the host's render errors in the failure detail and assert none occur, so the next miss names the exception from the phase-2 (structural column add) update. Probed with a forced throw: it reproduces the CI signature exactly and the error is captured. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
…mental-tableview-charts
…otstrap: pin only a direct WinAppSDK ref ReactorDataTemplate tracks realized shells in a ConditionalWeakTable. A shell the table released can be finalized while the table still yields it, so SetRender/UnmountAll read DataContext on a projection whose WinRT references are disposed and the reconcile throws ObjectDisposedException (the CI-only TableView_UpdatesColumnsInPlace failure). Skip and forget such shells. TableView_SkipsFinalizedCellShell reproduces the state deterministically by disposing a shell's interface references; it fails without the fix. Template pack 0.0.8-alpha no longer references Microsoft.WindowsAppSDK directly (WinUI flows from Microsoft.UI.Reactor), so the bootstrap scaffold step pins only when that reference is present. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7feb5bd2-d8b3-4135-8599-8bef352004c4
Comment on lines
+210
to
+217
| foreach (var field in type.GetFields(global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.DeclaredOnly)) | ||
| { | ||
| if (field.GetValue(shell) is WinRT.IObjectReference reference) | ||
| { | ||
| reference.Dispose(); | ||
| count++; | ||
| } | ||
| } |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Moves the repo to the Windows App SDK
2.6.6-experimentalchannel (WinUI2.6.6-experimental), which ships two new control families, and maps both intoReactor.Advanced. The controls first shipped in2.5.4-experimental;2.6.6-experimentaladds only new APIs (hierarchical rows, live shaping, column context flyouts, chart UI Automation), so the mapping is unchanged:Microsoft.UI.Reactor.Advanced.Tabular, overMicrosoft.UI.Xaml.Controls.Tabular.TableView), designed MVU-first:TableView(items, columns...)takes immutable record snapshots. It diffs each new snapshot into a collection it owns (.KeyRows<T>(...)for identity), so the table never resets: selection, scroll and active edits survive state changes.TextColumn<T>(header, read, onEdit)is the platform's own text column (accessible TextBlock cell, TextBox editor, UIA value pattern), fed by a pathless binding through a C# converter. No reflection, noINotifyPropertyChanged, NativeAOT-safe. Edits arrive as a callback (p with { Name = text }), and nothing is written into the row.TemplateColumn<T>(header, row => Element): Reactor-rendered cells in a code-builtDataTemplate.BoundColumn(header, path)keeps classic{Binding}available for INPC models.SortBy,FilterRows/GroupRows(viaTableViewSource), group-header/empty content, frozen columns, density, grid lines, selection.[GenerateReactorDescriptor]withClearValueOnUnset. The generated static constructor registers the control. Rows, columns, templates, the effectiveIsReadOnly, controlled selection and the events are hand-written in theCustomizehook. The events stay hand-written because a control holds a single event-payload box, so generated and custom events can't be mixed. Chart stays a hand-coded handler: the generator would cover only 2 of its properties.Microsoft.UI.Reactor.Advanced.Charts, overMicrosoft.UI.Xaml.Controls.Charts.Chart):Chart(LineSeries/BarSeries/AreaSeries...)with category / date-time / linear axes, legend, markers and data labels. Everything updates in place. Shared axes andSamplesare reconciled according to the native rules found while building this:Samples.samples/TableView.Sample,samples/WinUIChart.Sample.This supersedes #621, which hand-built a projection and checked in native binaries to reach a pre-release TableView. The control now ships in the SDK, so #621 can be closed.
Fixes found while getting CI green
.OnUnmountregression test:LT_OnUnmountSurvivesWrapperCollectionforces a GC between mount and unmount. Before the fix, 0 of 10 unmount callbacks fired.mainhas since shipped its own version of the fix (ReactorState.UnmountAction), so this PR now takesmain's implementation and keeps only the fixture.ConditionalWeakTablekeyed by the managed wrapper misses once CsWinRT collects and re-creates the wrapper.ReactorDataTemplatetracks realized cell shells weakly. A shell the table released can be finalized while the weak table still yields it, so re-rendering a template column readDataContexton a disposed projection and the reconcile threwObjectDisposedException. This was the intermittent CI-onlyTableView_UpdatesColumnsInPlacefailure. Such shells are now skipped and forgotten.TableView_SkipsFinalizedCellShellreproduces the state by disposing a shell's interface references, and it fails without the fix (JIT and NativeAOT).partialcollection (TableRows). Without it, CsWinRT can't project the collection and every TableView mount failed under AOT.WarningsNotAsErrorsoverrides the props file.Microsoft.WindowsAppSDKdirectly (packs up to 0.0.7-alpha float it atVersion="*", which never resolves a prerelease). Pack 0.0.8-alpha dropped that reference, so WinUI flows fromMicrosoft.UI.Reactorand there is nothing to pin.Linked issue / spec
Supersedes #621. Updates spec 063 §3.2 (the D1 tripwire fired; see below).
Test plan
tests/Reactor.Tests/ExperimentalControlsTests.cscover value normalization, axis inference and identity, the snapshot diff (RowDiff), the text-column commit rule and the column helpers.TableViewFixtures(8) andChartFixtures(3).onEdit.dotnet test tests/Reactor.Tests: passed, 15,387 succeeded, 0 failed (after mergingmain).TableView_TextColumnEditViaAutomation, skipped under AOT with a documented reason: in-process,CreatePeerForElementreturns no peer for a row.AnchorReset/Issue487/ItemsView_Parkingfailures, which fail identically on unmodifiedmainon this machine.dotnet pack src/Reactor.Advanced: the nuspec gains no generator or abstractions dependency.mainmerge. This includes Build solution, AOT / packaged / regular selftests, coverage, doc gate and bootstrap (packaged scaffold build and launch).Risk / breaking changes
WindowsAppSDKVersionis2.6.6-experimental, and CS8305 is demoted to a warning. Merge only if we're fine shipping on the experimental channel. Otherwise keep this as the integration branch until the APIs go stable.ms-appx:///, which a framework-dependent unpackaged app can't resolve. Reactor throws an explanatoryInvalidOperationException. Samples setWindowsAppSDKSelfContained=true.LocalSettingsnow writes the machine-local hive. The guard is now hive-agnostic. D1/D2 are not revisited here.Reactor.Advanced.