Skip to content

[net11.0] Trimmable element handlers#29952

Open
simonrozsival wants to merge 29 commits into
net11.0from
dev/simonrozsival/net10.0-use-element-handler-attributes
Open

[net11.0] Trimmable element handlers#29952
simonrozsival wants to merge 29 commits into
net11.0from
dev/simonrozsival/net10.0-use-element-handler-attributes

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jun 12, 2025

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

This is a follow-up to #28357.

Summary

Built-in controls now primarily use [ElementHandler] attributes instead of DI registration for handler resolution. Handler associations are visible at the type level, improving trimmability because unused controls no longer need to be rooted by the default handler-registration table.

HybridWebView remains an exception: its handler is registered through the RuntimeFeature.IsHybridWebViewSupported-guarded DI path so NativeAOT can eliminate trim-unsafe HybridWebView code when the feature switch is disabled.

Key changes

[ElementHandler] attribute

  • New internal ElementHandlerAttribute on view types declaratively associates a view with its handler.
  • Inherited = false — each concrete type that needs a different handler declares its own attribute.
  • Handler resolution walks the BaseType chain, so derived types without their own attribute inherit the nearest ancestor's handler.
  • Android Material3 controls use control-specific nested attributes so the handler type can be selected conditionally without unconditionally rooting both handler implementations.

Handler resolution in MauiHandlersFactory

Both GetHandler(Type) and GetHandlerType(Type) follow the same resolution order:

  1. Exact DI registration — if the concrete type is registered via AddHandler<Button, MyButtonHandler>(), that wins.
  2. Assignable DI registration — base/interface registrations still override inherited attribute defaults.
  3. [ElementHandler] attribute — walk the type's BaseType chain looking for the attribute.
  4. IContentView fallback — if the type implements IContentView, return ContentViewHandler.

GetHandler() is the primary API — it returns an instantiated IElementHandler. GetHandlerType() returns only the Type and is used by code that needs to compare handler types without instantiating them.

Attribute lookup is cached per view type to avoid repeated reflection walks during handler resolution.

ElementExtensions.ToHandler() / SetHandler()

These methods call GetHandler() directly. When a handler requires constructor parameters, ToHandler() catches the MissingMethodException and falls back to ActivatorUtilities.CreateInstance() with DI injection. This fallback path is cached per type.

Controls mapper remapping

Controls-layer mapper remaps no longer rely on static constructors or RuntimeHelpers.RunClassConstructor.

Controls remapping stays on the Controls virtual view types, matching where the old mapper static constructors lived. ElementHandler.SetVirtualView() invokes an internal Controls remap hook before mapper updates; each remappable control override uses its own Interlocked.CompareExchange field to run once, calls base.RemapForControls() first, then applies its own mapper changes.

This preserves the existing base-before-derived mapper ordering without forcing arbitrary class constructors to run, without putting Controls-specific remap logic on Core handlers, and without handwritten per-control handler-attribute classes.

Non-mapper command dependency setup (CommandProperty.DependsOn(...)) remains in the relevant control type initialization so binding behavior is independent of handler creation.

Cleanup

  • Removed IElementHandlerWithAndroidContext and ElementHandlerWithAndroidContextAttribute.
  • Removed dead RemapForControls(this MauiAppBuilder) and old RemapForControls() / RemapIfNeeded() patterns.
  • Removed RemappingHelper and the static-constructor-based mapper-remap helper path.
  • Removed an empty Layout.Mapper.cs remap file.
  • Fixed Slider not having mapper remapping coverage.
  • Restored IMauiHandlersFactory.GetHandler() to its original signature.
  • Removed unused MauiContext / HandlersContextStub declarations from tests and benchmarks.
  • Matched cell compatibility handler attributes to the platforms where those renderers actually exist.
  • Kept command dependency registration separate from mapper remapping so it is not delayed until handler attachment.

Copilot AI review requested due to automatic review settings June 12, 2025 07:43
@simonrozsival simonrozsival requested a review from a team as a code owner June 12, 2025 07:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR transitions built-in controls from DI registration to using [ElementHandler] attributes, introduces IElementHandlerWithAndroidContext<T> for Android renderers, and adds a new MSBuild property to toggle CSS support.

  • Apply [ElementHandler] to core controls and cells instead of DI
  • Introduce IElementHandlerWithAndroidContext<T> and CreateHandler(Context) on Android renderers
  • Add MauiCssEnabled MSBuild property and corresponding runtime host configuration

Reviewed Changes

Copilot reviewed 120 out of 120 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Controls/src/Core/ContentPage/ContentPage.Mapper.cs Added static ctor calling RemapForControls and VisualElement.RemapIfNeeded
src/Controls/src/Core/Compatibility/Handlers/TableView/Android/TableViewRenderer.cs Implemented IElementHandlerWithAndroidContext<TableViewRenderer> and CreateHandler
src/Controls/src/Core/Compatibility/Handlers/ListView/Android/ListViewRenderer.cs Implemented IElementHandlerWithAndroidContext<ListViewRenderer> and CreateHandler
src/Controls/src/Core/Compatibility/Handlers/Android/FrameRenderer.cs Implemented IElementHandlerWithAndroidContext<FrameRenderer> and CreateHandler
src/Controls/src/Core/Cells/*.cs Added [ElementHandler<...>] attributes for compatibility cell renderers
src/Controls/src/Core/Button/Button.cs Added [ElementHandler<ButtonHandler>]
src/Controls/src/Core//.Mapper.cs Added static ctors calling RemapForControls / RemapIfNeeded
src/Controls/src/Build.Tasks/nuget/.../Microsoft.Maui.Controls.targets Added MauiCssEnabled property and runtime host configuration option
src/Compatibility/Core/src/{iOS,Android}/Platform.cs Updated GetHandler calls to pass MauiContext
Comments suppressed due to low confidence (1)

src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets:240

  • Added the MauiCssEnabled build property but no corresponding public documentation under /docs/; please update the XML docs to describe this new feature.
<MauiCssEnabled Condition="'$(MauiCssEnabled)' == '' and '@(MauiCss->Count())' == '0'">false</MauiCssEnabled>

Comment thread src/Controls/src/Core/Compatibility/Handlers/ListView/Android/ListViewRenderer.cs Outdated
Comment thread src/Controls/src/Core/Compatibility/Handlers/Android/FrameRenderer.cs Outdated
Comment thread src/Core/src/RuntimeFeature.cs
@simonrozsival simonrozsival marked this pull request as draft June 12, 2025 08:06
@mattleibow

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@PureWeen PureWeen added this to the .NET 10.0-preview6 milestone Jun 17, 2025
@PureWeen PureWeen moved this from Todo to In Progress in MAUI SDK Ongoing Jun 17, 2025
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/net10.0-use-element-handler-attributes branch from 33afe18 to c49e57e Compare June 18, 2025 11:53
@PureWeen PureWeen added platform/macos macOS / Mac Catalyst platform/windows platform/android platform/ios area-architecture Issues with code structure, SDK structure, implementation details labels Aug 7, 2025
@simonrozsival

Copy link
Copy Markdown
Member Author

@copilot there are merge conflicts in this branch. please resolve them. When you're done, write summary of what this PR does and what functionality is missing to fully convert the "old" way of implementing handlers to the "new" way.

Copilot AI commented Nov 25, 2025

Copy link
Copy Markdown
Contributor

@simonrozsival I've opened a new pull request, #32861, to work on those changes. Once the pull request is ready, I'll request review from you.

@simonrozsival

Copy link
Copy Markdown
Member Author

@copilot oops, try now!

Copilot AI commented Nov 25, 2025

Copy link
Copy Markdown
Contributor

@simonrozsival I've opened a new pull request, #32862, to work on those changes. Once the pull request is ready, I'll request review from you.

@simonrozsival

Copy link
Copy Markdown
Member Author

@copilot there are several errors popping up:

  • src\Controls\src\Core\Shell\Shell.cs(29,25): Error CS0246: The type or namespace name 'ShellHandler' could not be found (are you missing a using directive or an assembly reference?)
  • src\Controls\src\Core\Shell\ShellItem.cs(18,25): Error CS0246: The type or namespace name 'ShellItemHandler' could not be found (are you missing a using directive or an assembly reference?)
  • buildtasks/Microsoft.Maui.Controls.Build.Tasks.After.targets(15,3): error MSB4024: The imported project file "/mnt/vss/_work/1/s/.buildtasks/Microsoft.Maui.Controls.targets" could not be loaded. Name cannot begin with the '<' character, hexadecimal value 0x3C. Line 325, position 2. [/mnt/vss/_work/1/s/src/BlazorWebView/tests/DeviceTests/MauiBlazorWebView.DeviceTests.csproj]

Fix them and make sure the product can be built.

Copilot AI commented Dec 1, 2025

Copy link
Copy Markdown
Contributor

@simonrozsival I've opened a new pull request, #32933, to work on those changes. Once the pull request is ready, I'll request review from you.

jfversluis pushed a commit that referenced this pull request Jun 1, 2026
…#35666)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description

Workaround for #35665.

The `[ElementHandler<CheckBoxHandler>]` attribute on `CheckBox` causes
handler resolution to fail on .NET 11 preview MAUI templates on Android
**Release** builds across all runtime flavors:

- **CoreCLR Default** — `System.TypeLoadException` when the generic
attribute is enumerated (`GenericArguments[0] ... violates the
constraint of type parameter 'THandler'`).
- **Mono Default** / **NativeAOT** — `HandlerNotFoundException`, since
the attribute isn't found and no DI registration exists.

`CheckBox` was the **only** type using `[ElementHandler<T>]`. This PR
removes the attribute and restores the regular DI handler registration
(`AddHandler<CheckBox, CheckBoxHandler>()`), which unblocks the failing
template builds.

The `[ElementHandler<T>]` attribute mechanism itself will undergo a
major refactoring in #29952, so this is intentionally a minimal,
targeted workaround rather than a fix of the attribute path.

### Issues Fixed

Fixes #35665

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

kubaflo commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

AI code review for net11.0 target

Automated, non-approval review comment. This does not constitute a human approval and intentionally does not use GitHub's Approve/Request-changes.

Verdict: Needs changes (architecture is sound; blocked on rebase + fresh CI)

What this PR does

Moves Controls handler registration from imperative AddControlsHandlers() (a big IMauiHandlersCollection.AddHandler<View,Handler>() block in AppHostBuilderExtensions) to declarative [ElementHandler(typeof(XHandler))] attributes on each view type, so handler types are statically referenced (trimmer/AOT-friendly). Conditional cases use private sealed XHandlerAttribute : ElementHandlerAttribute subclasses overriding GetHandlerType() (e.g. Android Material3 Handler/Handler2 switch). Mapper remapping moves from RemapForControls() into per-type static constructors, ordered via RemappingHelper.EnsureBaseTypeRemappedRuntimeHelpers.RunClassConstructor. Includes an updated docs/design/HandlerResolution.md.

Findings

  • Trimming special-cases handled correctly. HybridWebView is deliberately not given an [ElementHandler] attribute (that would hard-reference the handler and defeat trimming); it stays a conditional DI registration guarded by RuntimeFeature.IsHybridWebViewSupported, with scoped IL2026/IL3050 suppressions. The Material3 / CollectionView Handler2 branches are preserved via GetHandlerType() overrides annotated [return: DynamicallyAccessedMembers(PublicConstructors)]. Good attention to detail.
  • ⚠️ Static-constructor remapping ordering is subtle. Remappings now run lazily when each view type is first touched, instead of eagerly at host build. EnsureBaseTypeRemapped(typeof(Button), typeof(VisualElement)) force-runs only the named base's cctor, relying on each level to chain correctly. This is the highest-risk area for platform-specific regressions (a handler/mapper exercised before the view's cctor has run, or an intermediate base with its own remappings). Broad device-test coverage across controls is warranted; please confirm the existing handler/mapper suites all pass on-device.
  • ⚠️ Adding explicit static Foo() cctors removes beforefieldinit, slightly changing initialization timing for every affected control — intended, but a behavioral change worth calling out.
  • ⚠️ Merge state is CONFLICTING/DIRTY against net11.0; this 121-file change will need a careful rebase.
  • ✅ The design doc is updated to describe the new resolution order (exact DI → assignable DI → [ElementHandler] base-walk → IContentView fallback), which matches the code direction.

No blocking correctness defect was found in the slices I reviewed, but the breadth (≈all controls) plus the lazy-cctor ordering change make this merge-risky without a fully green on-device run.

CI note

Build 1436697 is stale and red, but Build/Pack macOS (Release) failed on infrastructure (MSB4276 — SDK resolver couldn't find /opt/hostedtoolcache/dotnet/sdk/10.0.100/Sdks/...), not a code/compile error from this PR. Because the build is stale and the PR is conflicting, a fresh full run (Build + Helix unit tests + device/UI tests) is required after rebase before merge.

Confidence: Medium — sound design and good trimming handling, but high blast radius, subtle init-ordering change, and no current green CI.

@simonrozsival

Copy link
Copy Markdown
Member Author

@copilot address latest review

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
@kubaflo

kubaflo commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

AI code review refresh for net11.0 target

Automated, non-approval review comment. This does not constitute a human approval and intentionally does not use GitHub's Approve / Request-changes.

Head reviewed: a2cd36111b5492c999bdd4d64ce03236a7834e4eTarget: net11.0Files changed: 121 • Diff: +1250 / −428

Verdict: Needs discussion — code looks ready; CI signal needs differential analysis vs. baseline before merge.

What changed since the prior round-1 fleet review (2026-06-01)

Only one new commit: a2cd361 "Merge net11.0 into handler attributes branch" (merge from 74e739f8). The combined merge diff shows only net11.0 catch-up files (Shell renderers, Items adapters, instructions, scripts, samples) — no authored changes to the PR's handler-attribute infrastructure (MauiHandlersFactory, ElementHandlerAttribute, RemappingHelper, Controls/Hosting/AppHostBuilderExtensions, view-level [ElementHandler] attributes, mapper-cctor remappings, or HybridWebView guard). The PR is now mergeable (MERGEABLE, no conflicts); only branch-protection (BLOCKED) holds it.

Prior review reconciliation

I re-verified PureWeen's seven adversarial-consensus findings (2026-05-13) against the current head:

# PureWeen finding Status at a2cd361
1 🔴 Material3 handlers silently broken on Android ✅ Resolved. Each affected control (Slider.cs, Switch.cs, Editor.cs, Picker.cs, TimePicker.cs, RadioButton.cs, Label.cs, Entry.cs, SearchBar.cs, Image.cs, DatePicker.cs, ActivityIndicator.cs, ProgressBar.cs) declares an internal sealed XxxHandlerAttribute : ElementHandlerAttribute under #if ANDROID whose GetHandlerType() returns the Handler2 variant when RuntimeFeature.IsMaterial3Enabled. New src/Controls/tests/DeviceTests/Material3HandlerResolutionTests.Android.cs explicitly asserts both GetHandlerType and platform-view instantiation for all 13 controls under each switch value.
2 🔴 SwipeItemView crashes on Windows ✅ Resolved. [ElementHandler(typeof(SwipeItemViewHandler))] is now wrapped in #if ANDROID || IOS || MACCATALYST || TIZEN, so Windows falls through to the IContentView → ContentViewHandler fallback (step 4).
3 🔴 DI base-type overrides silently ignored ✅ Resolved. MauiHandlersFactory.GetHandler/GetHandlerType now run the assignable-DI lookup before the attribute walk (step 2 with an explicit code comment about AddHandler<Button, …>() winning for FancyButton : Button). New unit test RegisteredBaseControlHandlerOverridesInheritedElementHandlerAttribute exercises exactly this case.
4 🟡 Uncached reflection on hot path ✅ Resolved. ConcurrentDictionary<Type, ElementHandlerAttribute?> _elementHandlerAttributeCache with GetOrAdd covers both GetHandler and GetHandlerType. A dedicated regression test was added (36f3926a).
5 🟡 Opaque MissingMethodException ✅ Resolved. CreateAttributeHandler catches MissingMethodException and rethrows HandlerNotFoundException with a message naming the parameterless-ctor requirement and pointing to AddHandler for constructor-injected handlers.
6 🟡 GetCollection() obsoletion w/ incomplete migration path ✅ Resolved. IMauiHandlersFactory.GetCollection() is no longer obsoleted (signature restored, per PR description).
7 🟢 ContainerChangedFiresWhenMapContainerIsCalled removed without replacement ➖ Acceptable. Replaced by broader handler-resolution and _elementHandlerAttributeCache coverage; the underlying MapContainerView/PlatformContainerViewChanged wiring is still exercised by Controls device-tests.

All three CRITICAL findings are now resolved with targeted tests. The earlier round-1 fleet-review caveats (lazy-cctor ordering risk, behavioural change from explicit static Foo() cctors removing beforefieldinit) remain intentional and observable — they're worth a final on-device pass but I did not find any new code path that breaks them.

Architecture re-spot-check at head

  • MauiHandlersFactory resolution order is consistent across both GetHandler and GetHandlerType: exact DI → assignable DI → [ElementHandler] base-walk → IContentView fallback. TryGetElementHandlerAttribute walks BaseType chain (Inherited = false on the attribute) with cache.
  • RemappingHelper.EnsureBaseTypeRemapped(derived, base) is invoked from every Controls-layer per-type static Xxx() cctor (e.g. Slider.Mapper.cs, WebView.Mapper.cs, VisualElement.Mapper.csElement.Mapper.cs). The chain bottoms out at Element which has no further EnsureBaseTypeRemapped call — correct.
  • [ElementHandler] attribute is internal, sealed-pattern subclasses are internal sealed and live inside each view type for trim-friendly conditional dispatch.
  • HybridWebView is the only view without an [ElementHandler] attribute on it; its handler is registered only when RuntimeFeature.IsHybridWebViewSupported is true, with scoped #pragma warning disable IL2026, IL3050. WebView itself remains attribute-driven with WebViewHandler. No new hard references to HybridWebViewHandler were introduced.
  • Controls/src/Core/RemappingHelper.cs is a tiny internal static helper with one Debug.Assert plus RuntimeHelpers.RunClassConstructor — minimal surface, scoped IL2059 suppression with a clear justification.

CI status (build 1459262, in-progress)

Bucket Count Notes
✅ Pass 25 All Windows builds, both Packs, macOS Release, all Helix unit-test legs that have reported so far, all RunOnAndroid + most RunOniOS (BlazorDebug/Release ± CoreCLR, MauiDebug ± CoreCLR, MauiNativeAOT, MauiRelease_CoreCLR), all Integration buckets except trim/AOT.
❌ Fail 5 (1) Build macOS (Debug) — infrastructure MSB4276 "default SDK resolver failed to resolve Microsoft.Build.NoTargets because /opt/hostedtoolcache/dotnet/sdk/10.0.100/Sdks/... did not exist". Same SDK-resolver infra failure pattern as round-1. Not a code defect. (2) AOT macOS, (3) RunOniOS_MauiReleaseTrimFull ARM64, (4) RunOniOS_MauiRelease ARM64, (5) RunOniOS_MauiReleaseTrimFull_CoreCLR ARM64 — all share the same root cause: ILLink emits IL2026 against <Module>..cctor() for HybridWebViewHandler.SchemeHandler.* / WebViewScriptMessageHandler.* (members carry RequiresUnreferencedCodeAttribute), and TreatWarningsAsErrors=true + TrimMode=full upgrades that to NETSDK1144.
⏳ Pending 4 Windows Helix Unit Tests (Debug/Release), the maui-pr rollup, Build Analysis.

Baseline differential. The two most recent net11.0 baseline runs (1455686 @ 74e739f8 — exactly this PR's merge target — and 1455594 @ d3684fa8) both fail with the same AOT macOS + RunOniOS_MauiReleaseTrimFull ARM64 jobs. So 2 of 5 failures are pre-existing net11.0 baseline failures. The remaining three (Build macOS Debug, RunOniOS_MauiRelease ARM64, RunOniOS_MauiReleaseTrimFull_CoreCLR ARM64) deserve a closer look before merge: the macOS Debug failure is clearly infra, but the two extra iOS legs also fail with the same <Module>..cctor()HybridWebViewHandler IL2026 cascade — strongly suggesting the same root cause is just being amplified across more trim configurations, not a PR-introduced regression. A simple way to confirm is /azp run after a baseline re-merge once arcade/infra stabilizes.

Blast radius

  • All built-in controls (~50 view types) now declare [ElementHandler] and shift mapper remapping into per-type static cctors, removing beforefieldinit. Initialization is now triggered lazily on first handler resolution rather than eagerly during host build. EnsureBaseTypeRemapped forces the named base type's cctor each level — coverage relies on each control having (and correctly chaining) its own static cctor.
  • The Controls/Hosting/AppHostBuilderExtensions.cs shrink from 211→137 lines is the biggest single-file simplification; AddControlsHandlers, RemapForControls, and IElementHandlerWithAndroidContext are gone. Any third-party code calling AddControlsHandlers (it was internal) is unaffected.
  • The internal sealed XxxHandlerAttribute pattern inside view classes is repeated in 13 places — consistent and trim-friendly, but a future maintenance burden if more conditional handler families appear.

Concise findings (new vs. prior round)

  • No new code-level findings. Every prior-round critical/moderate finding I re-checked was addressed in source with corresponding test coverage.
  • CI: 2/5 failures match net11.0 baseline today. The other 3 likely share the HybridWebView IL2026 root cause and a macOS image SDK-resolver hiccup. Recommend re-baselining (re-run after current net11.0 trim issues are fixed) before treating any of these as PR-blocking.

Confidence

Medium-high on code, low on CI as a merge signal. The handler-resolution architecture is well-tested at the unit-test and (newly added) Material3 device-test level. The trim/AOT failure delta vs. baseline is the open question; everything else looks merge-ready pending a clean run.

Non-approval disclaimer

This is an automated, non-approval review comment. It does not constitute a human approval and intentionally does not use GitHub's "Approve" or "Request changes" actions. Please defer to human reviewers for final merge sign-off.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: NEEDS_CHANGES (confidence: high)

Model split was 3 NEEDS_CHANGES / 1 NEEDS_DISCUSSION; the lone ND (opus-4.6) assumed all findings resolved but missed a confirmed regression. The architecture is sound and the prior multi-model review's findings (Material3 routing, uncached attribute reflection, SwipeItemView Windows guard, DI base-type override, MissingMethodException, GetCollection obsoletion) are already FIXED at this HEAD and were re-confirmed — they are not re-raised here. The one surviving code defect is a Tizen [ElementHandler] guard regression on ViewCell/SwitchCell.

Key findings

  • [warning] Tizen guard regression — SwitchCell.cs:9 & ViewCell.cs:11 (consensus: gpt-5.5 + opus-4.8 on SwitchCell; opus-4.8 on ViewCell — both verified). Both add TIZEN to their [ElementHandler] guard, but Handlers.Compatibility.SwitchCellRenderer/ViewCellRenderer exist only for Android, iOS/MacCatalyst, and Windows (the Compatibility/Handlers/ListView/Tizen/ folder has neither). The removed AddControlsHandlers() registered all six cell renderers under #if !TIZEN; the migration correctly dropped Tizen for the 4 sibling cells (Cell/EntryCell/ImageCell/TextCell) but erroneously added it for these two. References an undefined type → CS0246 on net11-tizen. Dormant today only because Tizen TFMs are disabled. Novel finding (prior TIZEN comments were about the now-resolved SwipeItemView guard).
  • [dropped] RemappingHelper.RunClassConstructor as the AOT root cause (gemini, speculative). Contradicted by evidence: RunOniOS_MauiNativeAOT PASSES while exercising the exact same EnsureBaseTypeRemapped path; the failing legs are trim/Release configs, not a runtime cctor failure. Not raised.
  • [dropped/minor] Design-doc drift (opus-4.6, suggestion). docs/design/HandlerResolution.md deletes GetCollection() from the shown IMauiHandlersFactory listing while the real interface keeps it. Real but pure-deletion (no added/modified line to anchor an inline comment), lowest severity, and a defensible illustrative-doc simplification — non-blocking, not posted inline.

CI

maui-pr is PR-failing. Failing legs are trim/AOT: AOT macOS, RunOniOS_MauiRelease, RunOniOS_MauiReleaseTrimFull, RunOniOS_MauiReleaseTrimFull_CoreCLR. Per the prior round's baseline differential, ~2/5 match the net11.0 baseline and the remainder trace to the HybridWebView IL2026 <Module>..cctor() trim cascade (TrimMode=full + TreatWarningsAsErrorsNETSDK1144) plus a macOS SDK-resolver infra hiccup — not the new attribute path (RunOniOS_MauiNativeAOT and AOT windows both pass). A clean re-baseline run is needed to confirm, but CI cannot be treated as green as-is.

{
/// <summary>A <see cref="Microsoft.Maui.Controls.Cell"/> with a label and an on/off switch.</summary>
[Obsolete("The controls which use SwitchCell (ListView and TableView) are obsolete. Please use CollectionView instead.")]
#if WINDOWS || ANDROID || IOS || MACCATALYST || TIZEN

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This [ElementHandler] guard adds TIZEN, but Handlers.Compatibility.SwitchCellRenderer is only defined for Android, iOS/MacCatalyst, and Windows — there is no Tizen implementation (Compatibility/Handlers/ListView/Tizen/ contains no SwitchCellRenderer/CellRenderer). The removed AddControlsHandlers() registered this renderer under #if !TIZEN, and the four sibling cells (Cell, EntryCell, ImageCell, TextCell) all use #if WINDOWS || ANDROID || IOS || MACCATALYST without TIZEN. Including TIZEN here references an undefined type and will fail to compile (CS0246) on net11-tizen; it is dormant today only because the Tizen TFMs are disabled. Drop TIZEN to match the sibling cells and the renderer's actual platform availability.

Suggested change
#if WINDOWS || ANDROID || IOS || MACCATALYST || TIZEN
#if WINDOWS || ANDROID || IOS || MACCATALYST

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in c110c98554: removed TIZEN from the SwitchCell [ElementHandler] guard, so it now matches the platforms where Handlers.Compatibility.SwitchCellRenderer actually exists: WINDOWS || ANDROID || IOS || MACCATALYST. I also rebuilt Controls.Core.UnitTests and reran HostBuilderHandlerTests successfully.

Comment thread src/Controls/src/Core/Cells/ViewCell.cs Outdated
/// <summary>A <see cref="Microsoft.Maui.Controls.Cell"/> containing a developer-defined <see cref="Microsoft.Maui.Controls.View"/>.</summary>
[Obsolete("The controls which use ViewCell (ListView and TableView) are obsolete. Please use CollectionView instead.")]
[ContentProperty("View")]
#if WINDOWS || IOS || MACCATALYST || ANDROID || TIZEN

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same regression as SwitchCell: this guard adds TIZEN, but Handlers.Compatibility.ViewCellRenderer is only defined for Android, iOS/MacCatalyst, and Windows — there is no Tizen ViewCellRenderer. The old AddControlsHandlers() registered it under #if !TIZEN, and the sibling cells exclude TIZEN. On net11-tizen this references an undefined type and breaks the build (CS0246); it is dormant only because the Tizen TFMs are currently disabled. Remove TIZEN from the condition.

Suggested change
#if WINDOWS || IOS || MACCATALYST || ANDROID || TIZEN
#if WINDOWS || IOS || MACCATALYST || ANDROID

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in c110c98554: removed TIZEN from the ViewCell [ElementHandler] guard, so it now matches the available Handlers.Compatibility.ViewCellRenderer platforms: WINDOWS || IOS || MACCATALYST || ANDROID. I also rebuilt Controls.Core.UnitTests and reran HostBuilderHandlerTests successfully.

simonrozsival and others added 2 commits June 15, 2026 13:30
Replace static constructor forcing with an instance remap hook that runs when handlers attach to remappable Controls elements.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove Tizen from SwitchCell and ViewCell compatibility handler attributes because those renderers are not available on Tizen.

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

Copy link
Copy Markdown
Member Author

Thanks for the review pass. I pushed two follow-up commits that address the latest comments and clean up the remapping design:

  • c110c98554 fixes the two inline Tizen guard comments:
    • SwitchCell no longer includes TIZEN in the Handlers.Compatibility.SwitchCellRenderer attribute guard.
    • ViewCell no longer includes TIZEN in the Handlers.Compatibility.ViewCellRenderer attribute guard.
  • 7536ccd2ea replaces the RuntimeHelpers.RunClassConstructor / static-constructor remapping approach with an instance-based RemapForControls(HashSet<Type>) hook that runs when a remappable Controls element is attached to a handler. Each override gates itself with the shared set, calls the base remap first, then applies its own mapper changes.

I also updated the PR description so it no longer describes the old static-constructor / RemappingHelper approach.

Validation run locally:

  • dotnet build src/Core/tests/UnitTests/Core.UnitTests.csproj -c Debug -p:PublicApiType=Validate -p:IncludeIosTargetFrameworks=false -p:IncludeAndroidTargetFrameworks=false -p:IncludeMacCatalystTargetFrameworks=false -p:IncludeWindowsTargetFrameworks=false -p:IncludeTizenTargetFrameworks=false --no-restore -v:minimal
  • dotnet build src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj -c Debug -p:PublicApiType=Validate -p:IncludeIosTargetFrameworks=false -p:IncludeAndroidTargetFrameworks=false -p:IncludeMacCatalystTargetFrameworks=false -p:IncludeWindowsTargetFrameworks=false -p:IncludeTizenTargetFrameworks=false --no-restore -v:minimal
  • dotnet test src/Core/tests/UnitTests/Core.UnitTests.csproj -c Debug --no-build --filter "FullyQualifiedName~SetVirtualViewRunsControlsMapperRemapOncePerKey|FullyQualifiedName~HostBuilderHandlerTests" -p:PublicApiType=Validate -p:IncludeIosTargetFrameworks=false -p:IncludeAndroidTargetFrameworks=false -p:IncludeMacCatalystTargetFrameworks=false -p:IncludeWindowsTargetFrameworks=false -p:IncludeTizenTargetFrameworks=false -v:minimal
  • dotnet test src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj -c Debug --no-build --filter "FullyQualifiedName~HostBuilderHandlerTests" -p:PublicApiType=Validate -p:IncludeIosTargetFrameworks=false -p:IncludeAndroidTargetFrameworks=false -p:IncludeMacCatalystTargetFrameworks=false -p:IncludeWindowsTargetFrameworks=false -p:IncludeTizenTargetFrameworks=false -v:minimal

Register command parameter dependencies during type initialization instead of delaying them until handler attachment.

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

Copy link
Copy Markdown
Member Author

One additional follow-up after the earlier review replies: I pushed 86ee494aa1 to keep command dependency registration separate from mapper remapping.

Specifically, CheckBox, RefreshView, and SearchBar now register their CommandProperty.DependsOn(...) / SearchCommandProperty.DependsOn(...) dependencies during type initialization instead of inside RemapForControls(HashSet<Type>). That keeps non-mapper binding behavior available before handler attachment, while RemapForControls stays focused on handler mapper changes.

I also updated the PR description to document this distinction.

Validation:

  • Controls.Core.UnitTests build passed.
  • Focused command-dependency and handler-resolution tests passed (CommandDependsOnCommandParameterBeforeHandlerAttached + HostBuilderHandlerTests).

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review verdict: LGTM (confidence: medium)

Re-review after your update: the prior blocking issue is fixed, and the one remaining item is a non-blocking test-coverage nit.

Fixed (prior NEEDS_CHANGES)

  • ViewCell / SwitchCell [ElementHandler] TIZEN guard — resolved. At HEAD (86ee494):
    • SwitchCell.cs: #if WINDOWS || ANDROID || IOS || MACCATALYST (no TIZEN)
    • ViewCell.cs: #if WINDOWS || IOS || MACCATALYST || ANDROID (no TIZEN)
    • These now match the sibling cells (Cell/EntryCell/ImageCell/TextCell) and the renderers' real platform availability, so the Tizen CS0246 risk is gone.

Still open

  • None blocking.
  • Non-blocking (test coverage)HostBuilderHandlerTests.BuiltInHandlerTypes: ListView/TableView carry [ElementHandler] for WINDOWS || IOS || MACCATALYST || TIZEN || ANDROID (Windows renderers exist), but the new test data asserts them only under #if ANDROID || IOS || MACCATALYST, leaving the Windows attribute path unverified. ShellItem/ShellSection are WINDOWS || TIZEN in source but asserted only under #if WINDOWS. TIZEN branches are dormant (Tizen TFMs disabled), so only the Windows gap is active. See inline comment.

Resolving the split

  • gpt-5.5 LGTM, opus-4.6 LGTM, gemini APPROVED (1 non-blocking nit) — all correct against the code.
  • opus-4.8 NEEDS_CHANGES (2 findings) — both are stale false positives: they re-flag the TIZEN guard on ViewCell/SwitchCell, which is already removed at HEAD (verified above). Its summary also claims iOS-TrimFull/AOT-macOS CI legs are failing; that is not reflected in the current checks (see below).
  • gemini's coverage nit is real and code-confirmed, but explicitly optional and non-blocking — kept as a single inline suggestion.

CI

Pending, no failing checks: license/cla pass; maui-pr and all build/Helix legs pending; Build Analysis pending; add-dogfood-comment pass; Bump global.json skipping. No PR-failing legs in the current check set (the new commit's CI is still running). Recommend confirming maui-pr (incl. iOS Release/TrimFull and AOT macOS legs) goes green before merge.

{ typeof(ViewCell), typeof(Handlers.Compatibility.ViewCellRenderer) },
#endif
#if ANDROID || IOS || MACCATALYST
{ typeof(ListView), typeof(Handlers.Compatibility.ListViewRenderer) },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking (test coverage): the newly added [ElementHandler] attributes on ListView/TableView are compiled for #if WINDOWS || IOS || MACCATALYST || TIZEN || ANDROID, and the Compatibility renderers do exist on Windows (Compatibility/Handlers/ListView/Windows/ListViewRenderer.cs, .../TableView/Windows/TableViewRenderer.cs). However this BuiltInHandlerTypes data only asserts their resolution under #if ANDROID || IOS || MACCATALYST, so VariousControlsGetCorrectHandler never exercises the new Windows attribute path for these two controls. Consider adding Windows entries so the Windows resolution is covered. (Similarly, ShellItem/ShellSection carry [ElementHandler] under #if WINDOWS || TIZEN but are asserted only under #if WINDOWS.) The TIZEN branches are dormant while Tizen TFMs are disabled, so the Windows gap is the only active one. Not blocking.

@kubaflo

kubaflo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

✅ LGTM — no blocking issues found

Re-review verdict: LGTM (confidence: medium)

Re-review after your update: the prior blocking issue is fixed, and the one remaining item is a non-blocking test-coverage nit.

Fixed (prior NEEDS_CHANGES)

  • ViewCell / SwitchCell [ElementHandler] TIZEN guard — resolved. At HEAD (86ee494):
    • SwitchCell.cs: #if WINDOWS || ANDROID || IOS || MACCATALYST (no TIZEN)
    • ViewCell.cs: #if WINDOWS || IOS || MACCATALYST || ANDROID (no TIZEN)
    • These now match the sibling cells (Cell/EntryCell/ImageCell/TextCell) and the renderers' real platform availability, so the Tizen CS0246 risk is gone.

Still open

  • None blocking.
  • Non-blocking (test coverage)HostBuilderHandlerTests.BuiltInHandlerTypes: ListView/TableView carry [ElementHandler] for WINDOWS || IOS || MACCATALYST || TIZEN || ANDROID (Windows renderers exist), but the new test data asserts them only under #if ANDROID || IOS || MACCATALYST, leaving the Windows attribute path unverified. ShellItem/ShellSection are WINDOWS || TIZEN in source but asserted only under #if WINDOWS. TIZEN branches are dormant (Tizen TFMs disabled), so only the Windows gap is active. See inline comment.

Resolving the split

  • gpt-5.5 LGTM, opus-4.6 LGTM, gemini APPROVED (1 non-blocking nit) — all correct against the code.
  • opus-4.8 NEEDS_CHANGES (2 findings) — both are stale false positives: they re-flag the TIZEN guard on ViewCell/SwitchCell, which is already removed at HEAD (verified above). Its summary also claims iOS-TrimFull/AOT-macOS CI legs are failing; that is not reflected in the current checks (see below).
  • gemini's coverage nit is real and code-confirmed, but explicitly optional and non-blocking — kept as a single inline suggestion.

CI

Pending, no failing checks: license/cla pass; maui-pr and all build/Helix legs pending; Build Analysis pending; add-dogfood-comment pass; Bump global.json skipping. No PR-failing legs in the current check set (the new commit's CI is still running). Recommend confirming maui-pr (incl. iOS Release/TrimFull and AOT macOS legs) goes green before merge.

Multi-model review (gpt-5.5 · opus-4.8 · opus-4.6 · gemini-3.1-pro). Comments only — not a formal approval.

simonrozsival and others added 2 commits June 15, 2026 15:13
Remove the element-side remap interface and use per-type Interlocked gates invoked during handler attribute resolution.

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

Copy link
Copy Markdown
Member Author

@kubaflo one more remapping cleanup pushed after the last follow-up.

We backed out the attribute-class dispatch experiment and kept remapping on the Controls virtual view types, which is where the old static constructors lived. The final shape is:

  • ElementHandler.SetVirtualView() calls an internal Controls remap hook before mapper updates.
  • Element exposes a virtual RemapForControls() on the Controls side.
  • Each control override uses its own Interlocked.CompareExchange field, returns immediately after the first run, calls base.RemapForControls() first, then applies the control-specific mapper changes.
  • No RuntimeHelpers.RunClassConstructor, no shared HashSet, no separate key/method pair, and no handwritten per-control handler-attribute classes.

I updated the PR description to reflect this final design.

Validation:

  • Core unit-test project build passed.
  • Controls.Core unit-test project build passed.
  • Focused Core HostBuilderHandlerTests passed.
  • Focused Controls HostBuilderHandlerTests and CommandDependsOnCommandParameterBeforeHandlerAttached coverage passed.

Keep mapper remapping on Controls virtual views with per-type Interlocked gates and remove the shared HashSet remap state.

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

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review verdict: NEEDS_CHANGES (confidence: high)

Re-review of the new commit ea04ec8 ("Trigger Controls remaps from handler attributes"). The prior Tizen fix still holds, but this commit introduces a new regression: Controls mapper remaps no longer run for DI-registered handler overrides.

New commit: regressed

  • gpt-5.5's finding is CONFIRMED (real regression), not a false positive. MauiHandlersFactory.cs:119RemapForControls() is now triggered only from FindElementHandlerAttribute() on the [ElementHandler] path (step 3). GetHandler()/GetHandlerType() return at step 1 (exact DI) or step 2 (assignable DI) before that path, so a view type that has a DI registration never gets remapped.
  • This same commit deleted the attach-time hook that previously covered all paths: IControlsMapperRemappable.cs was removed and ElementHandler.SetVirtualView's ControlsMapperRemapper.EnsureRemapped(...) call was deleted. That hook was keyed on the view, so it fired for every handler attach — including DI handlers. The parent commit 86ee494 (the prior LGTM HEAD) still had it.
  • Concrete impact: AddHandler<Button, ButtonHandler>() (the override pattern documented in docs/design/HandlerResolution.md:26) or any CustomButtonHandler : ButtonHandler that uses the inherited static ButtonHandler.Mapper (its default ctor passes that static mapper to base) never triggers Button.RemapForControls(). So Controls-only mappings — ContentLayout, TextTransform, Text, LineBreakMode, plus iOS/Android/Windows platform specifics — silently stop being applied. This directly contradicts the author's stated requirement in-thread: "Any custom handler registered via DI should still work."
  • One inline comment posted on the added line MauiHandlersFactory.cs:119 with a suggested fix (also invoke the view type's ElementHandlerAttribute.RemapForControls() when a DI registration wins, preserving DI precedence for the handler instance).

Why the other models missed it

  • opus-4.8 reviewed the parent commit (86ee494, "Decouple command dependencies from mapper remaps"), which still had the SetVirtualView hook — so its LGTM does not cover ea04ec8's change.
  • opus-4.6 described ea04ec8's architecture correctly but did not trace the DI-resolution → static-mapper interaction (it concluded "mappers are only mutated by their owning type" without noting the owning type's remap never fires under DI).
  • gemini APPROVED with one malformed/empty coverage finding (ignored).

Confirmed fixed (prior NEEDS_CHANGES)

  • ViewCell / SwitchCell [ElementHandler] TIZEN guard — still fixed at HEAD. All six cells guard on #if WINDOWS || ANDROID || IOS || MACCATALYST (no TIZEN), matching the real renderer availability. The CS0246 risk is gone.

Not duplicating prior comments

This DI-skip regression is new to ea04ec8 and was not present at the prior LGTM HEAD (86ee494 remapped on attach). It is distinct from the already-fixed prior findings (Material3 routing, uncached reflection, SwipeItemView guard, DI base-type ordering, MissingMethodException, GetCollection obsoletion, Tizen guard).

CI

Not PR-failing, but not yet green: license/cla and add-dogfood-comment pass; maui-pr and all build/Helix legs pending; Build Analysis pending; Bump global.json skipping. No failing legs in the current set. The fix above should be validated against maui-pr (incl. iOS Release/TrimFull and AOT macOS legs) plus a unit test asserting a DI-registered override still receives Controls mappings.

var elementHandlerAttribute = type.GetCustomAttribute<ElementHandlerAttribute>();
if (elementHandlerAttribute is not null)
{
return elementHandlerAttribute;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Controls remaps are skipped for DI-registered handler overrides. RemapForControls() is now reached only here, inside FindElementHandlerAttribute() on the [ElementHandler] resolution path (step 3). But GetHandler() returns earlier at step 1 (exact DI registration, lines 26-30) or step 2 (assignable DI, lines 35-39), and GetHandlerType() mirrors that same order — so whenever a view type has a DI registration, this remap never runs for it. This commit (ea04ec8) also removed the ElementHandler.SetVirtualView hook (if (VirtualView is IControlsMapperRemappable) ControlsMapperRemapper.EnsureRemapped(...)) that previously fired on every handler attach, including DI-registered handlers. Net effect: AddHandler<Button, ButtonHandler>() (the override pattern documented in docs/design/HandlerResolution.md) or any CustomButtonHandler : ButtonHandler that uses the inherited static ButtonHandler.Mapper never triggers Button.RemapForControls(), so Controls-only mappings (ContentLayout, TextTransform, Text, LineBreakMode, plus the iOS/Android/Windows platform specifics) silently stop being applied. That contradicts the stated design goal that “any custom handler registered via DI should still work,” and it is a behavior change versus the parent commit 86ee494, which still remapped on attach. Consider running the requested view type’s ElementHandlerAttribute.RemapForControls() even when a DI registration wins (e.g. resolve the attribute for the view type and invoke its remap before returning the DI handler), while preserving DI precedence for the handler instance itself.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. The ea04ec8f18 factory/attribute-dispatch approach was wrong for exactly this reason: DI could win before attribute lookup, so Controls remaps could be skipped. We backed that out and kept remapping on the virtual view attach path instead. Current head calls the internal Controls remap hook from ElementHandler.SetVirtualView(), so it runs regardless of whether the handler came from exact DI, assignable DI, [ElementHandler], or fallback. Each remappable Controls type owns its own Interlocked.CompareExchange gate and calls base.RemapForControls() before applying its own mappings. I also added regression coverage in d6697750f7: DiRegisteredHandlerOverrideStillRunsControlsMapperRemapOnAttach resolves a handler through a DI override that wins over [ElementHandler], attaches the remappable view via SetVirtualView, and verifies the Controls remap still runs. Validation: Core.UnitTests build passed and the focused HostBuilderHandlerTests run passed (34 tests).

simonrozsival and others added 3 commits June 15, 2026 16:06
Verify Controls mapper remapping still runs when a handler is resolved through a DI override instead of ElementHandlerAttribute.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align remap method bodies with the previous mapper static constructor bodies after switching to early-return guards.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the command dependency static constructors back to the mapper partial files to reduce churn in the main control files.

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

kubaflo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

✅ LGTM — no blocking issues found

PR #29952 — [net11.0] Trimmable element handlers

Verdict: LGTM (confidence: high) — HEAD a80e9265

Re-review: The prior blocking DI-handler RemapForControls regression is FIXED. The author reverted the offending change and added regression coverage, so the model split (2 NC / 2 LGTM) collapses once the actual code at this HEAD is read.

Is the DI-handler regression fixed or still present? — FIXED

The prior NEEDS_CHANGES was: RemapForControls() was reached only on the [ElementHandler] attribute path inside MauiHandlersFactory.FindElementHandlerAttribute() (~line 119), so handlers resolved via exact/assignable DI registration (AddHandler<Button, ButtonHandler>()) returned early from GetHandler/GetHandlerType and never ran the Controls remap.

Evidence at a80e9265 that this is resolved:

  1. MauiHandlersFactory.cs has ZERO RemapForControls calls (grep -c = 0). Line 119 is now just return elementHandlerAttribute; inside the BaseType-walk loop of FindElementHandlerAttribute() — it no longer triggers any remap.
  2. The trigger is back on the instance path: ElementHandler.SetVirtualView() (src/Core/src/Handlers/Element/ElementHandler.cs:56-58) runs
    if (VirtualView is IControlsMapperRemappable remappable) remappable.RemapForControls();
    on every handler attach — independent of whether the handler came from exact DI (step 1), assignable DI (step 2), the [ElementHandler] attribute (step 3), or the IContentView fallback (step 4).
  3. Branch history is an explicit remediation: ea04ec8 "Trigger Controls remaps from handler attributes" (the regression) -> 78ed6985 "Revert 'Trigger Controls remaps from handler attributes'" -> 22a55999 "Simplify Controls mapper remap hook" -> d6697750 "Add DI remap regression coverage" -> a80e9265 (HEAD).
  4. Dedicated regression test: DiRegisteredHandlerOverrideStillRunsControlsMapperRemapOnAttach (src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs:429) registers a DI override (AddHandler<AttributedRemappableViewStub, AlternateAttributedViewHandlerStub>()), resolves the DI handler, asserts RemapCount == 0 pre-attach, then asserts RemapCount == 1 after SetVirtualView — exactly the previously-broken scenario, now green.

Reconciling the model split

  • opus-4.8 (NC) is STALE — its own summary says "Re-review at HEAD ea04ec8 ... 97 files," i.e. it reviewed the pre-revert commit. Its MauiHandlersFactory.cs:119 finding does not reproduce at a80e9265.
  • gpt-5.5 (NC) explicitly agrees the DI regression is resolved ("remapping now runs from ElementHandler.SetVirtualView ... the added unit test covers that path"). Its NC is for a separate, new concern (below), not the adjudicated regression.
  • opus-4.6 (LGTM) and gemini (LGTM) both confirm the remap fires from SetVirtualView for all resolution paths.

Net on the adjudicated regression: fixed (code + test + 3 models; the lone dissent reviewed an obsolete commit).

Non-blocking note (not the adjudicated regression)

gpt-5.5 (error) and opus-4.6 (warning) independently observe a timing change: Controls remaps moved from eager (builder.RemapForControls() in SetupDefaults, during UseMauiApp) to lazy (first SetVirtualView). Because remaps use ReplaceMapping on the shared static ViewHandler.ViewMapper / per-control mappers (e.g. VisualElement.Mapper.cs:28 ReplaceMapping(nameof(BackgroundColor), ...)), an app that customizes one of the exact remapped keys after UseMauiApp but before first attach could now be overwritten on first attach. This is a real but narrow, advanced-extensibility edge case with no failing test/repro; opus-4.6 — which also found it — rated it non-blocking and recommended documenting the timing change. Treated here as a follow-up doc/clarification item, not a merge blocker, and orthogonal to the DI regression under adjudication.

CI

Not a blocker: license/cla pass, add-dogfood-comment pass; maui-pr, Build Analysis, and Helix Unit Tests (Win Debug/Release) are pending (not failing) on buildId 1464689.

Multi-model review (gpt-5.5 · opus-4.8 · opus-4.6 · gemini-3.1-pro). Comments only — not a formal approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-architecture Issues with code structure, SDK structure, implementation details copilot platform/android platform/ios platform/macos macOS / Mac Catalyst platform/windows

Projects

Status: Changes Requested

Development

Successfully merging this pull request may close these issues.

10 participants