[net11.0] Expose safe area contract for custom views - #37750
Conversation
Make the per-edge safe area interfaces and shared bindable-property plumbing reusable outside MAUI. Keep platform inset reporting internal, migrate built-in controls, and add custom-view regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 37750Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 37750" |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
This PR exposes public per-edge safe-area contracts for custom MAUI views and native hosts, adds shared plumbing, and preserves internal iOS inset reporting.
Changes:
- Publishes safe-area interfaces and edge lookup APIs.
- Migrates built-in controls to shared safe-area helpers.
- Adds API baselines and regression coverage.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Summary |
|---|---|
src/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt |
Records Core API additions. |
src/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt |
Records Core API additions. |
src/Core/src/PublicAPI/net/PublicAPI.Unshipped.txt |
Records Core API additions. |
src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt |
Records Core API additions. |
src/Core/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt |
Records Core API additions. |
src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt |
Records Core API additions. |
src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt |
Records Core API additions. |
src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt |
Records Core API additions. |
src/Core/src/Primitives/SafeAreaEdges.cs |
Exposes per-edge lookup. |
src/Core/src/Platform/iOS/MauiView.cs |
Uses the internal inset sink. |
src/Core/src/Core/ISafeAreaView2.cs |
Publishes the per-edge safe-area contract. |
src/Core/src/Core/ISafeAreaInsets.cs |
Defines internal inset reporting. |
src/Core/src/Core/ISafeAreaElement.cs |
Publishes the shared element contract. |
src/Controls/tests/Core.UnitTests/SafeAreaTests.cs |
Critical (1 vote): the private CustomSafeAreaView has a non-public constructor, causing Activator.CreateInstance(Type) to throw MissingMethodException. |
src/Controls/src/Core/ScrollView/ScrollView.cs |
Migrates safe-area behavior. |
src/Controls/src/Core/SafeAreaElement.cs |
Adds shared safe-area property plumbing. |
src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt |
Records Controls API additions. |
src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt |
Records Controls API additions. |
src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt |
Records Controls API additions. |
src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt |
Records Controls API additions. |
src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt |
Records Controls API additions. |
src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt |
Records Controls API additions. |
src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt |
Records Controls API additions. |
src/Controls/src/Core/Page/Page.cs |
Implements inset handling. |
src/Controls/src/Core/Layout/Layout.cs |
Migrates safe-area behavior. |
src/Controls/src/Core/ContentView/ContentView.cs |
Migrates safe-area behavior. |
src/Controls/src/Core/ContentPage/ContentPage.cs |
Migrates safe-area behavior. |
src/Controls/src/Core/Border/Border.cs |
Migrates safe-area behavior. |
Suppressed comments (4)
src/Controls/src/Core/ContentPage/ContentPage.cs:174
HasExplicitSafeAreaEdgesnow correctly usesIsSafeAreaEdgesSet, but the adjacent per-edge resolver still usesIsSet.IsSettreats default-value creation as set, so merely readingSafeAreaEdgesfirst causes this method to skip the iOSIgnoreSafeAreafallback and return the defaultNoneinstead. UseSafeAreaElement.IsSafeAreaEdgesSet(this)here as well and cover the read-then-resolve sequence.
bool ISafeAreaView2.HasExplicitSafeAreaEdges => SafeAreaElement.IsSafeAreaEdgesSet(this);
src/Controls/src/Core/Layout/Layout.cs:376
- Please regenerate the profiled AOT artifacts for this rename. The checked-in
maui.aotprofile.txtandmaui-sc.aotprofile.txtstill listLayout/ScrollView:ISafeAreaElement.SafeAreaEdgesDefaultValueCreator, while these implementations are nowGetDefaultSafeAreaEdges;Microsoft.Maui.Controls.targetsimports the corresponding binary profiles for Android, so these calls will no longer match the profiled methods (and the profile tool may report missing methods).
SafeAreaEdges ISafeAreaElement.GetDefaultSafeAreaEdges()
{
return SafeAreaEdges.Container;
}
src/Controls/src/Core/ScrollView/ScrollView.cs:553
- This renames the explicit
ISafeAreaElementimplementation used byLayoutandScrollView, but the checked-in profiled-AOT lists still referenceMicrosoft.Maui.ISafeAreaElement.SafeAreaEdgesDefaultValueCreator(inmaui.aotprofile.txtandmaui-sc.aotprofile.txt). Regenerate those profile outputs so they referenceGetDefaultSafeAreaEdges; otherwise the profiles are stale and no longer describe methods in the assembly.
SafeAreaEdges ISafeAreaElement.GetDefaultSafeAreaEdges()
src/Controls/tests/Core.UnitTests/SafeAreaTests.cs:287
- These assertions verify the public contracts and property mapping only; they never create a handler or send insets through the iOS
MauiView/Android inset-listener paths. A regression in the platform-sideISafeAreaView2lookup or listener refresh would therefore still pass these tests even though a direct customViewno longer receives the advertised per-edge behavior. Add focused device coverage for the custom view on Android and iOS, or include the probe as an automated test.
public void CustomView_CanReuseSafeAreaEdgesContract()
{
var view = new CustomSafeAreaView();
var safeAreaView = (ISafeAreaView2)view;
|
Validated the review findings against the current head:
This branch is actively owned in another worktree with recent source/build activity, so the monitor is reply-only here: I did not edit or push, and the actionable thread remains open for the owning agent. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
@copilot-pull-request-reviewer addressed the validated feedback in ecf8393: corrected ContentPage explicit-edge detection after default-value reads, regenerated both binary/text AOT profiles for the renamed explicit implementations, fixed private test-view construction, and added Android/iOS custom-view handler coverage. Focused validation passed (64 unit, 40 iOS View, 5 iOS Page, and 51 Android View tests). This is ready for re-review — thanks! |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 36 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
src/Controls/src/Core/SafeAreaElement.cs:24
- This newly reusable property is not wired to safe-area invalidation on Mac Catalyst: the existing
ViewHandlermapper is guarded by#if ANDROID || IOS, whileMauiViewandViewHandler.iOS.csalso compile for Mac Catalyst. Consequently, changing a custom view'sSafeAreaEdgesafter its handler is connected does not invalidate the safe-area layout there. RegisterMapSafeAreaEdgesforMACCATALYSTas well and add a post-handler-change regression test.
public static readonly BindableProperty SafeAreaEdgesProperty =
BindableProperty.Create(nameof(ISafeAreaElement.SafeAreaEdges), typeof(SafeAreaEdges), typeof(ISafeAreaElement), SafeAreaEdges.Default,
defaultValueCreator: SafeAreaEdgesDefaultValueCreator);
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
@copilot-pull-request-reviewer[bot] addressed both latest findings in dc737cb: descendant safe-area caches are invalidated when an ancestor strategy changes, and the mapper now runs on Mac Catalyst. Focused View device tests passed 41/41 on both iOS and Mac Catalyst. This is ready for re-review — thanks! |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 38 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-sc.aotprofile.txt:3287
- The text snapshot now records
GetDefaultSafeAreaEdges, but the pairedmaui-sc.aotprofilebinary is not regenerated.Microsoft.Maui.Controls.targetsimports that binary for Android profiled AOT, so consumers will still ship a profile containing the removed interface method and will not profile the new calls. Please rerun the documentedRecordtarget formaui-scand commit the regenerated binary with this snapshot.
src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui.aotprofile.txt:2594 - The text snapshot now records
GetDefaultSafeAreaEdges, but the pairedmaui.aotprofilebinary is not regenerated.Microsoft.Maui.Controls.targetsimports that binary for Android profiled AOT, so consumers will still ship a profile containing the removed interface method and will not profile the new calls. Please rerun the documentedRecordtarget formauiand commit the regenerated binary with this snapshot.
Microsoft.Maui.SafeAreaEdges Microsoft.Maui.Controls.Layout:Microsoft.Maui.ISafeAreaElement.GetDefaultSafeAreaEdges ()
|
Thanks for the re-review. The two suppressed AOT-profile notes are already satisfied by ecf8393: that commit changed both |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: de9c7c01-82c4-42fd-9ab7-882279aadd15
|
Final MauiBot follow-up is pushed at
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review
Posted by an independent reviewer process: three independent model reviewers plus the repository's own maui-expert-reviewer domain specialist analyzed this PR in parallel against the full diff and source at this exact commit, with a follow-up dispute round for contested findings. Only claims that survived cross-model consensus and direct source verification are reported below.
Scope: Full PR reviewed, with focused scrutiny on the 2 commits/4 files changed since the prior review at 490a33f8 (MauiView.cs, MauiScrollView.cs, the new safe-area-ios.instructions.md guidance, and the 2 new ViewTests.iOS.cs device tests).
Findings
MauiView.ValidateSafeArea() now unconditionally sets _safeAreaInvalidated = true and clears _parentHandledSafeAreaEdges on every LayoutSubviews() call while the keyboard is visible and this view's Bottom edge is SoftInput — regardless of whether the frame or keyboard geometry actually changed since the last pass. This defeats the ancestor-chain cache (GetParentHandledSafeAreaEdges) for the entire duration the keyboard is shown, causing O(view-count × ancestor-depth) recomputation on every layout pass in deeper hierarchies or forms with multiple SoftInput fields. Consider gating the forced invalidation on an actual pixel-level change to frame/keyboard overlap rather than unconditionally. Flagged by 3/3 (2 independent reviewers + repo domain specialist, rated major).
GetAdjustedSafeAreaInsets, viewBottomY is computed via Superview.ConvertRectToView(Frame, Window) (the window's own local/bounds coordinate space) but compared directly against keyboardTopY = _keyboardFrame.Y, where _keyboardFrame is taken as-is from UIKeyboard.FrameEndUserInfoKey — which Apple's own documentation says is in screen coordinates and explicitly recommends converting via ConvertRect(from/to)View before use. The two values only coincide when the window's frame origin is at the screen origin (the common single full-screen-window case). For a window not positioned at the screen origin — iPad Split View/Slide Over, multi-scene/external-display windows, or Stage Manager — this can misjudge the keyboard overlap. Note this file compiles for both iOS and MacCatalyst TFMs. Flagged by 1/3 reviewers, independently verified against source and Apple's documented UIKeyboardFrameEndUserInfoKey semantics.
IsSoftInputHandledByParent (consumed by KeyboardAutoManagerScroll.AdjustPositionDebounce to decide whether to skip its own auto-scroll-to-reveal-focused-view logic) checks only IsModernSafeAreaView + SafeAreaEdges.IsSoftInput(...) on an ancestor, but does not check that ancestor's own RespondsToSafeArea(). A MauiView ancestor configured with Bottom = SoftInput that is itself nested inside a UIScrollView has RespondsToSafeArea() == false (by design, since the scroll view handles insets itself) and therefore does not actually apply any bottom padding (_appliesSafeAreaAdjustments is false) — yet IsSoftInputHandledByParent still reports "handled," incorrectly suppressing KeyboardAutoManagerScroll's auto-scroll fallback. A focused Entry in that configuration could be left obscured by the keyboard with neither mechanism protecting it. Flagged by 1/3 reviewers, independently verified against source (RespondsToSafeArea/_scrollViewDescendant).
InvalidateSafeArea(UIView platformView) broadcasts _safeAreaInvalidated = true down the entire native subtree exactly once, at keyboard show/hide (OnKeyboardWillShow/ClearKeyboardState). Only a view whose own Bottom edge is SoftInput gets the new per-layout-pass re-invalidation added in ValidateSafeArea(). A plain, non-SoftInput descendant (e.g. Container on Bottom) nested under a SoftInput ancestor consumes its one-time broadcast invalidation immediately after the notification, then does not get re-validated on later layout passes as the ancestor's own multi-pass convergence settles its final overlap (the exact multi-pass convergence the new tests exercise for SoftInput+SoftInput nesting). This can leave such a descendant's "ancestor handles Bottom" decision frozen at a stale value — wrongly suppressed or wrongly double-padded — for a scenario not exercised by the new tests. Flagged by the repo domain specialist (major), independently verified via direct source trace of the invalidation call graph.
💡 Testing — The two new device tests (ParentAndChildKeyboardSafeAreasDoNotDoublePadArrangedChild, ParentAndChildKeyboardSafeAreasProtectOverflowingChild) manually reassign the child's native Frame to the parent's LastArrangeBounds rather than letting a real cross-platform container's arrange (PlatformArrangeHandler synchronously setting Center/Bounds) drive that repositioning. This is a faithful stand-in for the real mechanism (confirmed via the actual CrossPlatformArrange → PlatformArrangeHandler call chain), but it means a regression in the real synchronous-arrange-before-child-layout ordering that these tests implicitly depend on for correctness would not be caught by them. Flagged by 1 reviewer originally; the same underlying test-harness gap was independently reconfirmed by two reviewers during dispute-round analysis of a related (ultimately discarded) claim — see below.
💡 Architectural Layer Placement — Element.OnBindablePropertySet (shared lifecycle hook for every BindableProperty on every Element) hardcodes a ReferenceEquals(property, SafeAreaElement.SafeAreaEdgesProperty) special case to force a handler update on specificity-only changes. This couples one Controls-layer feature's setter-specificity semantics into common core infrastructure with no general extension point — a future property needing the same "notify on specificity change without a value change" behavior can't reuse this without another hardcoded reference-equality branch here. Flagged by the repo domain specialist (moderate), independently verified against source.
💡 Edge case (self-healing) — KeyboardAutoManagerScroll.Disconnect() removes only its own NSNotificationCenter observers; it doesn't affect MauiView's independent keyboard subscription. If an app calls Disconnect() before the keyboard opens, and a view then dynamically switches to SafeAreaEdges.SoftInput while the keyboard is already showing, the IsKeyboardShowing/KeyboardFrame static-state rehydration fallback in SubscribeToKeyboardNotifications won't fire for that view until the next full keyboard show/hide cycle. Narrow precondition (Disconnect() must be called explicitly, plus a same-session dynamic edge change), self-heals on the next keyboard cycle — flagged for completeness, not blocking. Flagged by 1/3 reviewers, partially independently verified (call-site confirmed; not inline-eligible since Disconnect() itself is unchanged by this PR).
Discarded claims
- "Transient/compounding double-padding when both a parent and nested child are
SoftInput" — one reviewer argued the new tests show a real one-pass window where both levels reserve full keyboard padding simultaneously. This was sent through a dispute round; both other reviewers independently disagreed, citing the same concrete evidence: a real container'sCrossPlatformArrangesynchronously repositions the child's nativeCenter/BoundsviaPlatformArrangeHandleras part of the same call that reserves the parent's inset, and UIKit's top-downlayoutSubviewsordering means the child always computes its own overlap against its already-repositioned frame. The "simultaneous 50pt reservation" the test showed at one point is an artifact of the test's native-only (AddSubview) wiring bypassing a real container, not a producible production state. Discarded as a correctness finding (see the retained Testing finding above for the legitimate, narrower observation about test realism). - A pre-existing gap in
net-tizen'sPublicAPI.Unshipped.txt(missingSafeAreaEdges.Container.get) was investigated and confirmed present at this PR's actual base (via the authoritative PR diff, not a stale localgit diff) — not introduced by this PR, out of scope. ContentPage.GetDefaultSafeAreaEdges()returningNoneeven though its effective computed default isContaineron iOS/MacCatalyst — inert in practice sinceISafeAreaViewStrategytakes precedence for built-in controls; uncorroborated and no concrete consequence identified. Discarded.- An open question about whether
MauiScrollViewcould lag on a parent's overlap change absent a keyboard notification — the reviewer who raised it could not construct a concrete trigger not already covered by the subtreeInvalidateSafeAreapropagation. Discarded as unverifiable.
Test coverage
The two new device tests target the exact incremental logic change (removal of the IsSoftInputHandledByParent guard inside GetAdjustedSafeAreaInsets) and are internally deterministic (keyboard notifications are handled synchronously in this harness). See the Testing finding above for a gap in how closely they mirror real container-driven arrange.
Methodology
3 independent reviewers with adversarial consensus + repo domain specialist (maui-expert-reviewer). One disputed finding went through a full follow-up round; both other reviewers independently reached the same conclusion, refuting it with source-grounded evidence, so it was discarded rather than included. All retained findings above were independently verified against the source at this exact commit.
Adversarial review — automated, independent reviewer process. event: COMMENT; no approval or change request is implied.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: de9c7c01-82c4-42fd-9ab7-882279aadd15
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: de9c7c01-82c4-42fd-9ab7-882279aadd15
|
Final review and merge-conflict follow-up is pushed at
All eight outstanding review threads are resolved and the PR description now records the merged-head evidence. |
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: de9c7c01-82c4-42fd-9ab7-882279aadd15
|
Additional exact-head hardening is pushed at The keyboard-overlap cache now compares absolute values after device-pixel rounding. The prior adjacent-delta comparison could update its baseline after every sub-pixel move and therefore miss a sequence whose cumulative motion crossed a physical pixel boundary. The strengthened cache regression first proves an unchanged/sub-pixel pass performs zero ancestor reads, then advances by two |
This comment has been minimized.
This comment has been minimized.
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review
An independent reviewer process reviewed the full GitHub-scoped diff and exact source at 104f8146610f38465118de78bdd2c2f9336e4751.
Finding
One exact-diff iOS regression survived adversarial consensus: a floating or undocked keyboard can produce false SoftInput bottom padding for a laterally disjoint view. See the inline comment for the concrete path and fix. Consensus: 2/3 reviewers after dispute.
Prior review reconciliation
The wrong secondary-display scale and full-subtree invalidation costs were independently rediscovered, but MauiBot already documented them in issue comment 5420344625; they are not duplicated here. Earlier findings about coordinate conversion, active ancestors, descendant convergence, explicit defaults, and specificity propagation are addressed in the current code.
Finalization
The title accurately describes the new public Safe Area contract, and the detailed description matches the implementation, compatibility model, API baselines, AOT profiles, and test scope.
Methodology
3 independent reviewers with adversarial consensus + a separate MAUI domain specialist. Review event: COMMENT; no approval or change request is implied.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 5 findings
See inline comments for details.
Handle floating keyboard geometry, preserve UIKit-adjusted scroll insets, and avoid redundant ancestor work while retaining layout-order correctness. Add focused device and specificity regressions for every exact-head review finding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: de9c7c01-82c4-42fd-9ab7-882279aadd15
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: de9c7c01-82c4-42fd-9ab7-882279aadd15
AI Review Summary
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After FixGate Result:
|
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🧪 SafeAreaTests SafeAreaTests |
🛠️ BUILD ERROR | ✅ PASS — 18s |
📄 SafeAreaEdgesTests SafeAreaEdgesTests |
🛠️ BUILD ERROR | ✅ PASS — 17s |
📄 Tests Tests |
🛠️ BUILD ERROR | ✅ PASS — 125s |
📱 PageTests (ReadingDefaultSafeAreaEdgesPreservesLegacySafeAreaFallback) Category=Page |
🛠️ BUILD ERROR | |
📱 ScrollViewHandlerTests (LegacySafeAreaViewWithoutModernContractRemainsEdgeToEdge) Category=ScrollView |
🔴 Without fix — 🧪 SafeAreaTests: 🛠️ BUILD ERROR · 17s
Error-relevant lines (filtered from the build log):
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(577,35): error CS0539: 'SafeAreaTests.DerivedSafeAreaContentPage.GetDefaultSafeAreaEdges()' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(583,26): error CS0539: 'SafeAreaTests.DerivedDefaultSafeAreaContentPage.HasExplicitSafeAreaEdges' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(584,35): error CS0539: 'SafeAreaTests.DerivedDefaultSafeAreaContentPage.GetDefaultSafeAreaEdges()' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(597,26): error CS0539: 'SafeAreaTests.CustomNoneSafeAreaView.HasExplicitSafeAreaEdges' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(599,35): error CS0539: 'SafeAreaTests.CustomNoneSafeAreaView.GetDefaultSafeAreaEdges()' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(612,26): error CS0539: 'SafeAreaTests.CustomMixedSafeAreaView.HasExplicitSafeAreaEdges' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(614,35): error CS0539: 'SafeAreaTests.CustomMixedSafeAreaView.GetDefaultSafeAreaEdges()' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(497,43): error CS0535: 'SafeAreaTests.CustomSafeAreaView' does not implement interface member 'ISafeAreaElement.SafeAreaEdgesDefaultValueCreator()' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(524,43): error CS0535: 'SafeAreaTests.CustomSafeAreaPage' does not implement interface member 'ISafeAreaElement.SafeAreaEdgesDefaultValueCreator()' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(587,47): error CS0535: 'SafeAreaTests.CustomNoneSafeAreaView' does not implement interface member 'ISafeAreaElement.SafeAreaEdgesDefaultValueCreator()' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(602,48): error CS0535: 'SafeAreaTests.CustomMixedSafeAreaView' does not implement interface member 'ISafeAreaElement.SafeAreaEdgesDefaultValueCreator()' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
🟢 With fix — 🧪 SafeAreaTests: PASS ✅ · 18s
(no coded error found; showing last 1200 chars)
Default [< 1 ms]
Passed SafeAreaEdgesTypeConverter_ConvertFromFourValues [< 1 ms]
Passed IsSafeAreaEdgesSet_NullBindableThrows [< 1 ms]
Passed SafeAreaEdgesTypeConverter_ConvertFromInvalidValue_ThrowsException [< 1 ms]
Passed SafeAreaEdges_UniformConstructor_AppliesAllEdges [< 1 ms]
Passed StackLayouts_RespectUserSettings [< 1 ms]
Passed GetEdgeValue_TwoValues_AppliesCorrectly [< 1 ms]
Passed CustomView_DefaultRegionsUseDeclaredEdges [< 1 ms]
Passed GetEdgeValue_FourValues_AppliesCorrectly [< 1 ms]
Passed SafeAreaEdgesTypeConverter_ConvertFromInvalidLength_ThrowsException [< 1 ms]
Passed Layout_ImplementsISafeAreaView [< 1 ms]
Passed SafeAreaEdges_AllEnumValues_WorkCorrectly [< 1 ms]
[xUnit.net 00:00:00.68] Finished: Microsoft.Maui.Controls.Core.UnitTests
Passed CustomPage_CanOverrideInheritedSafeAreaStrategy [< 1 ms]
Passed CustomView_CanReuseSafeAreaEdgesContract [< 1 ms]
Passed StackLayout_HorizontalOrientation_RespectsDirectProperty_RTL [< 1 ms]
Passed GetEdges_DefaultValue_ReturnsDefault [< 1 ms]
Passed HasExplicitSafeAreaEdges_StyleValueCountsAsExplicit [1 ms]
Test Run Successful.
Total tests: 77
Passed: 77
Total time: 0.8946 Seconds
🔴 Without fix — 📄 SafeAreaEdgesTests: 🛠️ BUILD ERROR · 9s
Error-relevant lines (filtered from the build log):
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/SafeAreaEdgesTests.xaml.cs(91,25): error CS0539: 'CustomSafeAreaElement.HasExplicitSafeAreaEdges' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/SafeAreaEdgesTests.xaml.cs(93,34): error CS0539: 'CustomSafeAreaElement.GetDefaultSafeAreaEdges()' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/SafeAreaEdgesTests.xaml.cs(81,52): error CS0535: 'CustomSafeAreaElement' does not implement interface member 'ISafeAreaElement.SafeAreaEdgesDefaultValueCreator()' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj]
🟢 With fix — 📄 SafeAreaEdgesTests: PASS ✅ · 17s
(no coded error found; showing last 1200 chars)
aui.Controls.Xaml.UnitTests
[xUnit.net 00:00:03.49] Starting: Microsoft.Maui.Controls.Xaml.UnitTests
Passed FourValueConversions(inflator: SourceGen) [30 ms]
Passed FourValueConversions(inflator: XamlC) [< 1 ms]
Passed FourValueConversions(inflator: Runtime) [30 ms]
Passed SingleValueConversions(inflator: XamlC) [< 1 ms]
Passed SingleValueConversions(inflator: SourceGen) [< 1 ms]
Passed SingleValueConversions(inflator: Runtime) [1 ms]
Passed TwoValueConversions(inflator: XamlC) [< 1 ms]
Passed TwoValueConversions(inflator: Runtime) [1 ms]
Passed TwoValueConversions(inflator: SourceGen) [< 1 ms]
[xUnit.net 00:00:03.61] Finished: Microsoft.Maui.Controls.Xaml.UnitTests
Passed PropertyInflation_WorksWithAllEnumValues(inflator: XamlC) [1 ms]
Passed PropertyInflation_WorksWithAllEnumValues(inflator: Runtime) [5 ms]
Passed PropertyInflation_WorksWithAllEnumValues(inflator: SourceGen) [< 1 ms]
Passed ControlSpecificProperties(inflator: Runtime) [1 ms]
Passed ControlSpecificProperties(inflator: XamlC) [< 1 ms]
Passed ControlSpecificProperties(inflator: SourceGen) [< 1 ms]
Test Run Successful.
Total tests: 15
Passed: 15
Total time: 3.8015 Seconds
🔴 Without fix — 📄 Tests: 🛠️ BUILD ERROR · 7s
Error-relevant lines (filtered from the build log):
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/SafeAreaEdgesTests.xaml.cs(91,25): error CS0539: 'CustomSafeAreaElement.HasExplicitSafeAreaEdges' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/SafeAreaEdgesTests.xaml.cs(93,34): error CS0539: 'CustomSafeAreaElement.GetDefaultSafeAreaEdges()' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/SafeAreaEdgesTests.xaml.cs(81,52): error CS0535: 'CustomSafeAreaElement' does not implement interface member 'ISafeAreaElement.SafeAreaEdgesDefaultValueCreator()' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj]
🟢 With fix — 📄 Tests: PASS ✅ · 125s
(no coded error found; showing last 1200 chars)
(inflator: XamlC) [54 ms]
Passed ThrowOnInstanceProperty(inflator: SourceGen) [11 ms]
Passed ThrowOnInstanceProperty(inflator: Runtime) [< 1 ms]
Passed XStaticCanAccessInheritedStaticField(inflator: XamlC) [< 1 ms]
Passed XStaticCanAccessInheritedStaticField(inflator: SourceGen) [< 1 ms]
Passed XStaticCanAccessInheritedStaticField(inflator: Runtime) [4 ms]
Passed XStaticCanAccessInheritedStaticProperty(inflator: XamlC) [< 1 ms]
[xUnit.net 00:01:59.69] Finished: Microsoft.Maui.Controls.Xaml.UnitTests
Passed XStaticCanAccessInheritedStaticProperty(inflator: SourceGen) [< 1 ms]
Passed XStaticCanAccessInheritedStaticProperty(inflator: Runtime) [3 ms]
Passed XStaticCanAccessInheritedConstant(inflator: XamlC) [< 1 ms]
Passed XStaticCanAccessInheritedConstant(inflator: Runtime) [1 ms]
Passed XStaticCanAccessInheritedConstant(inflator: SourceGen) [< 1 ms]
Passed XStaticCanAccessDerivedClassMember(inflator: Runtime) [8 ms]
Passed XStaticCanAccessDerivedClassMember(inflator: XamlC) [< 1 ms]
Passed XStaticCanAccessDerivedClassMember(inflator: SourceGen) [< 1 ms]
Test Run Successful.
Total tests: 2123
Passed: 2115
Skipped: 8
Total time: 1.9982 Minutes
🔴 Without fix — 📱 PageTests (ReadingDefaultSafeAreaEdgesPreservesLegacySafeAreaFallback): 🛠️ BUILD ERROR · 37s
Error-relevant lines (filtered from the build log):
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Elements/View/ViewTests.cs(85,26): error CS0539: 'ViewTests.CustomSafeAreaView.HasExplicitSafeAreaEdges' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj::TargetFramework=net11.0-ios]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Elements/View/ViewTests.cs(87,35): error CS0539: 'ViewTests.CustomSafeAreaView.GetDefaultSafeAreaEdges()' in explicit interface declaration is not found among members of the interface that can be implemented [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj::TargetFramework=net11.0-ios]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Elements/View/ViewTests.cs(73,43): error CS0535: 'ViewTests.CustomSafeAreaView' does not implement interface member 'ISafeAreaElement.SafeAreaEdgesDefaultValueCreator()' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj::TargetFramework=net11.0-ios]
Build FAILED.
🟢 With fix — 📱 PageTests (ReadingDefaultSafeAreaEdgesPreservesLegacySafeAreaFallback): ⚠️ ENV ERROR · 61s
No log file found
🔴 Without fix — 📱 ScrollViewHandlerTests (LegacySafeAreaViewWithoutModernContractRemainsEdgeToEdge): ⚠️ ENV ERROR · 54s
No log file found
🟢 With fix — 📱 ScrollViewHandlerTests (LegacySafeAreaViewWithoutModernContractRemainsEdgeToEdge): ⚠️ ENV ERROR · 56s
No log file found
⚠️ Failure Details (7 tests)
- 🛠️ SafeAreaTests without fix: build failed before tests could run
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/SafeAreaTests.cs(519,26): error CS0539: 'SafeAreaTests.CustomSafeAreaView.HasExplicitSafeAreaEdges' in explicit interface declaration i...
- 🛠️ SafeAreaEdgesTests without fix: build failed before tests could run
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/SafeAreaEdgesTests.xaml.cs(91,25): error CS0539: 'CustomSafeAreaElement.HasExplicitSafeAreaEdges' in explicit interface declaration is ...
- 🛠️ Tests without fix: build failed before tests could run
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Xaml.UnitTests/SafeAreaEdgesTests.xaml.cs(91,25): error CS0539: 'CustomSafeAreaElement.HasExplicitSafeAreaEdges' in explicit interface declaration is ...
- 🛠️ PageTests (ReadingDefaultSafeAreaEdgesPreservesLegacySafeAreaFallback) without fix: build failed before tests could run
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Elements/View/ViewTests.cs(85,26): error CS0539: 'ViewTests.CustomSafeAreaView.HasExplicitSafeAreaEdges' in explicit interface declaration...
⚠️ ScrollViewHandlerTests (LegacySafeAreaViewWithoutModernContractRemainsEdgeToEdge) without fix:XHarness did not produce the expected fresh result 'testResults.xml' for requested class(es) 'Microsoft.Maui.DeviceTests.ScrollViewHandlerTests' (the target tests did not run).⚠️ PageTests (ReadingDefaultSafeAreaEdgesPreservesLegacySafeAreaFallback) with fix:XHarness did not produce the expected fresh result 'testResults.xml' for requested class(es) 'Microsoft.Maui.DeviceTests.PageTests' (the target tests did not run).⚠️ ScrollViewHandlerTests (LegacySafeAreaViewWithoutModernContractRemainsEdgeToEdge) with fix:XHarness did not produce the expected fresh result 'testResults.xml' for requested class(es) 'Microsoft.Maui.DeviceTests.ScrollViewHandlerTests' (the target tests did not run).
📁 Fix files reverted (43 files)
src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-blazor.aotprofilesrc/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-blazor.aotprofile.txtsrc/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-sc.aotprofilesrc/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-sc.aotprofile.txtsrc/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui.aotprofilesrc/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui.aotprofile.txtsrc/Controls/src/Core/BindableObject.cssrc/Controls/src/Core/BindableProperty.cssrc/Controls/src/Core/Border/Border.cssrc/Controls/src/Core/ContentPage/ContentPage.cssrc/Controls/src/Core/ContentView/ContentView.cssrc/Controls/src/Core/Element/Element.cssrc/Controls/src/Core/InputView/InputView.cssrc/Controls/src/Core/Layout/Layout.cssrc/Controls/src/Core/Page/Page.cssrc/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txtsrc/Controls/src/Core/SafeAreaElement.cssrc/Controls/src/Core/ScrollView/ScrollView.cssrc/Core/src/Core/ISafeAreaElement.cssrc/Core/src/Core/ISafeAreaView2.cssrc/Core/src/Handlers/View/ViewHandler.Android.cssrc/Core/src/Handlers/View/ViewHandler.cssrc/Core/src/Handlers/View/ViewHandler.iOS.cssrc/Core/src/Platform/Android/MauiWindowInsetListener.cssrc/Core/src/Platform/Android/SafeAreaExtensions.cssrc/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cssrc/Core/src/Platform/iOS/MauiScrollView.cssrc/Core/src/Platform/iOS/MauiView.cssrc/Core/src/Platform/iOS/SafeAreaPadding.cssrc/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
New files (not reverted):
src/Core/src/Core/ISafeAreaInsets.cssrc/Core/src/Core/ISafeAreaViewStrategy.cs
📋 Pre-Flight — Context & Validation
PR #37750 Pre-Flight
Context
- PR:
[net11.0] Expose safe area contract for custom views - Request: Issue [net11] Let custom views provide their own safe-area strategy — make the SafeAreaEdges API public (ISafeAreaView2), as ISafeAreaView was in net9 #37384 asks .NET 11 to let third-party views declare and expose the per-edge
SafeAreaEdgesstrategy that MAUI platform views already understand. The current publicISafeAreaViewis only an all-or-nothing legacy contract. - Target:
net11.0 - Local review commit:
d97b44ecc2bb0bfce31995d89397a99cf56efed0(PR #37750 squashed for review) - Live PR head observed during pre-flight:
79cdc7408d6140335f5fa27a9aeb999897cfb0f9 - Gate: Inconclusive because the existing gate could not build/run. It must not be rerun and is not evidence that the PR fix fails.
Existing Fix
The PR replaces the internal numbered contract with a public ISafeAreaElement contract and an internal strategy resolver, exposes reusable bindable-property plumbing and an effective-value accessor, preserves the shipped ISafeAreaView fallback, and routes Apple/Android safe-area handling through the common resolver. It also expands nested Apple safe-area and keyboard handling, ancestor suppression/cache invalidation, pixel-resolution comparisons, Android listener refresh, and public API baselines.
Direct inspection of HEAD^..HEAD found 56 changed files, +2,895/-534. The largest implementation areas are:
src/Core/src/Core/ISafeAreaElement.cs- new
src/Core/src/Core/ISafeAreaInsets.cs - new
src/Core/src/Core/ISafeAreaViewStrategy.cs - deleted
src/Core/src/Core/ISafeAreaView2.cs src/Core/src/Platform/iOS/MauiView.cssrc/Core/src/Platform/iOS/MauiScrollView.cs- Controls safe-area property implementations and public API baselines
- focused Controls/Core device, unit, and XAML tests
The approach already rejects layout-order-dependent ancestor caching and subtree invalidation pruning. Any alternative must use a different root-cause mechanism rather than relocating equivalent resolver or cache logic.
Test Surface
The primary iOS behavioral surface is the Controls View device-test category (the PR reports 62 iOS View tests and adds the bulk of its safe-area tests under ViewTests.iOS.cs):
pwsh .github/skills/run-device-tests/scripts/Run-DeviceTests.ps1 -Project Controls -Platform ios -TestFilter "Category=View"Only after that command passes, run the 13 mandated HostApp regression invocations in the order supplied by the caller: Issue35756 once, then Issue28986_ParentChildTest, Issue32586, Issue33595, and Issue33934 three times each. Any regression failure makes the candidate Fail.
Attempt Constraints
- One implementation/test pass and at most one focused correction/retest per candidate.
- Use only the primary command and mandated regressions; do not run the gate or a full suite.
- The try-fix baseline allow-list is authoritative. This PR adds production files, so each attempt must inspect
.github/.baseline-state.jsonand reportBlockedwithout editing ifNewFilesis non-empty. - The worktree contains pre-existing unrelated modifications and untracked files under
.github/scripts,.github/skills, andeng/scripts. Preserve them exactly; only the restore script may clean an attempt.
🔬 Code Review — Deep Analysis
Expert Evaluation — PR #37750
Verdict: REQUEST CHANGES
Confidence: Medium on the concrete correctness finding; low on overall runtime safety because the trusted Gate was inconclusive.
The public-contract refactor is broadly coherent: it replaces the internal numbered contract with public ISafeAreaElement, preserves the shipped ISafeAreaView fallback, keeps the effective strategy centralized, updates the applicable API baselines, and adds substantial focused coverage. The expert review nevertheless found one blocking iOS layout regression and several performance, compatibility, and API-design risks in behavioral changes bundled with the contract extraction.
Blocking finding
- Critical — nested scrolling safe areas can be applied twice:
MauiScrollView.UpdateContentInsets()removed the prior!IsParentHandlingSafeArea()suppression from_appliesSafeAreaAdjustments, but applies the replacement per-edge exclusion only whenSystemAdjustedContentInsetis zero or adjustment behavior isNever. In the UIKit-adjusted branch, a scrolling child under a safe-area-applying ancestor can retain the full system inset, subtract it again during measure/arrange, and reintroduce the layout-cycle/double-inset behavior previously fixed for [net10] iOS 18.6 crashing on navigating to a ContentPage with Padding set and Content set to a <Grid RowDefinitions="*,Auto"> with ScrollView on row 0 #33595. This must be corrected or disproved with a targeted regression test.
Additional expert findings
- Major performance/layout risks: whole-subtree recursive invalidation allocates and schedules layout for every descendant; invalidation is initiated from within layout and can fan out by depth; and the ancestor walk replaces a cached Boolean read with repeated UIKit and bindable-property strategy resolution.
- Moderate hot-path risk: keyboard overlap evaluation performs strategy resolution and UIKit coordinate conversion before the normal non-invalidated early return.
- Moderate compatibility risks: the new iOS and Android mapper guards exclude legacy-only views from measure/listener refresh paths without negative-case coverage, and mapper registration newly changes Mac Catalyst behavior without corresponding platform validation.
- Moderate API-design risk: an unset
ContentPagecan expose differentSafeAreaEdgesvalues through its public CLR property and the newly public interface, creating an enduring ambiguity between configured and effective values.
The raw inline findings are preserved in inline-findings.json as nine actionable comments (1 critical, 3 major, 5 moderate). The Gate result remains INCONCLUSIVE because its build/run was blocked; it is not counted as a regression-test failure and was not rerun.
🛠️ Try-Fix — Analysis & Comparison
PR #37750 — Try-Fix Final Aggregate
Candidates run: 2 of 2 (sequential; final)
Overall status: Blocked — both candidates stopped at the mandatory baseline boundary before implementation or validation.
Two-candidate summary
| Candidate | Distinct intended mechanism | Result | Changed files | Primary | Regressions | Review |
|---|---|---|---|---|---|---|
| 1 | Capability descriptor resolved/cached at handler connect | Blocked |
none | not run | 0/13 | 0 |
| 2 | Default per-edge member evolving existing public ISafeAreaView |
Blocked |
none | not run | 0/13 | 0 |
The PR uses a replacement public contract, property plumbing, effective resolver, centralized platform capability resolution, and Apple nesting/cache behavior. Candidate 1 instead proposed handler-connect capability discovery/caching. Candidate 2 instead proposed binary-compatible evolution of the existing public legacy contract through ordinary interface dispatch. Neither repeated another mechanism, but neither was implemented or tested.
Both runs independently encountered the same pre-existing dirty harness-owned .github/eng state, so the baseline script created no state. The PR's two added production files independently imply non-empty NewFiles. Both candidates recorded empty diffs, zero tests, zero empty-diff review findings, passing artifact gates, and exact restore attempts. Candidate 2 is final; no candidate is validated.
Candidate 1 — full narrative (preserved verbatim)
PR #37750 — Try-Fix Candidate 1
Result: Blocked (attempt 1 of at most 2; artifacts under CustomAgentLogsTmp/PRState/37750/PRAgent/try-fix/attempt-1)
Approach description
The intended candidate mechanism — never applied, because the skill's baseline boundary blocked
editing before any source change — was capability-descriptor-driven safe-area strategy discovery
resolved once at the handler-connect boundary. The idea: have the iOS handler resolve a view's
per-edge safe-area strategy a single time when the handler connects, reading it from the already-public
cross-platform element surface via a discoverable capability descriptor, and cache the resolved value
on the handler. The Apple platform view would then consume an already-resolved value instead of
re-deriving one on every layout pass.
This was not implemented. See "Blocker analysis" below.
Prior approach avoided and mechanism-level difference
Prior approach (PR #37750, d97b44ecc2): publicly exposes ISafeAreaElement, adds
ISafeAreaInsets and ISafeAreaViewStrategy, deletes ISafeAreaView2, moves shared bindable-property
plumbing and an effective-value resolver into src/Controls/src/Core (SafeAreaElement.cs, plus
Border, ContentPage, ContentView, InputView, Layout, Page, ScrollView), preserves the
legacy ISafeAreaView fallback, centralizes platform capability resolution in
ViewHandler.iOS.cs/ViewHandler.Android.cs, and adds substantial nested Apple safe-area, keyboard
(KeyboardAutoManagerScroll.cs) and cache-invalidation behaviour in MauiView.cs (+341) and
MauiScrollView.cs (+85). Its root-cause hypothesis is: "third parties cannot implement the internal
interface, therefore publish the interface and its resolver."
Mechanism-level difference (intended, unproven): the candidate's root-cause hypothesis is
different — "the strategy is unreachable because it is only ever read from a MAUI-internal type at
platform layout time." Resolving the strategy once at handler connect from the already-public element
surface would mean no new public interface, no shared bindable-property plumbing across seven Controls
types, and no per-layout effective-value resolver. The nested Apple cache/invalidation behaviour the
existing PR adds would become unnecessary rather than relocated, because the layout path would read a
value that is already final. This is a mechanism difference, not a code-location difference — but it
was not validated, so it must be treated as a hypothesis only.
Files changed
None. No source file was created, edited, or deleted. git status --short -- src is empty.
Full captured diff
Empty — explicitly. fix.diff is a zero-byte file because the attempt was blocked before any edit:
(no changes)
Test results
| Command | Executions | Outcome |
|---|---|---|
pwsh .github/skills/run-device-tests/scripts/Run-DeviceTests.ps1 -Project Controls -Platform ios -TestFilter "Category=View" (primary) |
0 of 1 permitted | Not run |
BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue35756" (regression 1) |
0 | Not run |
... "Issue28986_ParentChildTest" (regressions 2, 6, 10) |
0 | Not run |
... "Issue32586" (regressions 3, 7, 11) |
0 | Not run |
... "Issue33595" (regressions 4, 8, 12) |
0 | Not run |
... "Issue33934" (regressions 5, 9, 13) |
0 | Not run |
No test was executed. With no candidate change applied, running the primary device-test command
would have measured the unmodified review commit — producing zero candidate evidence while consuming
the strictly bounded two-execution budget. The 13 mandated HostApp regressions are gated on the
primary command passing, so none were eligible. The gate was not rerun and
.../PRAgent/gate/content.md was not touched.
Self-review count
0 findings (reviewer-findings.json = []). Step 6 was performed inline against
.github/agents/maui-expert-reviewer.md (Overarching Principles plus the always-active dimensions
Logic and Correctness, Regression Prevention, Complexity Reduction); with an empty diff there were no
hunks to evaluate, so [] is the correct and honest output. Step 7.5 drift detection compared the
post-loop diff to reviewer-findings.diff and found no change (both empty), so no refresh was needed.
No sub-agent, reviewer agent, or rubber-duck was invoked — the inline self-review was the only review.
Blocker analysis
pwsh .github/scripts/EstablishBrokenBaseline.ps1 (Step 2) failed with exit code 1:
ERROR: DIRTY WORKING DIRECTORY - Cannot establish baseline
...
EstablishBrokenBaseline.ps1 failed: Working directory is not clean.
It listed ~44 pre-existing modified/deleted paths under .github/scripts, .github/skills and
eng/scripts — including EstablishBrokenBaseline.ps1 itself and a deleted
.github/scripts/Set-ScreenResolution.Tests.ps1. These are harness-owned inputs that the task
requires be preserved exactly, and the script's only suggested remedy is git checkout -- ., which is
forbidden (no git checkout/restore/reset/clean/stash/rm). Working around the guard is also
forbidden, so the run stopped there.
Two independent conditions each mandate Blocked under skill Core Principle 7:
.github/.baseline-state.jsonis absent (verified:NO BASELINE STATE FILE). Without it there
is noRevertedFilesallow-list, so every tracked path is read-only and no edit is permitted.- The PR adds production files —
src/Core/src/Core/ISafeAreaInsets.csand
src/Core/src/Core/ISafeAreaViewStrategy.cs(git diff HEAD^ HEAD --diff-filter=A). Had baseline
succeeded,NewFileswould be non-empty, which is itself a mandated pre-editBlockedbecause the
restore script cannot safely restore added production files.
This blocker is environmental and deterministic: a second sequential candidate will hit the identical
failure unless the harness commits (or otherwise legitimately clears) the pre-existing .github/eng
modifications. Note also that condition 2 persists even with a clean worktree — any genuine alternative
to this PR needs at least one new production type or Core platform edit, so new-production-file
approaches are structurally unavailable to try-fix for PR #37750.
Because no edit was permitted, the repository instruction files (public-api, safe-area-ios,
threading-async, performance-hotpaths, handler-patterns, helix-device-tests) governing code changes
had no applicable change to govern.
Restoration confirmation
Step 9 ran the exact script-only restore command:
pwsh .github/scripts/EstablishBrokenBaseline.ps1 -Restore
→ No baseline state found. Nothing to restore.
Restored: False Message: No baseline state found
Restored False is the accepted outcome for this verified no-state path: baseline state was never
created and no attempt edits were made. Verified afterwards:
git status --short -- src→ empty (no attempt-created source changes).- The 58 pre-existing dirty entries under
.github/scriptsandeng/scriptsremain untouched, as do
all pre-existing untracked paths. Nogit checkout/restore/reset/clean/stashand norm
was used at any point. - Only
CustomAgentLogsTmp/PRState/37750/PRAgent/try-fix/attempt-1,.../try-fix-1/content.mdand
.../try-fix/content.mdwere written..../gate/content.mdwas not created or modified.
This attempt's status: Done (Blocked — do not retry the same path without an environment fix).
Candidate 2 — full narrative
PR #37750 — Try-Fix Candidate 2
Result: Blocked (attempt 2 of 2; artifacts: CustomAgentLogsTmp/PRState/37750/PRAgent/try-fix/attempt-2)
Approach description
The intended mechanism—never applied because baseline enforcement blocked all source edits—was binary-compatible evolution of the already-public ISafeAreaView. A default per-edge member would derive its fallback from the legacy Boolean behavior; the existing Apple consumer would read that member. Existing implementations would retain old behavior, while third-party views could override the per-edge value directly. This would add no replacement interface, shared Controls property implementation, effective resolver, capability descriptor, or handler-connect cache. It remains unimplemented and unvalidated.
Prior approaches avoided and mechanism-level difference
PR mechanism avoided: PR #37750 publishes ISafeAreaElement, adds ISafeAreaInsets and ISafeAreaViewStrategy, replaces ISafeAreaView2, adds shared bindable-property plumbing and an effective resolver, preserves separate legacy fallback, centralizes platform capability resolution, and expands nested Apple safe-area/keyboard/cache behavior. Its mechanism is a new public nominal contract plus resolver precedence.
Candidate 1 mechanism avoided: capability-descriptor-driven discovery resolved and cached once at handler connect. Candidate 2 uses no descriptor, connect-time resolution, or cache.
Mechanism-level difference: Candidate 2 treats the defect as a versioning gap in the existing public contract: ISafeAreaView already crosses the third-party boundary, but its Boolean cannot encode four independent edges. A default interface member would carry edge intent through ordinary interface dispatch while retaining old Boolean semantics. Thus neither the PR's replacement-interface/effective-resolver chain nor candidate 1's handler-time discovery/cache participates; this is contract evolution, not equivalent resolver logic moved elsewhere.
Files changed
None. No tracked source, test, instruction, .github, or eng path was changed. Post-restore git status --short -- src was empty. Only attempt/report artifacts under CustomAgentLogsTmp/PRState/37750/PRAgent/ were written.
Full captured diff
Empty—explicitly. attempt-2/fix.diff is zero bytes:
(no changes)Primary and regression test results
| # | Exact command/filter | Runs | Outcome |
|---|---|---|---|
| Primary | pwsh .github/skills/run-device-tests/scripts/Run-DeviceTests.ps1 -Project Controls -Platform ios -TestFilter "Category=View" |
0 | Not run—baseline blocked before implementation |
| 1 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue35756" |
0 | Not eligible |
| 2 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue28986_ParentChildTest" |
0 | Not eligible |
| 3 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue32586" |
0 | Not eligible |
| 4 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue33595" |
0 | Not eligible |
| 5 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue33934" |
0 | Not eligible |
| 6 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue28986_ParentChildTest" |
0 | Not eligible |
| 7 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue32586" |
0 | Not eligible |
| 8 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue33595" |
0 | Not eligible |
| 9 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue33934" |
0 | Not eligible |
| 10 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue28986_ParentChildTest" |
0 | Not eligible |
| 11 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue32586" |
0 | Not eligible |
| 12 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue33595" |
0 | Not eligible |
| 13 | pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue33934" |
0 | Not eligible |
Testing the unchanged review commit would provide no candidate evidence. Regressions require a passing primary run, so none ran. Gate verification was not rerun and PRAgent/gate/content.md was not touched.
Self-review count
0 findings (reviewer-findings.json = []). The inline review applied all eight Overarching Principles and always-active Logic and Correctness, Regression Prevention, and Complexity Reduction checks. With no candidate hunk, no concrete finding existed. reviewer-findings.diff is empty and matches the final candidate diff, so Step 7.5 found no candidate-code drift. No reviewer or other sub-agent was invoked.
Failure/blocker analysis
The candidate's Step 2 command, pwsh .github/scripts/EstablishBrokenBaseline.ps1, exited 1. Full output is in attempt-2/baseline.log; it reported ERROR: DIRTY WORKING DIRECTORY and enumerated pre-existing harness-owned .github/scripts, .github/skills, and eng/scripts modifications/deletions. These had to remain untouched.
Two independent facts require Blocked:
.github/.baseline-state.jsonwas absent after failure, so noRevertedFilesedit allow-list existed.- The local
HEAD^..HEADdiff addssrc/Core/src/Core/ISafeAreaInsets.csandsrc/Core/src/Core/ISafeAreaViewStrategy.cs; therefore a successful baseline would have non-emptyNewFiles, independently requiring a pre-edit stop.
No source edit or test was permitted. The artifact gate passed all eight required files. Because blocking preceded any permitted code edit, the listed source instruction sets never became actionable; no boundary workaround was attempted.
Restoration confirmation
Step 9 ran exactly:
pwsh .github/scripts/EstablishBrokenBaseline.ps1 -RestoreIt returned No baseline state found. Nothing to restore., Restored: False, and Message: No baseline state found. This is the accepted no-state result because baseline state was never created and no attempt edit occurred. Post-restore verification found .github/.baseline-state.json absent, source status empty, and 68 pre-existing scoped harness dirty entries preserved. No checkout/restore/reset/clean/stash/rm cleanup was used.
This attempt's status: Done (Blocked; second and final candidate, no retry).
🏁 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Winner: pr-plus-reviewer
The submitted PR has a sound public safe-area contract design and its previously recorded 13 regression invocations passed, but the expert review identified a concrete iOS nesting gap: system-adjusted MauiScrollView insets bypass ancestor per-edge suppression and can therefore be subtracted again after an ancestor has already handled the same edge. The winning candidate preserves the PR's architecture, closes that gap, adds a no-keyboard layout fast path and API-contract clarification, and passes the complete required candidate validation.
Candidate comparison
| Rank | Candidate | Implementation | Primary validation | Required regressions | Assessment |
|---|---|---|---|---|---|
| 1 | pr-plus-reviewer |
Raw PR plus one consolidated four-file reviewer patch | PASS — 62/62 iOS View tests |
PASS — 13/13 | Best correctness/evidence balance; fixes the expert's blocking nested-scroll finding without regressing the mandated scenarios. |
| 2 | pr |
Submitted public contract, resolver, platform routing, nested safe-area/keyboard behavior, and broad tests | Trusted Gate INCONCLUSIVE; no gate rerun | PASS — 13/13 in the existing regression-check artifact | Strong base implementation, but the expert's system-adjusted scroll-inset finding remains unresolved in submitted HEAD. |
| 3 | try-fix-1 |
Proposed connect-time capability descriptor/cache | Not run | Not run | Blocked before implementation by the baseline boundary; empty diff and no candidate evidence. |
| 4 | try-fix-2 |
Proposed default per-edge member on legacy ISafeAreaView |
Not run | Not run | Blocked before implementation by the same boundary; empty diff and no candidate evidence. |
No candidate failed a regression test. The two try-fix-* entries rank below both implemented candidates because neither produced code or validation. The raw PR ranks below pr-plus-reviewer because passing existing regressions does not exercise the expert's system-adjusted nested-scroll scenario, while the refined candidate changes that focused expectation and still passes all required regression runs.
Expert-review reconciliation
- Resolved in the winner: ancestor suppression now applies in both
MauiScrollViewinset branches; no-keyboard layout avoids unnecessary strategy/geometry work; public interface documentation acknowledges built-in compatibility resolution. - Not changed: full descendant invalidation and direct ancestor-input resolution are retained because the PR includes explicit layout-order and edge-disjoint descendant behavior that pruning/cached prior-pass state could break.
- Remaining discussion: mapper negative-case coverage, Mac Catalyst validation, and overall nested-layout performance remain unverified. They do not outweigh the winner's concrete improvement and passing required validation, but they keep confidence below high.
The PR should adopt pr-plus-reviewer/reviewer.patch before approval. The trusted Gate remains INCONCLUSIVE, not failed, and is not used as an independent reason to request changes.
🔗 Regression Cross-Reference
🔍 Regression Cross-Reference
✗ Revert risks detected — this PR removes 3 line(s) previously added by labeled bug-fix PRs.
| File | Fix PR | Fixed issue(s) | Risk | Reverted line |
|---|---|---|---|---|
src/Core/src/Platform/iOS/MauiScrollView.cs |
#34024 | #32586, #33934, #33595, #34042 | ✗ REVERT | bool? _parentHandlesSafeArea; |
src/Core/src/Platform/iOS/MauiView.cs |
#34024 | #32586, #33934, #33595, #34042 | ✗ REVERT | bool? _parentHandlesSafeArea; |
src/Core/src/Platform/iOS/SafeAreaPadding.cs |
#34024 | #32586, #33934, #33595, #34042 | ✗ REVERT | return RoundToPixel(Left, scale) == RoundToPixel(other.Left, scale) |
Action required: Verify that issues #32586, #33595, #33934, #34042 do not re-regress before merging.
🧪 Regression Tests to Verify
These tests were added by the fix PRs being reverted. They must still pass:
| Fix PR | Type | Test | Filter |
|---|---|---|---|
| #34024 | UITest | Issue28986_ParentChildTest | Issue28986_ParentChildTest |
| #34024 | UITest | Issue32586 | Issue32586 |
| #34024 | UITest | Issue33595 | Issue33595 |
| #34024 | UITest | Issue33934 | Issue33934 |
| #34024 | UITest | Issue28986_ParentChildTest | Issue28986_ParentChildTest |
| #34024 | UITest | Issue32586 | Issue32586 |
| #34024 | UITest | Issue33595 | Issue33595 |
| #34024 | UITest | Issue33934 | Issue33934 |
| #34024 | UITest | Issue28986_ParentChildTest | Issue28986_ParentChildTest |
| #34024 | UITest | Issue32586 | Issue32586 |
| #34024 | UITest | Issue33595 | Issue33595 |
| #34024 | UITest | Issue33934 | Issue33934 |
🧪 Regression Test Results
✅ PASSED — 13 passed, 0 failed, 0 skipped
| Fix PR | Test | Type | Result |
|---|---|---|---|
| #35916 | Issue35756 | UITest | ✅ PASSED |
| #34024 | Issue28986_ParentChildTest | UITest | ✅ PASSED |
| #34024 | Issue32586 | UITest | ✅ PASSED |
| #34024 | Issue33595 | UITest | ✅ PASSED |
| #34024 | Issue33934 | UITest | ✅ PASSED |
| #34024 | Issue28986_ParentChildTest | UITest | ✅ PASSED |
| #34024 | Issue32586 | UITest | ✅ PASSED |
| #34024 | Issue33595 | UITest | ✅ PASSED |
| #34024 | Issue33934 | UITest | ✅ PASSED |
| #34024 | Issue28986_ParentChildTest | UITest | ✅ PASSED |
| #34024 | Issue32586 | UITest | ✅ PASSED |
| #34024 | Issue33595 | UITest | ✅ PASSED |
| #34024 | Issue33934 | UITest | ✅ PASSED |
📱 UI Tests — Border,Layout,Page,SafeAreaEdges,ScrollView,ViewBaseTests
Detected UI test categories: Border,Layout,Page,SafeAreaEdges,ScrollView,ViewBaseTests
❌ Deep UI tests — 660 passed, 1 failed, 6 skipped across 6 categories on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
Border |
58/58 ✓ | — |
Layout |
193/199 (1 ❌, 5 skipped) | — |
Page |
26/26 ✓ | — |
SafeAreaEdges |
109/109 ✓ | — |
ScrollView |
162/163 (1 skipped) ✓ | — |
ViewBaseTests |
112/112 ✓ | — |
🔍 AI analysis of failures — PR-related vs unrelated
🔍 AI-generated triage (GitHub Copilot CLI) — a heuristic judgement of whether each deep UI test failure is connected to this PR's changes. Verify before relying on it.
Likely PR-related: one or more failures appear connected to this PR's changes.
- ✗ PR-related — iOS keyboard scrolling/layout (1 test):
EntriesScrollingPageTestleaves an entry below the keyboard, directly overlapping the PR's changes to iOS keyboard overlap, safe-area propagation,KeyboardAutoManagerScroll,MauiView, andMauiScrollView.
Strongest signal: the asserted view bottom was 869 while the keyboard began at 829, precisely the keyboard-avoidance behavior modified by this PR.
❌ Layout — 1 failed test
EntriesScrollingPageTest
Assert.That(arg1, Is.LessThan(arg2))
Expected: less than 829
But was: 869
at NUnit.Framework.Legacy.ClassicAssert.Less(Int32 arg1, Int32 arg2)
at Microsoft.Maui.TestCases.Tests.KeyboardScrolling.CheckIfViewAboveKeyboard(IApp app, String marked, Boolean isEditor) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/KeyboardScrolling.cs:line 98
at Microsoft.Maui.TestCases.Tests.KeyboardScrolling.ClickText(IApp app, String marked, Boolean isEditor, Boolean& didReachEndofPage) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/KeyboardScrolling.cs:line 67
at Microsoft.Maui.TestCases.Tests.KeyboardScrolling.RunScrollingTest(IApp app, String galleryName, Boolean isEditor) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/KeyboardScrolling.cs:line 47
at Microsoft.Maui.TestCases.Tests.KeyboardScrolling.EntriesScrollingTest(IApp app, String galleryName) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/KeyboardScrolling.cs:line 17
at Microsoft.Maui.TestCases.Test
...
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)
🧭 Next Steps — reviewer changes required
The reviewer-enhanced candidate identified changes that are not yet in the submitted PR.
Why: The refined PR preserves the submitted public-contract design while fixing the expert-identified nested system-inset suppression gap. It passed all 62 targeted iOS View tests and all 13 required regression invocations; both try-fix alternatives were blocked before producing code or test evidence.
Address the actionable findings in this review before merging.
|
The AI summary is based on stale head |
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!
Summary
ISafeAreaElementthe public per-edge safe-area contract for custom views and native hostsHasExplicitSafeAreaEdgesandGetDefaultSafeAreaEdges()so platform code can preserve control defaults and distinguish an explicit value from a default-created valueGetEffectiveSafeAreaEdges()so public native hosts read the same effective strategy as MAUI handlers, including built-in controls that intentionally preserve an explicitDefaultSafeAreaElement.SafeAreaEdgesPropertyandSafeAreaElement.IsSafeAreaEdgesSet(BindableObject)helper for customBindableObjectimplementationsISafeAreaViewcontract unchanged as a compatible legacy fallbackThis targets
net11.0because #37384 is an API request for .NET 11.Public API and compatibility
The new public surface is added to every applicable Core and Controls API baseline:
ISafeAreaElement.SafeAreaEdgesISafeAreaElement.HasExplicitSafeAreaEdgesISafeAreaElement.GetDefaultSafeAreaEdges()SafeAreaElementExtensions.GetEffectiveSafeAreaEdges(ISafeAreaElement)SafeAreaElement.SafeAreaEdgesPropertySafeAreaElement.IsSafeAreaEdgesSet(BindableObject)ISafeAreaViewremains unchanged because it is already shipped and implemented by external controls. Built-in controls use an internal strategy for their existing legacy/default semantics; third-party controls only implementISafeAreaElement.Leaving
ContentPage.SafeAreaEdgesunset preserves its existing platform/legacy default. Explicit values remain explicit, including full and partialDefaultregions, so setting the shipped named default does not become edge-to-edge.GetEffectiveSafeAreaEdges()delegates to the same strategy resolver as the handlers, preventing public native hosts from interpreting those values differently.Validation
The broad focused build, API, unit, XAML, AOT, and platform validation below was completed through product head
777f65e227af9af1a0148a55223317a59ec94a0a. Commitsc32e41e882e0b6baa9da3b830a2840262e015c2eand310d7e93c0fd3280aba2833ef1191b54afd51a41harden nested BottomSoftInputresidual handling and its geometry-sensitive coverage. Commitc265a307d821eea36c61116b1a549fdf7a9e03f6adds pixel-gated overlap caching, screen-to-window keyboard conversion, active-ancestor checks, descendant convergence invalidation, a real cross-platform arrange regression, and reusable bindable-property specificity metadata. Commit104f8146610f38465118de78bdd2c2f9336e4751compares absolute overlap values in device-pixel buckets so cumulative sub-pixel movement cannot be ignored indefinitely. Current PR head79cdc7408d6140335f5fa27a9aeb999897cfb0f9adds two-dimensional floating-keyboard intersection and clamping, uses the owning window's screen scale, preserves UIKit-applied scroll insets, verifies specificity-only/no-op updates, and removes avoidable ancestor work without making suppression depend on layout order. It includes target headbedd1b18b7682193e05b47267509cec8c49c6853, preserving both the target's initial-connection guard and this PR's dynamic safe-area invalidation.The screenshots and videos were captured at
f5a35e4a729a7875b26e2474e4fd90c1a1af0099. The later commits repair Android AOT profile encoding, clarify compatibility documentation, and harden nested keyboard behavior; they do not change the capturedContainer → None → bottom-only Container → Containerprobe flow. That flow and the keyboard regressions were rebuilt and revalidated in the final platform suites.SafeAreaTests:77/77SafeAreaTestsplus bindable-object selection at merged head:174/174SafeAreaEdgesTests:15/15across runtime, XamlC, and source-generator paths, including a third-party control using the public shared propertyMicrosoft.Maui.BuildTasks.slnf: succeeded withPublicApiType=Validatenetstandard2.0, iOS, Mac Catalyst, and AndroidThe platform-handler implementation was exercised at
f6db02a3ad6cb67b3e555085ca03eb33997f2b98; the only later commit adds the public resolver, its documentation/API baselines, and focused tests. Exact-head platform libraries and both empirical apps were rebuilt, and the new resolver itself is displayed in the iOS and Android evidence below.ViewPageScrollViewScrollViewHandler62/625/512passed,1ignored57passed,1ignored62/625/512passed,1ignored52passed,6ignoredView)53/5316/1612/1279cdc7408d6140335f5fa27a9aeb999897cfb0f9was rebuilt and empirically rerun after publication: iOS62/62, Mac Catalyst62/62, Android53/53, and focused units77/77FloatingKeyboardUsesClampedViewIntersectioncovers a laterally disjoint floating keyboard and an oversized frame; restoring the old Y-only overlap produced the sole iOS failure (61/62)SystemAdjustedScrollViewInsetsAreNotSuppressedByParentpreserves the full viewport inset that UIKit already applies; restoring system-inset suppression produced the sole iOS failure (61/62)60/62)61/62)_safeAreaInvalidatedsubtree early exit produced the sole iOS failure (61/62), proving that measure-invalidated parents can still have descendants requiring safe-area invalidation76/77)SoftInput: a correctly arranged child does not double-pad, while an overflowing child retains its positive frame-relative residual; transformed geometry is recomputed without requiring another keyboard notification, keyboard hide clears the residual, and a nestedMauiScrollViewstill suppresses its raw/system Bottom insetGridmeasure/arrange path instead of assigning native frames manuallySoftInputguard produced50/51, with only the overflowing-child regression failing50/51, with the transformed child retaining stale height50instead of the expected100MauiScrollView's raw/system Bottom inset as a computed keyboard residual produced50/51, with only the nested scroll-view suppression regression failing55/56, with only the stable-geometry cache regression failing (expected0ancestor reads, actual4)55/56, with only the strengthened cache regression failing because two individually sub-pixel moves crossed a cumulative device-pixel boundary55/56, with only the nonzero-window-origin regression failing (expected X0, actual137)SoftInputancestor underUIScrollViewas active produced55/56, with only the fallback-auto-scroll regression failing55/56, with only the non-SoftInputdescendant convergence regression failing (expected height70, actual100)51/53, failing exactly the listener attach/detach regressions; the restored APK passed53/53Invalid userId -2; the same rebuilt APK passed53/53when instrumentation explicitly targeted emulator owner user0SafeAreaEdgescases passed across final-head runs: both full-category runs passed108/109, with only the order-dependent initial-state assertion after an orientation-changing fixture failing; that complete fixture immediately passed4/4in isolation, and the earlier exact-f5afull run passed109/109OnBindablePropertySetentriesMicrosoft.Maui.SafeAreaViewStrategytype, one Android-reachableTryGetSafeAreaEdgesmethod, no leading-dot type, and no linker-unreachableGetSafeAreaRegionsForEdgeentrymaui: 38 modules / 1,814 types / 8,632 methods;maui-sc: 49 / 2,264 / 10,963;maui-blazor: 43 / 2,186 / 8,568110/110assemblies; the unstripped output contains the nativeMicrosoft_Maui_SafeAreaViewStrategy_TryGetSafeAreaEdges_object_Microsoft_Maui_SafeAreaEdges__boolbodyContentPage.Default, specificity-only assignments, Android exact inset consumption, nested/disjoint Apple edges, child keyboard overlap beyond a parent container inset, complete descendant invalidation, legacyMauiScrollViewbehavior, residual sub-pixel ancestor insets, completed ancestor traversal, and stale five-parameter AOT signaturesCompleted public build
1567759uses merge commit30ce3756e59cf4d10e60ea9c6ce9f5e916746767, whose second parent is PR head490a33f8dc17a653823d181f844aacd41b2e87fc. Every integration-test job succeeded, including Android runtime, six iOS CoreCLR/NativeAOT configurations, AOT/build, samples, Blazor, multi-project, and Windows template/build coverage.Replacement build
1567853uses merge commitcdb262e0517e81025f2038574b3a1bd9da829515, with target parent4695c95801e0b6764beb83f314c62141ee9c7f2eand PR parent310d7e93c0fd3280aba2833ef1191b54afd51a41. Its final raw timeline contains 24 successful jobs and only two failed jobs: Windows Debug and Release. Every failed task/job/phase log was downloaded and inspected without deduplication; both build tasks report only the target branch's fourCS0103errors for missingAssertEventuallyatTabbedPageTests.Windows.cslines 141 and 155 across the two Windows TFMs. Both Windows Helix jobs succeeded. Target commitc127c3f3503e06a347c0f100504911659f6154c1fixes that compile error and is included in current head79cdc7408d6140335f5fa27a9aeb999897cfb0f9.Build
1568885was audited from every failed raw phase/job/task log and all 16 Helix work-item details and console logs, preserving repeated lines. Every test process passed with zero failures and exited0; Windows Helix reported exit-4only when publishing results failed withTF10216: Azure DevOps services are currently unavailableor one Azure request-read timeout. This build contains no product or test failure.Claude Opus 5 independently reviewed the safe-area hot paths and rejected both applied-state ancestor caching and
_safeAreaInvalidatedsubtree pruning as layout-order correctness regressions. Its topology-independent optimizations are in79cdc7408d. A separate GPT-5.6 Terra exact-diff review reported no significant issues. All six exact-head review threads have evidence-backed replies and are resolved.Empirical evidence
A temporary
SafeAreaProbe : TemplatedView, ISafeAreaElementimplemented only the new public contract, reused the public shared bindable property, and displayedGetEffectiveSafeAreaEdges()asPublic host: .... At the capture head it was advanced live through all-edgeContainer,None, bottom-onlyContainer, and back to all-edgeContainer. The probe was removed after capture.Both videos are H.264/yuv420p at a constant 30 fps and contain only the ordered runtime states
1 → 2 → 3 → 1; sampled-frame review found no blank, splash, crash, or stale-state frames. On iOS, Appium taps drove the live property changes deterministically.iOS — iPhone 11 Pro simulator
T44/B34)T0/B0)T0/B34)Video — live runtime transitions:
ios-safe-area-demo-f5a.mp4
Android — API 34 arm64 emulator
The probe theme leaves the Android system bars opaque. Edge-to-edge is demonstrated by the
SAFE CONTENTmarkers moving beneath those bars (and therefore disappearing), while bottom-only restores only the bottom marker; the background cannot show through the opaque bars.Video — live runtime transitions:
android-safe-area-demo-f5a.mp4
MauiBot follow-up
Defaultresolution, explicit shared-property misuse, and the public CLR-property pattern required by XAMLContentPage.Defaultregions while retaining the unset edge-to-edge defaultSoftInputMauiViewcompute its own residual even when an ancestor also declaresSoftInput: an arranged child falls back to ordinary ancestor suppression, while an overflowing child retains only its positive live overlapSoftInputgeometry during layout so transforms cannot preserve stale keyboard overlap; mutation testing makes the transformed-child assertion fail without the refreshSoftInputancestor to respond to safe area before it can suppress keyboard auto-scrollGridElementsafe-area identity special case with reusable internal bindable-property specificity metadataMauiScrollViewdeliberately keeps ordinary suppression for raw/system insets, with a bottom-edge regression that fails if the keyboard exemption is applied thereSystemAdjustedContentInsetvalues while keeping per-edge ancestor suppression for manually computed scroll insets_safeAreaInvalidatedcannot represent descendant state; a persistent latch is also unsound across same-window reparenting of genericUIViewsubtreesIScrollViewimplementationsExisting PR comparison
I searched the open pull requests for #37384 and equivalent safe-area API titles. No competing implementation exists, so there was no alternative change set to compare.
Fixes #37384