Skip to content

[net11.0] Expose safe area contract for custom views - #37750

Open
kubaflo wants to merge 28 commits into
dotnet:net11.0from
kubaflo:kubaflo/37384-public-safe-area-api
Open

[net11.0] Expose safe area contract for custom views#37750
kubaflo wants to merge 28 commits into
dotnet:net11.0from
kubaflo:kubaflo/37384-public-safe-area-api

Conversation

@kubaflo

@kubaflo kubaflo commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

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

  • make ISafeAreaElement the public per-edge safe-area contract for custom views and native hosts
  • expose HasExplicitSafeAreaEdges and GetDefaultSafeAreaEdges() so platform code can preserve control defaults and distinguish an explicit value from a default-created value
  • expose GetEffectiveSafeAreaEdges() so public native hosts read the same effective strategy as MAUI handlers, including built-in controls that intentionally preserve an explicit Default
  • expose the shared SafeAreaElement.SafeAreaEdgesProperty and SafeAreaElement.IsSafeAreaEdgesSet(BindableObject) helper for custom BindableObject implementations
  • keep the shipped ISafeAreaView contract unchanged as a compatible legacy fallback
  • keep built-in default/legacy normalization and edge-index lookup internal rather than adding another numbered public interface or a public magic-integer API
  • route iOS, Mac Catalyst, Android, keyboard, collection-cell, and nested-view safe-area handling through one capability resolver and reuse one four-edge snapshot per evaluation
  • preserve runtime Apple descendant invalidation, per-edge ancestor suppression, child keyboard overlap beyond ancestor container insets, pixel-level inset tolerance, and Android listener refresh
  • add unit, XAML, device, HostApp, AOT, mutation, and runtime-media coverage for public-only custom views and legacy behavior

This targets net11.0 because #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.SafeAreaEdges
  • ISafeAreaElement.HasExplicitSafeAreaEdges
  • ISafeAreaElement.GetDefaultSafeAreaEdges()
  • SafeAreaElementExtensions.GetEffectiveSafeAreaEdges(ISafeAreaElement)
  • SafeAreaElement.SafeAreaEdgesProperty
  • SafeAreaElement.IsSafeAreaEdgesSet(BindableObject)

ISafeAreaView remains 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 implement ISafeAreaElement.

Leaving ContentPage.SafeAreaEdges unset preserves its existing platform/legacy default. Explicit values remain explicit, including full and partial Default regions, 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. Commits c32e41e882e0b6baa9da3b830a2840262e015c2e and 310d7e93c0fd3280aba2833ef1191b54afd51a41 harden nested Bottom SoftInput residual handling and its geometry-sensitive coverage. Commit c265a307d821eea36c61116b1a549fdf7a9e03f6 adds 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. Commit 104f8146610f38465118de78bdd2c2f9336e4751 compares absolute overlap values in device-pixel buckets so cumulative sub-pixel movement cannot be ignored indefinitely. Current PR head 79cdc7408d6140335f5fa27a9aeb999897cfb0f9 adds 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 head bedd1b18b7682193e05b47267509cec8c49c6853, 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 captured Container → None → bottom-only Container → Container probe flow. That flow and the keyboard regressions were rebuilt and revalidated in the final platform suites.

  • exact pushed-head SafeAreaTests: 77/77
  • focused SafeAreaTests plus bindable-object selection at merged head: 174/174
  • SafeAreaEdgesTests: 15/15 across runtime, XamlC, and source-generator paths, including a third-party control using the public shared property
  • Microsoft.Maui.BuildTasks.slnf: succeeded with PublicApiType=Validate
  • Core and Controls Core: succeeded for netstandard2.0, iOS, Mac Catalyst, and Android

The 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.

Platform Controls View Controls Page Controls ScrollView Core ScrollViewHandler
iPhone 11 Pro CoreSimulator, iOS 26.5 62/62 5/5 12 passed, 1 ignored 57 passed, 1 ignored
Mac Catalyst arm64 62/62 5/5 12 passed, 1 ignored 52 passed, 6 ignored
Android arm64 emulator (API 35 for final View) 53/53 16/16 12/12
  • exact pushed head 79cdc7408d6140335f5fa27a9aeb999897cfb0f9 was rebuilt and empirically rerun after publication: iOS 62/62, Mac Catalyst 62/62, Android 53/53, and focused units 77/77
  • FloatingKeyboardUsesClampedViewIntersection covers a laterally disjoint floating keyboard and an oversized frame; restoring the old Y-only overlap produced the sole iOS failure (61/62)
  • SystemAdjustedScrollViewInsetsAreNotSuppressedByParent preserves the full viewport inset that UIKit already applies; restoring system-inset suppression produced the sole iOS failure (61/62)
  • removing the empty-inset ancestor fast paths produced exactly the two instrumented failures (60/62)
  • restoring farther-ancestor keyboard geometry after Bottom was already resolved produced the sole iOS failure (61/62)
  • adding the suggested _safeAreaInvalidated subtree early exit produced the sole iOS failure (61/62), proving that measure-invalidated parents can still have descendants requiring safe-area invalidation
  • disabling specificity-change metadata produced the sole focused unit failure (76/77)
  • final-head nested-keyboard tests cover parent and child both declaring Bottom 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 nested MauiScrollView still suppresses its raw/system Bottom inset
  • the nested arrange regression now uses a real MAUI Grid measure/arrange path instead of assigning native frames manually
  • restoring the old ancestor-SoftInput guard produced 50/51, with only the overflowing-child regression failing
  • removing active keyboard-geometry refresh produced 50/51, with the transformed child retaining stale height 50 instead of the expected 100
  • treating MauiScrollView's raw/system Bottom inset as a computed keyboard residual produced 50/51, with only the nested scroll-view suppression regression failing
  • removing the pixel-level overlap gate produced 55/56, with only the stable-geometry cache regression failing (expected 0 ancestor reads, actual 4)
  • restoring adjacent-delta comparison produced 55/56, with only the strengthened cache regression failing because two individually sub-pixel moves crossed a cumulative device-pixel boundary
  • skipping screen-to-window keyboard conversion produced 55/56, with only the nonzero-window-origin regression failing (expected X 0, actual 137)
  • treating a non-responding SoftInput ancestor under UIScrollView as active produced 55/56, with only the fallback-auto-scroll regression failing
  • removing interaction-change descendant invalidation produced 55/56, with only the non-SoftInput descendant convergence regression failing (expected height 70, actual 100)
  • disabling reusable specificity-change metadata produced Android 51/53, failing exactly the listener attach/detach regressions; the restored APK passed 53/53
  • the updated repository XHarness failed before Android app launch with Invalid userId -2; the same rebuilt APK passed 53/53 when instrumentation explicitly targeted emulator owner user 0
  • all 109 iOS HostApp SafeAreaEdges cases passed across final-head runs: both full-category runs passed 108/109, with only the order-dependent initial-state assertion after an orientation-changing fixture failing; that complete fixture immediately passed 4/4 in isolation, and the earlier exact-f5a full run passed 109/109
  • all three binary AOT profiles exactly match their text snapshots; each has two six-parameter and zero stale five-parameter OnBindablePropertySet entries
  • each profile contains one fully qualified Microsoft.Maui.SafeAreaViewStrategy type, one Android-reachable TryGetSafeAreaEdges method, no leading-dot type, and no linker-unreachable GetSafeAreaRegionsForEdge entry
  • final profile counts are maui: 38 modules / 1,814 types / 8,632 methods; maui-sc: 49 / 2,264 / 10,963; maui-blazor: 43 / 2,186 / 8,568
  • exact-head Android Mono profiled-AOT publish consumed the profiles and compiled all 110/110 assemblies; the unstripped output contains the native Microsoft_Maui_SafeAreaViewStrategy_TryGetSafeAreaEdges_object_Microsoft_Maui_SafeAreaEdges__bool body
  • temporary public-only Sandbox probe built in Release for iOS Simulator and Android arm64 before the empirical runs below
  • mutation checks proved the assertions detect public custom-view resolution, canonical public-host strategy precedence, declared defaults, explicit and partial ContentPage.Default, specificity-only assignments, Android exact inset consumption, nested/disjoint Apple edges, child keyboard overlap beyond a parent container inset, complete descendant invalidation, legacy MauiScrollView behavior, residual sub-pixel ancestor insets, completed ancestor traversal, and stale five-parameter AOT signatures

Completed public build 1567759 uses merge commit 30ce3756e59cf4d10e60ea9c6ce9f5e916746767, whose second parent is PR head 490a33f8dc17a653823d181f844aacd41b2e87fc. 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 1567853 uses merge commit cdb262e0517e81025f2038574b3a1bd9da829515, with target parent 4695c95801e0b6764beb83f314c62141ee9c7f2e and PR parent 310d7e93c0fd3280aba2833ef1191b54afd51a41. 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 four CS0103 errors for missing AssertEventually at TabbedPageTests.Windows.cs lines 141 and 155 across the two Windows TFMs. Both Windows Helix jobs succeeded. Target commit c127c3f3503e06a347c0f100504911659f6154c1 fixes that compile error and is included in current head 79cdc7408d6140335f5fa27a9aeb999897cfb0f9.

Build 1568885 was 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 exited 0; Windows Helix reported exit -4 only when publishing results failed with TF10216: Azure DevOps services are currently unavailable or 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 _safeAreaInvalidated subtree pruning as layout-order correctness regressions. Its topology-independent optimizations are in 79cdc7408d. 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, ISafeAreaElement implemented only the new public contract, reused the public shared bindable property, and displayed GetEffectiveSafeAreaEdges() as Public host: .... At the capture head it was advanced live through all-edge Container, None, bottom-only Container, and back to all-edge Container. 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

All edges (T44/B34) Edge-to-edge (T0/B0) Bottom only (T0/B34)
iOS public custom view using Container on all edges iOS public custom view using None edge-to-edge iOS public custom view using Container on the bottom edge only

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 CONTENT markers moving beneath those bars (and therefore disappearing), while bottom-only restores only the bottom marker; the background cannot show through the opaque bars.

All edges Edge-to-edge Bottom only
Android public custom view using Container on all edges Android public custom view using None edge-to-edge Android public custom view using Container on the bottom edge only

Video — live runtime transitions:

android-safe-area-demo-f5a.mp4

MauiBot follow-up

  • applied the connect-time Apple invalidation guard
  • strengthened Android coverage to assert exact consumed insets and listener replacement
  • simplified the final public surface to one modern custom-view contract while retaining the existing legacy interface unchanged
  • documented custom-view Default resolution, explicit shared-property misuse, and the public CLR-property pattern required by XAML
  • preserved explicit and partially explicit ContentPage.Default regions while retaining the unset edge-to-edge default
  • added one canonical public-host resolver so public consumers and handlers preserve those built-in defaults identically
  • replaced repeated handler/type resolution on layout paths with one reusable four-edge snapshot
  • made nested Apple suppression per-edge, pixel-tolerant, and bounded once all four ancestor-handled edges are known
  • preserved a child's frame-relative keyboard overlap when an ancestor handles only the smaller container inset, with an arranging-parent regression whose assertion fails under unconditional suppression
  • made every Bottom SoftInput MauiView compute its own residual even when an ancestor also declares SoftInput: an arranged child falls back to ordinary ancestor suppression, while an overflowing child retains only its positive live overlap
  • refreshed active Bottom SoftInput geometry during layout so transforms cannot preserve stale keyboard overlap; mutation testing makes the transformed-child assertion fail without the refresh
  • retained ancestor-edge caches across unchanged keyboard-visible layout passes by comparing absolute overlap values in device-pixel buckets, including cumulative sub-pixel movement
  • converted keyboard notification frames from screen coordinates into the active window before view-frame intersection
  • required a two-dimensional keyboard/view intersection, clamped overlap to the view height, and classified ancestor pixel values with the owning window's screen scale
  • required a SoftInput ancestor to respond to safe area before it can suppress keyboard auto-scroll
  • propagated actual safe-area interaction changes to plain descendants so multi-pass keyboard layout converges without per-layout subtree churn
  • exercised the synchronous cross-platform arrange ordering through a real MAUI Grid
  • replaced the shared Element safe-area identity special case with reusable internal bindable-property specificity metadata
  • documented the same-edge keyboard-residual exception and why MauiScrollView deliberately keeps ordinary suppression for raw/system insets, with a bottom-edge regression that fails if the keyboard exemption is applied there
  • preserved UIKit SystemAdjustedContentInset values while keeping per-edge ancestor suppression for manually computed scroll insets
  • skipped ancestor lookup for empty adjusted insets and skipped farther-ancestor keyboard geometry once Bottom is resolved
  • retained full native-subtree invalidation because an exact mutation proves _safeAreaInvalidated cannot represent descendant state; a persistent latch is also unsound across same-window reparenting of generic UIView subtrees
  • preserved the pre-existing edge-to-edge fallback for legacy-only IScrollView implementations
  • regenerated all three binary/text AOT profile pairs for the six-parameter property-set signatures, repaired the fully qualified strategy type, removed the Android-unreachable strategy method, and validated them with a real Mono profiled-AOT publish

Existing 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

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>
Copilot AI lite review requested due to automatic review settings August 22, 2026 23:18
@kubaflo
kubaflo temporarily deployed to copilot-pat-pool August 22, 2026 23:19 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 37750

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 37750"

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@kubaflo
kubaflo temporarily deployed to copilot-pat-pool August 22, 2026 23:19 — with GitHub Actions Inactive
@kubaflo
kubaflo temporarily deployed to copilot-pat-pool August 22, 2026 23:20 — with GitHub Actions Inactive
@kubaflo
kubaflo temporarily deployed to copilot-pat-pool August 22, 2026 23:23 — with GitHub Actions Inactive
@kubaflo
kubaflo temporarily deployed to copilot-pat-pool August 22, 2026 23:24 — with GitHub Actions Inactive
@github-actions github-actions Bot added area-safearea Issues/PRs that have to do with the SafeArea functionality platform/ios labels Aug 22, 2026
@kubaflo
kubaflo temporarily deployed to copilot-pat-pool August 22, 2026 23:24 — with GitHub Actions Inactive

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 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

  • HasExplicitSafeAreaEdges now correctly uses IsSafeAreaEdgesSet, but the adjacent per-edge resolver still uses IsSet. IsSet treats default-value creation as set, so merely reading SafeAreaEdges first causes this method to skip the iOS IgnoreSafeArea fallback and return the default None instead. Use SafeAreaElement.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.txt and maui-sc.aotprofile.txt still list Layout/ScrollView:ISafeAreaElement.SafeAreaEdgesDefaultValueCreator, while these implementations are now GetDefaultSafeAreaEdges; Microsoft.Maui.Controls.targets imports 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 ISafeAreaElement implementation used by Layout and ScrollView, but the checked-in profiled-AOT lists still reference Microsoft.Maui.ISafeAreaElement.SafeAreaEdgesDefaultValueCreator (in maui.aotprofile.txt and maui-sc.aotprofile.txt). Regenerate those profile outputs so they reference GetDefaultSafeAreaEdges; 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-side ISafeAreaView2 lookup or listener refresh would therefore still pass these tests even though a direct custom View no 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;

Comment thread src/Controls/tests/Core.UnitTests/SafeAreaTests.cs Outdated
@kubaflo

kubaflo commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Validated the review findings against the current head:

  • ContentPage.GetSafeAreaRegionsForEdge still uses IsSet, so default-value creation can incorrectly bypass the legacy iOS fallback.
  • Both checked-in AOT profiles retain four SafeAreaEdgesDefaultValueCreator entries after the rename.
  • The 28-file diff contains no device-test coverage for the platform inset path.
  • The private-constructor Activator.CreateInstance issue is confirmed separately in the inline thread.

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 AI review requested due to automatic review settings August 23, 2026 02:09
@kubaflo

kubaflo commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

@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!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 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 ViewHandler mapper is guarded by #if ANDROID || IOS, while MauiView and ViewHandler.iOS.cs also compile for Mac Catalyst. Consequently, changing a custom view's SafeAreaEdges after its handler is connected does not invalidate the safe-area layout there. Register MapSafeAreaEdges for MACCATALYST as 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);

Comment thread src/Controls/src/Core/SafeAreaElement.cs Outdated
Comment thread src/Core/src/Core/ISafeAreaView2.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
Copilot AI review requested due to automatic review settings August 23, 2026 03:36
@kubaflo

kubaflo commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

@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!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 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 paired maui-sc.aotprofile binary is not regenerated. Microsoft.Maui.Controls.targets imports 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 documented Record target for maui-sc and 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 paired maui.aotprofile binary is not regenerated. Microsoft.Maui.Controls.targets imports 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 documented Record target for maui and commit the regenerated binary with this snapshot.
	Microsoft.Maui.SafeAreaEdges Microsoft.Maui.Controls.Layout:Microsoft.Maui.ISafeAreaElement.GetDefaultSafeAreaEdges ()

@kubaflo

kubaflo commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the re-review. The two suppressed AOT-profile notes are already satisfied by ecf8393: that commit changed both maui.aotprofile and maui-sc.aotprofile, and aprofutil -m shows the new GetDefaultSafeAreaEdges entries in each binary. A full sorted method-set comparison against the checked-in text snapshots reports zero differences (maui: 8,634 methods; maui-sc: 10,965 methods), so another profile regeneration would be redundant. No code change is needed for these suppressed findings.

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Aug 23, 2026
@MauiBot

This comment has been minimized.

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

Copilot-Session: de9c7c01-82c4-42fd-9ab7-882279aadd15
@kubaflo

kubaflo commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Final MauiBot follow-up is pushed at 310d7e93c0fd3280aba2833ef1191b54afd51a41.

  • Parent and child Bottom SoftInput now retain only a positive child-frame keyboard residual, avoiding both double padding and under-padding.
  • Active keyboard geometry is recomputed during layout, including transform-only changes.
  • The same-edge exception is documented; MauiScrollView intentionally retains ordinary suppression for raw/system insets and now has bottom-edge coverage.
  • Current-head device results: iOS Simulator 51/51, Mac Catalyst 51/51, Android emulator 53/53.
  • Three targeted mutations each reduce the Apple View suite to 50/51, proving the parent-guard, geometry-refresh, and scroll-view assertions independently detect their protected behavior.

@kubaflo

This comment has been minimized.

@MauiBot

This comment has been minimized.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Aug 26, 2026

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

⚠️ PerformanceMauiView.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).

⚠️ Coordinate space (Logic) — In 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.

⚠️ LogicIsSoftInputHandledByParent (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).

⚠️ Logic and Correctness — The new recursive 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 CrossPlatformArrangePlatformArrangeHandler 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 PlacementElement.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's CrossPlatformArrange synchronously repositions the child's native Center/Bounds via PlatformArrangeHandler as part of the same call that reserves the parent's inset, and UIKit's top-down layoutSubviews ordering 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's PublicAPI.Unshipped.txt (missing SafeAreaEdges.Container.get) was investigated and confirmed present at this PR's actual base (via the authoritative PR diff, not a stale local git diff) — not introduced by this PR, out of scope.
  • ContentPage.GetDefaultSafeAreaEdges() returning None even though its effective computed default is Container on iOS/MacCatalyst — inert in practice since ISafeAreaViewStrategy takes precedence for built-in controls; uncorroborated and no concrete consequence identified. Discarded.
  • An open question about whether MauiScrollView could 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 subtree InvalidateSafeArea propagation. 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.

Comment thread src/Core/src/Platform/iOS/MauiView.cs Outdated
Comment thread src/Core/src/Platform/iOS/MauiView.cs Outdated
Comment thread src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs
Comment thread src/Core/src/Platform/iOS/MauiView.cs
Comment thread src/Controls/tests/DeviceTests/Elements/View/ViewTests.iOS.cs
Comment thread src/Controls/src/Core/Element/Element.cs Outdated
Copilot CI and others added 2 commits August 26, 2026 14:50
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
@kubaflo

kubaflo commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Final review and merge-conflict follow-up is pushed at 542e0e9208ac9121a28948eef94941aa0ec7425a (review fixes in c265a307d821eea36c61116b1a549fdf7a9e03f6).

  • Addressed all six findings from the exact-310d adversarial review: pixel-gated overlap caching, screen-to-window keyboard coordinates, inactive scroll-ancestor handling, non-SoftInput descendant convergence, real MAUI Grid arrange coverage, and reusable specificity-change metadata.
  • Merged current net11.0 head bedd1b18b7682193e05b47267509cec8c49c6853 and resolved the MapSafeAreaEdges conflict while preserving both the new upstream initial-connection guard and this PR dynamic invalidation.
  • Post-merge validation passes: iOS Simulator 56/56, Mac Catalyst 56/56, Android emulator 53/53, and focused unit tests 174/174.
  • Five focused mutations were killed: four Apple variants each fell to 55/56; disabling specificity metadata fell to Android 51/53 with the exact listener regressions.
  • Raw Azure build 1567853 audit found 24 successful jobs and only the target-branch Windows Debug/Release AssertEventually compile failure; both Windows Helix jobs passed. The upstream fix is included in this merged head.

All eight outstanding review threads are resolved and the PR description now records the merged-head evidence.

@kubaflo

This comment has been minimized.

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

Copilot-Session: de9c7c01-82c4-42fd-9ab7-882279aadd15
@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Aug 26, 2026
@kubaflo

kubaflo commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Additional exact-head hardening is pushed at 104f8146610f38465118de78bdd2c2f9336e4751.

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 0.4-pixel steps and proves the cumulative 0.8-pixel move invalidates. Restored iOS Simulator and Mac Catalyst suites pass 56/56; reverting only the absolute-bucket comparison produces 55/56, with UnchangedKeyboardGeometryKeepsAncestorSafeAreaCache as the sole failure. The iOS mutation and restored run were repeated on an isolated CoreSimulator to exclude concurrent bundle interference.

@kubaflo

This comment has been minimized.

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread src/Core/src/Platform/iOS/MauiView.cs Outdated

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 5 findings

See inline comments for details.

Comment thread src/Core/src/Platform/iOS/MauiView.cs
Comment thread src/Core/src/Platform/iOS/MauiView.cs
Comment thread src/Core/src/Platform/iOS/MauiView.cs Outdated
Comment thread src/Core/src/Platform/iOS/MauiScrollView.cs Outdated
Comment thread src/Controls/src/Core/BindableProperty.cs
MauiBot

This comment was marked as outdated.

@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR s/agent-review-in-progress AI review is currently running for this PR labels Aug 26, 2026
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
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Aug 26, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: de9c7c01-82c4-42fd-9ab7-882279aadd15
@MauiBot

MauiBot commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

AI Review Summary

@kubaflo — new AI review results are available based on commit 79cdc74.

Gate Inconclusive Confidence Medium Platform iOS


🗂️ Review Sessions — click to expand

[!WARNING]
This run reviewed commit 79cdc74, but the PR advanced to 3d3c42b while it was running. These results are informational; re-run /review for the current head.


🚦 Gate — Test Before & After Fix

Gate Result: ⚠️ INCONCLUSIVE

Platform: IOS · Base: net11.0 · Merge base: ec79089f

🩺 Could not verify — environment/infrastructure error. The gate ran the tests but hit an environment error (an emulator/simulator/Appium/XHarness flake, a device that would not boot, or an empty/invalid result file), so it could not record a real pass/fail. The ⚠️ ENV ERROR marks below are infrastructure, not test failures — this is not a problem with your PR. Comment /review to retry on a fresh agent.

XHarness did not produce the expected fresh result 'testResults.xml' for requested class(es) 'Microsoft.Maui.DeviceTests.ScrollViewHandlerTests' (the target tests did not run).

⚠️ Gate coverage limitations

  • The A/B gate did not verify 1 dropped DeviceTest group(s): ViewTests (CustomViewSafeAreaEdgesReachMauiView, ChangingParentSafeAreaEdgesInvalidatesDescendants, MeasureInvalidatedParentDoesNotBlockDescendantSafeAreaInvalidation, ParentSafeAreaSuppressionDoesNotDependOnLayoutOrder, ResolvedBottomEdgeSkipsFartherAncestorKeyboardGeometry, ResidualParentInsetDoesNotSuppressChildSafeArea, ParentHandledEdgeLookupStopsWhenAllEdgesAreResolved, EmptySafeAreaSkipsParentHandledEdgeLookup, EmptyManualScrollViewSafeAreaSkipsParentHandledEdgeLookup, KeyboardSafeAreaChangesInvalidateDescendants, ParentContainerSafeAreaDoesNotSuppressChildKeyboardSafeArea, ParentAndChildKeyboardSafeAreasDoNotDoublePadArrangedChild, ParentAndChildKeyboardSafeAreasProtectOverflowingChild, UnchangedKeyboardGeometryKeepsAncestorSafeAreaCache, ChangedKeyboardOverlapInvalidatesNonSoftInputDescendants, SoftInputAncestorInsideScrollViewDoesNotSuppressKeyboardAutoScroll, KeyboardFrameConvertsFromScreenToWindowCoordinates, FloatingKeyboardUsesClampedViewIntersection, NestedKeyboardSafeAreasUseCrossPlatformArrange, ParentOnlySuppressesOverlappingChildSafeAreaEdges, ParentOnlySuppressesOverlappingScrollViewSafeAreaEdges, SystemAdjustedScrollViewInsetsAreNotSuppressedByParent, ChangingAncestorSafeAreaEdgesInvalidatesEdgeDisjointGrandchild). Deep UI Tests runs HostApp UI categories only and does not execute DeviceTests; separate device-test validation is required.
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 ⚠️ ENV ERROR
📱 ScrollViewHandlerTests (LegacySafeAreaViewWithoutModernContractRemainsEdgeToEdge) Category=ScrollView ⚠️ ENV ERROR ⚠️ ENV ERROR
🔴 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.aotprofile
  • src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-blazor.aotprofile.txt
  • src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-sc.aotprofile
  • src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-sc.aotprofile.txt
  • src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui.aotprofile
  • src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui.aotprofile.txt
  • src/Controls/src/Core/BindableObject.cs
  • src/Controls/src/Core/BindableProperty.cs
  • src/Controls/src/Core/Border/Border.cs
  • src/Controls/src/Core/ContentPage/ContentPage.cs
  • src/Controls/src/Core/ContentView/ContentView.cs
  • src/Controls/src/Core/Element/Element.cs
  • src/Controls/src/Core/InputView/InputView.cs
  • src/Controls/src/Core/Layout/Layout.cs
  • src/Controls/src/Core/Page/Page.cs
  • src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/SafeAreaElement.cs
  • src/Controls/src/Core/ScrollView/ScrollView.cs
  • src/Core/src/Core/ISafeAreaElement.cs
  • src/Core/src/Core/ISafeAreaView2.cs
  • src/Core/src/Handlers/View/ViewHandler.Android.cs
  • src/Core/src/Handlers/View/ViewHandler.cs
  • src/Core/src/Handlers/View/ViewHandler.iOS.cs
  • src/Core/src/Platform/Android/MauiWindowInsetListener.cs
  • src/Core/src/Platform/Android/SafeAreaExtensions.cs
  • src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs
  • src/Core/src/Platform/iOS/MauiScrollView.cs
  • src/Core/src/Platform/iOS/MauiView.cs
  • src/Core/src/Platform/iOS/SafeAreaPadding.cs
  • src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt

New files (not reverted):

  • src/Core/src/Core/ISafeAreaInsets.cs
  • src/Core/src/Core/ISafeAreaViewStrategy.cs

📋 Pre-Flight — Context & Validation

PR #37750 Pre-Flight

Context

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.cs
  • src/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.json and report Blocked without editing if NewFiles is non-empty.
  • The worktree contains pre-existing unrelated modifications and untracked files under .github/scripts, .github/skills, and eng/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

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 ContentPage can expose different SafeAreaEdges values 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:

  1. .github/.baseline-state.json is absent (verified: NO BASELINE STATE FILE). Without it there
    is no RevertedFiles allow-list, so every tracked path is read-only and no edit is permitted.
  2. The PR adds production filessrc/Core/src/Core/ISafeAreaInsets.cs and
    src/Core/src/Core/ISafeAreaViewStrategy.cs (git diff HEAD^ HEAD --diff-filter=A). Had baseline
    succeeded, NewFiles would be non-empty, which is itself a mandated pre-edit Blocked because 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/scripts and eng/scripts remain untouched, as do
    all pre-existing untracked paths. No git checkout/restore/reset/clean/stash and no rm
    was used at any point.
  • Only CustomAgentLogsTmp/PRState/37750/PRAgent/try-fix/attempt-1, .../try-fix-1/content.md and
    .../try-fix/content.md were written. .../gate/content.md was 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:

  1. .github/.baseline-state.json was absent after failure, so no RevertedFiles edit allow-list existed.
  2. The local HEAD^..HEAD diff adds src/Core/src/Core/ISafeAreaInsets.cs and src/Core/src/Core/ISafeAreaViewStrategy.cs; therefore a successful baseline would have non-empty NewFiles, 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 -Restore

It 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 MauiScrollView inset 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): EntriesScrollingPageTest leaves an entry below the keyboard, directly overlapping the PR's changes to iOS keyboard overlap, safe-area propagation, KeyboardAutoManagerScroll, MauiView, and MauiScrollView.

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.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Aug 27, 2026
@kubaflo

kubaflo commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

The AI summary is based on stale head 79cdc74, as its own warning notes. Current head 3d3c42b incorporates the review follow-ups for system-adjusted nested scroll insets, the hidden-keyboard fast path, API-contract documentation, and the Android connecting-handler guard. This branch is actively owned and running the targeted keyboard/UI validation now, so I am leaving that live validation flow undisturbed; a current-head review result can supersede this informational report.

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-safearea Issues/PRs that have to do with the SafeArea functionality p/0 Current heighest priority issues that we are targeting for a release. platform/ios s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-review-in-progress AI review is currently running for this PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants