Skip to content

August 10th, Candidate - #37393

Merged
kubaflo merged 113 commits into
mainfrom
inflight/candidate
Sep 11, 2026
Merged

kubaflo merged 113 commits into
mainfrom
inflight/candidate

Conversation

@kubaflo

@kubaflo kubaflo commented Aug 13, 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!

What's Coming

This draft promotes the current inflight/candidate contents into main for the .NET 10 SR11 (10.0.110) candidate cycle.

Candidate Snapshot

Stabilization Since the Initial Cut

Rebase Validation

  • Commit accounting: 111 exact mappings with no modified, dropped, or added patches
  • Independent reference merge and rebased branch have the identical tree ID 3e10281363c889cbafd4de790911efb0ae365e2a
  • BuildTasks succeeds with 0 warnings and 0 errors across the restored Android, iOS, Mac Catalyst, and .NET targets
  • All 39 focused ScrollViewUnitTests pass
  • No merge commits, conflict markers, or new whitespace warnings; the 26 existing whitespace warnings remain unchanged
  • Post-rebase maui-pr, maui-pr-devicetests, and maui-pr-uitests runs complete
  • Candidate-only build and test failures triaged
  • Release-readiness assessment completed

This PR remains a draft while candidate validation and stabilization continue.

@kubaflo
kubaflo temporarily deployed to copilot-pat-pool August 13, 2026 11:39 — 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 -- 37393

Or

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

@kubaflo
kubaflo temporarily deployed to copilot-pat-pool August 13, 2026 11:39 — with GitHub Actions Inactive
@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 13, 2026 11:39 — with GitHub Actions Inactive
@kubaflo

kubaflo commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

/azp run maui-pr-uitests

@kubaflo

kubaflo commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

/azp run maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

1 similar comment
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@kubaflo

kubaflo commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

/azp run maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@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 30, 2026

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

@@ -18,7 +18,7 @@ public partial class TimePickerHandler : ITimePickerHandler
{
public static IPropertyMapper<ITimePicker, ITimePickerHandler> Mapper = new PropertyMapper<ITimePicker, ITimePickerHandler>(ViewHandler.ViewMapper)
{
#if ANDROID || WINDOWS
#if IOS || ANDROID || WINDOWS

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.

🔍 AI-Generated Review (multi-model)

[moderate] Platform-Specific Code Scoping — This guard adds IOS but omits MACCATALYST, while this same PR adds a new TimePickerHandler.MacCatalyst.cs MapBackground implementation (the one that calls RemoveBackgroundLayer() / resets BackgroundColor when the brush is cleared). MACCATALYST and IOS are separate compilation symbols in this repo (see the #if IOS || MACCATALYST pattern used throughout src/Core/src/Handlers, and ImageButtonHandler.cs in this same PR which spells out __IOS__ || MACCATALYST), so on MacCatalyst the entry is never added to the mapper and the new MacCatalyst code is dead. Concrete scenario: set TimePicker.Background to a gradient and then clear it on MacCatalyst — the specialized clear path never runs and the stale background layer remains. Suggested: #if IOS || MACCATALYST || ANDROID || WINDOWS.

@@ -25,6 +25,7 @@ public partial class DatePickerHandler : IDatePickerHandler
#if ANDROID || WINDOWS
[nameof(IDatePicker.Background)] = MapBackground,
#elif IOS
[nameof(IDatePicker.Background)] = MapBackground,

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.

🔍 AI-Generated Review (multi-model)

[moderate] Platform-Specific Code Scoping — Same gap as TimePickerHandler.cs: this new [nameof(IDatePicker.Background)] = MapBackground entry sits under #elif IOS, which does not include MacCatalyst, yet the PR adds MapBackground to DatePickerHandler.MacCatalyst.cs (registered nowhere else — the only mapper registrations for this handler are lines 26 and 28 plus DatePickerHandler2.Android.cs). On MacCatalyst the DatePicker falls back to the generic ViewMapper background mapping, so the newly added ImageSourcePaint support and the RemoveBackgroundLayer() clear path never execute. Suggested: extend the branch to #elif IOS || MACCATALYST (keeping MapFlowDirection iOS-only if that is intended).


// Strong reference to the launching activity — prevents GC from collecting the CWT entry
// before Register() can migrate the TCS to the new activity after a config change.
ComponentActivity _inFlightActivity;

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.

🔍 AI-Generated Review (multi-model)

[moderate] Memory Leak Prevention — _inFlightActivity is a strong reference held by a process-wide singleton (PickVisualMediaForResult.Instance / PickMultipleVisualMediaForResult.Instance), replacing the previous WeakReference<ComponentActivity>. It is cleared only in the result callback, in CancelPendingRequest (which ActivityStateManager.OnActivityDestroyed invokes only when IsFinishing == true), and in MigratePendingRequests (only when IsChangingConfigurations == true). Concrete scenario: the picker is open and the OS destroys the launching Activity to reclaim memory — IsFinishing and IsChangingConfigurations are both false, so no path clears the field and the destroyed Activity plus its whole view hierarchy is retained until the next picker launch. Consider storing a WeakReference<ComponentActivity> here (the ConditionalWeakTable entry is already kept alive by the callback's captured registeredActivity).

{
lock (gate)
{
PruneDeadSubscriptions();

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.

🔍 AI-Generated Review (multi-model)

[moderate] Performance-Critical Path — HasHandlers calls PruneDeadSubscriptions() inside the lock, directly contradicting the doc comment immediately above it ("This is a plain count check and does not prune dead subscriptions on every read ... so it is cheap to query on every sensor reading"). Accelerometer.OnChanged gates shake processing on HasHandlers, so every accelerometer sample (up to SensorSpeed.Fastest) now takes a lock and walks/mutates the whole subscription list instead of doing the advertised count check. The same pattern is duplicated at line 329. Either drop the PruneDeadSubscriptions() call (pruning already happens in Raise/Subscribe/Unsubscribe) or correct the documented contract.

// would incorrectly skip cleanup the next time it disconnects a newly connected,
// non-disposed UIStepper if the handler instance is reused.
_platformViewDisposed = false;

_proxy.Connect(VirtualView, platformView);
}

protected override void DisconnectHandler(UIStepper platformView)
{
base.DisconnectHandler(platformView);

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.

🔍 AI-Generated Review (multi-model)

[minor] Handler Mapper and Property Patterns — The _platformViewDisposed guard added below (line 84) is documented as running "before touching platformView again ... [so] a second DisconnectHandler call on an already-disposed platform view" is safe, but base.DisconnectHandler(platformView) is invoked first on line 79, i.e. before the guard. On a second disconnect of the same UIStepper (which this code disposes at line 113 on iOS/Mac 26+), any base-class access to the native handle happens on an already-disposed object. Moving if (_platformViewDisposed) return; above the base call would make the code match its stated intent.

@kubaflo

kubaflo commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

Updated [ShimSkiaSharp](https://github.com/wieslawsoltes/Svg.Skia) from
5.1.1 to 5.2.3.

<details>
<summary>Release notes</summary>

_Sourced from [ShimSkiaSharp's
releases](https://github.com/wieslawsoltes/Svg.Skia/releases)._

## 5.2.3

## What's Changed
* Update SVG upstream and prepare v5.2.3 by @​wieslawsoltes in
wieslawsoltes/Svg.Skia#562


**Full Changelog**:
wieslawsoltes/Svg.Skia@v5.2.2...v5.2.3

## 5.2.2

## What's Changed
* Add styled Avalonia SVG source resources by @​wieslawsoltes in
wieslawsoltes/Svg.Skia#560
* Keep run typeface when character fallback finds nothing by
@​donaldsteele in wieslawsoltes/Svg.Skia#559
* Add net10.0 target to MAUI controls by @​wieslawsoltes in
wieslawsoltes/Svg.Skia#561

## New Contributors
* @​donaldsteele made their first contribution in
wieslawsoltes/Svg.Skia#559

**Full Changelog**:
wieslawsoltes/Svg.Skia@v5.2.1...v5.2.2

## 5.2.1

## What's Changed
* Fix Linux SkiaSharp native asset version mismatch by @​wieslawsoltes
in wieslawsoltes/Svg.Skia#555
* Fix Avalonia SVG opacity layers by @​wieslawsoltes in
wieslawsoltes/Svg.Skia#556


**Full Changelog**:
wieslawsoltes/Svg.Skia@v5.2.0...v5.2.1

## 5.2.0

## What's Changed
* Update SkiaSharp to 4.148.0 and HarfBuzzSharp to 14.2.0 by
@​mattleibow in wieslawsoltes/Svg.Skia#544
* Restore Avalonia CurrentColor and CSS overrides by @​wieslawsoltes in
wieslawsoltes/Svg.Skia#549
* Strong-name sign all packaged assemblies by @​wieslawsoltes in
wieslawsoltes/Svg.Skia#547
* Honor variable font weights during text shaping by @​wieslawsoltes in
wieslawsoltes/Svg.Skia#548
* Ensure svg model has no trimmer warnings by @​OleRoss in
wieslawsoltes/Svg.Skia#551

## New Contributors
* @​OleRoss made their first contribution in
wieslawsoltes/Svg.Skia#551

**Full Changelog**:
wieslawsoltes/Svg.Skia@v5.1.1...v5.2.0

Commits viewable in [compare
view](wieslawsoltes/Svg.Skia@v5.1.1...v5.2.3).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ShimSkiaSharp&package-manager=nuget&previous-version=5.1.1&new-version=5.2.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@kubaflo

kubaflo commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

@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 Sep 10, 2026

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

AI Review Summary

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

Gate Passed Confidence Low Platform Android


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: 699f16e4

✅ Fix verified — 3 test(s) reproduce the bug (FAIL without the fix → PASS with it). 1 test(s) pass in both states and are not bug-reproducing; under the "at least one test reproduces the bug and none regress" rule they don't block the gate.

⚠️ Gate coverage limitations

  • The A/B gate did not verify 15 dropped DeviceTest group(s): MauiWindowInsetListenerTests (ViewTrackedBeforeImeAnimationStaysGatedAndAttachedViewBypassesGateOnce, GateExemptionIsClearedWhenTheAnimationEnds, GateReleasesSynchronouslyWhenNoAttachedViewIsAvailable, ReapplyStaysOwedWhenTheAnimationEndsWithNoUsablePoster, GatedDispatchesTriggerOneReapplyWhenTheAnimationEnds, StaleGateReleaseDoesNotOpenTheGateOfTheNextAnimation), ExplicitIntentTestActivity, HybridWebViewTests_MessageOrigins (RawMessagesFromOtherOriginsAreIgnored, InvokeCompletionsFromOtherOriginsAreIgnored), BlazorWebViewTests (BlazorWebViewDoesNotClampSmallCssFontSizes, OrdinaryExternalUriCreatesViewIntent, AndroidAppUriPreservesImplicitRoutingData, IntentUriPreservesImplicitRoutingData, StructuredIntentUriRemovesExplicitTargets, StructuredIntentUriRemovesUnsupportedActivityFlags, StructuredIntentUriWithExplicitComponentDoesNotLaunchActivity, IntentUriWithoutBrowsableHandlerDoesNotLaunchActivity, IntentUriWithBrowsableHandlerLaunchesActivity, BlazorStartupSetsUpWindowExternal, BlazorStartupSetsStartingAndStartedFlags, BlazorStartupCapturesNativePort, BlazorStartupScriptIsIdempotent, BlazorStartupRejectionPreservesLiveBridge, BlazorMessageDispatchOnlyProcessesNativeSourceMessages), EntryHandlerTests (SemanticDescriptionInitializesAccessibilityContentDescription, PasswordKeyboardRespectsIsPasswordFalseInitially, PasswordKeyboardIsPasswordToggleWorksCorrectly), CollectionViewTests (ItemsSourceDoesNotLeak), FormattedStringTests (UpdatingFormattedTextClearsPreviousHighlights), SwipeViewTests (SwipeItemCommandExecutesViaAccessibilityActionClick, SwipeItemViewCommandExecutesViaAccessibilityActionClick), MapTests (UpdatingSingleElementPreservesTrackingForOtherElements), CarouselView2 (TableViewSourceReplacementUnsubscribesPreviousSource, ContextActionsCellDisposalUnsubscribesCell, SecondaryToolbarItemDisposalUnsubscribesAfterCustomViewReplacement, HandlerDoesNotLeak), NavigationPageTests (PushingToPageWithoutNavigationBarClearsAppBarInsetPadding), PickerHandlerTests (LongSelectedTextScrollsHorizontallyAndRemainsReadOnly, TapJitterClicksButFastSwipeDoesNot, ExactTouchSlopDoesNotClick, VerticalDragDoesNotClick, CanceledDragDoesNotSuppressNextTap, PointerReplacementWithoutMovementRemainsATap, MovementMethodRemainsDisabled), ScrollViewTests (DoesNotLeak), ControlsHandlerTestBase, WebViewHelpers. Deep UI Tests runs HostApp UI categories only and does not execute DeviceTests; separate device-test validation is required.
  • The A/B gate did not verify 53 dropped UI test group(s): Issue35890, Issue36179, Issue36267, Issue36302, Issue36523, Issue36543, Issue36629, Issue36652, Issue36694, Issue36697, Issue36801, Issue36801DeferredElement, Issue36933, Issue37012, Issue35643, Issue37145, Issue34472, Issue34271, ShellSearchHandlerFeatureTests, ShellFlyoutHeaderScrollViewContent, SafeArea_GridFeatureTests, Issue17389, Issue34396, SafeArea_BorderFeatureTests, Issue19883, Issue30381, Issue31480, Issue31917, Issue26924, Issue37187, Material3ImageFeatureTests, Material3TimePickerFeatureTests, SafeArea_ContentPageFeatureTests, SafeArea_ContentViewFeatureTests, ShapesFeatureTests, Material3SwitchFeatureTests, Material3LabelFeatureTests, SoftInputExtensionsPageTests, BorderFeatureTests, SwitchFeatureTests, Issue34898, Issue33034, Issue31731, Issue26049, IndicatorViewFeatureTests, ImageFeatureTests, ImageButtonMaterial3FeatureTests, ImageButtonFeatureTests, ButtonMaterial3FeatureTests, ButtonFeatureTests, Bugzilla44166, LabelFeatureTests, TimePickerFeatureTests. Those UI categories are exercised separately by the Deep UI Tests stage, without the gate's before/after comparison.
Test Without Fix (expect FAIL) With Fix (expect PASS)
🧪 CommandTests CommandTests 🛠️ BUILD ERROR ✅ PASS — 97s
🧪 CSharpExpressionDiagnosticsTests CSharpExpressionDiagnosticsTests ✅ FAIL — 37s ✅ PASS — 24s
🧪 FlexLayoutTests FlexLayoutTests 🛠️ BUILD ERROR ✅ PASS — 14s
🧪 FormattedStringTests FormattedStringTests 🛠️ BUILD ERROR ✅ PASS — 14s
🧪 GeometryGroupMemoryTests GeometryGroupMemoryTests 🛠️ BUILD ERROR ✅ PASS — 14s
🧪 IndicatorViewTests IndicatorViewTests 🛠️ BUILD ERROR ✅ PASS — 14s
🧪 IntegrationTests IntegrationTests ✅ FAIL — 31s ✅ PASS — 20s
🧪 LayoutMemoryTests LayoutMemoryTests 🛠️ BUILD ERROR ✅ PASS — 14s
🧪 LinearGradientBrushTests LinearGradientBrushTests 🛠️ BUILD ERROR ✅ PASS — 14s
🧪 ScrollViewUnitTests ScrollViewUnitTests 🛠️ BUILD ERROR ✅ PASS — 14s
🧪 TableViewUnitTests TableViewUnitTests 🛠️ BUILD ERROR ✅ PASS — 14s
📄 CSharpExpressions.sgen CSharpExpressions.sgen 🛠️ BUILD ERROR 🔍 NO MATCH
📄 Tests Tests 🛠️ BUILD ERROR ✅ PASS — 119s
📱 Android_Permissions_Tests (RequestAsync_FromBackgroundThread_ThrowsPermissionException) Category=Permissions ✅ FAIL — 765s ✅ PASS — 353s
📱 ShellTests (ImplicitLabelStyleOverridesDefaultFlyoutItemTextColor, FlyoutItemLabelClassStyleOverridesDefaultFlyoutItemTextColor, HiddenShellNavigationBarClearsAppBarInsetPadding) Category=Shell 🛠️ BUILD ERROR ✅ PASS — 465s
🖥️ Issue36505 Issue36505 🔍 NO MATCH 🔍 NO MATCH
🖥️ Issue35889 Issue35889 ❌ PASS — 1755s ✅ PASS — 564s
🔴 Without fix — 🧪 CommandTests: 🛠️ BUILD ERROR · 126s

Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/ScrollViewUnitTests.cs(987,77): error CS0234: The type or namespace name 'IScrollViewportProvider' does not exist in the namespace 'Microsoft.Maui.Handlers' (are you missing an assembly reference?) [/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
🟢 With fix — 🧪 CommandTests: PASS ✅ · 97s

(no coded error found; showing last 1200 chars)

  Passed GenericCanExecute(expected: True) [< 1 ms]
  Passed GenericCanExecute(expected: False) [< 1 ms]

This Gate section was shortened to keep every required review section visible. Full details remain available in the pipeline build artifacts.


📋 Pre-Flight — Context & Validation

Pre-Flight: PR #37393

PR Context

  • Title: August 10th, Candidate
  • State: Open draft
  • Base / head: main / inflight/candidate
  • Remote head: bcf0c90b67dff60de8d6df806258f6c945639a5a
  • Local review materialization: ab5238f090 (PR #37393 squashed for review)
  • Merge base: 699f16e4c239cae5486c63ed472039aa5c4e6cf2
  • Scope: 1,541 files, 32,078 additions, 3,474 deletions. This is the .NET 10 SR11 candidate rollup, not a single-purpose bug-fix PR.

Bounded STEP 5a Target

The Android permissions regression is the bounded target because the gate recorded a genuine behavioral fail/pass result for it and the implementation is independently traceable in git history to Fix stale Android permission requests after off-main-thread failures (#37056).

The existing implementation in src/Essentials/src/Permissions/Permissions.android.cs:

  1. Checks MainThread.IsMainThread before allocating or registering a pending request.
  2. Replaces the shared mutable request-code field with a request-local value.
  3. Removes the matching pending request if ActivityCompat.RequestPermissions throws.
  4. Removes the request under the lock in OnRequestPermissionsResult, then completes its task outside the lock.

The added regression test, src/Essentials/test/DeviceTests/Tests/Android/Permissions_Tests.cs, invokes a runtime permission request from Task.Run, expects PermissionException, and verifies that the pending-request dictionary count is unchanged.

Test Evidence and Scope

  • Platform: Android
  • Primary command: pwsh .github/skills/run-device-tests/scripts/Run-DeviceTests.ps1 -Project Essentials -Platform android -TestFilter "Category=Permissions"
  • Primary test: Android_Permissions_Tests.RequestAsync_FromBackgroundThread_ThrowsPermissionException
  • Gate result: FAIL without the fix (1 failed test), PASS with the fix.
  • No separate mandatory regression command was supplied for STEP 5a. The primary category is the only requested test scope for this bounded target; the gate must not be re-run.

The gate also detected unrelated source-generation and integration fail/pass signals across the rollup. Those are outside this bounded Android candidate target. Its Shell device-test group failed to build in the broken baseline because other candidate APIs were removed, so it is not evidence for the permission-request mechanism.

Try-Fix Constraints

  • Target implementation file: src/Essentials/src/Permissions/Permissions.android.cs.
  • The PR-added permission test is evidence only and must not be modified.
  • Each attempt must use EstablishBrokenBaseline.ps1 and obey its RevertedFiles allow-list.
  • The candidate rollup includes PR-added production files. If .github/.baseline-state.json has any NewFiles, the try-fix skill requires a truthful Blocked result before editing, followed by the exact scripted restore.
  • Pre-existing dirty and untracked pipeline files are harness-owned and must remain untouched.

🔬 Code Review — Deep Analysis

Expert PR Evaluation

Verdict: NEEDS_DISCUSSION

Confidence: low

Independent Assessment

What this changes: The Android permission request path now rejects off-main-thread calls before registering pending state, uses a request-local request code, removes the exact pending entry if native request dispatch throws, and removes callback state under the lock before completing its task outside the lock.

Inferred motivation: Prevent failed permission requests from leaving stale entries in the static pending-request dictionary, avoid races through a shared mutable request code, and avoid task-continuation reentrancy while holding the dictionary lock.

Findings

No actionable defects were found on added or modified lines. The required inline findings artifact contains [].

Test Assessment

The added Android device test directly covers the reported background-thread regression by asserting both PermissionException and an unchanged pending-request count. The trusted Gate reports that this test fails without the fix and passes with it.

The expert identified one non-blocking coverage improvement: add a focused device test that forces ActivityCompat.RequestPermissions to throw on the main thread and verifies the newly added catch removes the matching pending entry. This would directly cover the rollback branch rather than only the earlier background-thread guard.

Failure-Mode Probing

  • Off-main-thread request: Throws before activity lookup, request-code allocation, or dictionary registration, so no stale pending entry is created.
  • Native request dispatch throws synchronously: The catch removes only the entry whose request code and completion source both match this request, then rethrows.
  • Concurrent callback completion: The callback obtains and removes its completion source under the lock, then completes it outside the lock; a later duplicate callback finds no entry and returns.
  • Concurrent request-code reuse: Cleanup checks both the key and TaskCompletionSource identity, so an old failing request cannot remove a newer request that reused the same code.

External Output Contract

Not applicable.

Summary

The submitted mechanism is sound for the bounded Android permissions regression, and no inline code defect was found. The verdict remains NEEDS_DISCUSSION with low confidence only because the expert could not independently retrieve required-check status; the caller-provided trusted Gate does establish targeted fail-without/pass-with evidence.


🛠️ Try-Fix — Analysis & Comparison

Aggregate Try-Fix Results: PR #37393

Candidate 1 — Registration Lease

  • Model: gpt-5.3-codex
  • Approach: Encapsulate pending-request ownership in a scoped registration lease that removes its request entry unless ownership is explicitly transferred to the asynchronous Android permission callback.
  • Prior approach avoided: The existing #37056 implementation relies on an early main-thread precheck plus branch-specific cleanup around ActivityCompat.RequestPermissions, a request-local code, and callback dequeue/completion separation.
  • Mechanism-level difference: A dispose-by-default lease would make cleanup an ownership invariant rather than relying on each synchronous failure path to remember to remove the dictionary entry.
  • Files changed: None.
  • Diff: Empty.
  • Test: Not run; the attempt was blocked before edits.
  • Result: Blocked
  • Failure analysis: EstablishBrokenBaseline.ps1 rejected the pre-existing dirty tracked worktree. No .github/.baseline-state.json was created, so no RevertedFiles edit allow-list existed and the skill prohibited implementation or testing.
  • Self-review: 0 findings.
  • Artifacts: CustomAgentLogsTmp/PRState/37393/PRAgent/try-fix/attempt-1/
  • Detailed narrative: CustomAgentLogsTmp/PRState/37393/PRAgent/try-fix-1/content.md
  • Restoration: The exact command pwsh .github/scripts/EstablishBrokenBaseline.ps1 -Restore ran and reported the valid no-state blocked result (No baseline state found, Restored False).

Candidate 2 — Completion-Coupled Eviction

  • Model: gpt-5.6-sol
  • Approach: Associate each request-local code and completion source with cleanup triggered by the request task reaching a terminal result or exception.
  • Prior approaches avoided: Unlike the current #37056 implementation, cleanup would not depend on an early guard plus explicit rollback branches. Unlike candidate 1, it would not use a scoped registration lease or dispose-by-default ownership.
  • Mechanism-level difference: Pending-state eviction would be coupled to task completion, making result or exception completion the single cleanup trigger rather than precondition branches or lease disposal.
  • Files changed: None.
  • Diff: Empty.
  • Test: Not run; the attempt was blocked before edits.
  • Result: Blocked
  • Failure analysis: EstablishBrokenBaseline.ps1 again rejected the pre-existing dirty tracked worktree. No .github/.baseline-state.json was created, so no RevertedFiles edit allow-list existed and the skill prohibited implementation or testing.
  • Self-review: 0 findings.
  • Artifacts: CustomAgentLogsTmp/PRState/37393/PRAgent/try-fix/attempt-2/
  • Detailed narrative: CustomAgentLogsTmp/PRState/37393/PRAgent/try-fix-2/content.md
  • Restoration: The exact command pwsh .github/scripts/EstablishBrokenBaseline.ps1 -Restore ran and reported the valid no-state blocked result (No baseline state found, Restored False). Target files remained unchanged.

Current Aggregate

Candidate Model Result Code/Test Outcome
1 gpt-5.3-codex Blocked No edit or test; baseline state could not be established
2 gpt-5.6-sol Blocked No edit or test; baseline state could not be established

STEP 5a Outcome

Two bounded, mechanism-level alternatives were designed, but neither could be implemented or tested because the mandatory baseline script rejected the harness's pre-existing dirty tracked worktree before creating its restoration state. No candidate code changes remain in the worktree. STEP 5b must treat both candidates as Blocked, not as passing alternatives.


📝 PR Finalize — Recommended Title & Description

Assessment: ✏️ Recommend updating — the current title is not searchable or specific, and the description's snapshot is two commits behind the submitted PR HEAD (113 commits at bcf0c90b, not 111 at b643a7e3).

Recommended title

[Cross-platform] .NET 10 SR11: Promote inflight/candidate into main

Recommended description

## What's Coming

This draft promotes the current `inflight/candidate` contents into `main` for the .NET 10 SR11 (`10.0.110`) candidate cycle.

## Candidate Snapshot

- Initial source: `inflight/current`, followed by candidate-specific stabilization and approved backports
- Rebased onto `main`: [`b96aa036`](https://github.com/dotnet/maui/commit/b96aa036b89fe41fe1ce6cae63a3f2d1550e5682)
- Current candidate head: [`bcf0c90b`](https://github.com/dotnet/maui/commit/bcf0c90b67dff60de8d6df806258f6c945639a5a)
- Final candidate commits relative to `main`: [113 commits](https://github.com/dotnet/maui/compare/b96aa036b89fe41fe1ce6cae63a3f2d1550e5682...inflight/candidate)
- Rebase accounting: all 111 pre-rebase commits map exactly to 111 rebased commits, with no modified, dropped, or added patches; two stabilization commits were added afterward
- Current candidate tree: `67c056e509c2332ed17e14de6fc2fca848d22b0d`
- Patch version: `110`
- PR #37033 remains intentionally excluded because it merged into `inflight/current` after the initial candidate cut.

## Stabilization Since the Initial Cut

- #37669 fixes the MauiBlazorWebView device-test build.
- #37672 reverts unstable iOS and Mac Catalyst leak tests.
- #37810 reverts the iOS wrapped-scroll-content large-title change.
- #37837 completes iOS `ScrollView` requests inside collapsed branches.
- #37438 updates the first set of candidate test failures.
- #38021 fixes the Windows `ContentPresenter` device-test regression; its patch is now supplied by `main`.
- #38096 reverts the Accelerometer weak-event change.
- #38126 backports the BlazorWebView and HybridWebView fixes.
- #38028 updates the remaining candidate test failures and stale iOS snapshots.
- #38392 updates candidate test failures after the latest stabilization.
- #38390 updates the SkiaSharp dependency group.

## Rebase Validation

- [x] Commit accounting: 111 exact pre-rebase mappings with no modified, dropped, or added patches
- [x] At candidate head `b643a7e3`, the independent reference merge and rebased branch had the identical tree ID `3e10281363c889cbafd4de790911efb0ae365e2a`
- [x] Two post-rebase stabilization commits advance the submitted candidate to `bcf0c90b`

This pr-finalize section was shortened to keep every required review section visible. Full details remain available in the pipeline build artifacts.


🏁 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Winner: pr-plus-reviewer

pr-plus-reviewer preserves the submitted production fix and adds direct regression coverage for its synchronous native-dispatch rollback path. Its one required Android validation run passed, including both the original background-thread regression test and the new rollback test.

Comparative Ranking

Rank Candidate Implementation Evidence Assessment
1 pr-plus-reviewer Raw PR fix plus one test-only refinement that forces AndroidX request dispatch to throw after pending-state registration and verifies cleanup Candidate-root build succeeded with 0 warnings/errors; 283 tests passed, 0 failed; both bounded permission tests passed Winner. Same sound production mechanism as the PR, with direct coverage of the otherwise untested catch cleanup branch
2 pr Rejects off-main-thread requests before registration, uses a request-local code, conditionally rolls back synchronous dispatch failures, and completes callbacks outside the lock Trusted Gate: test fails without fix and passes with fix; expert review found no actionable defect Correct and proven for the reported stale-request regression, but its submitted test covers only the early main-thread guard, not the new post-registration rollback branch
3 try-fix-1 Proposed a dispose-by-default registration lease Blocked before edits; empty diff; no tests run Design-only candidate with no executable evidence
4 try-fix-2 Proposed completion-coupled pending-state eviction Blocked before edits; empty diff; no tests run Design-only candidate with no executable evidence and a broader lifecycle change than needed

No candidate failed a regression test. The two passing candidates therefore rank above both blocked try-fix candidates, which produced neither code nor test evidence.

Expert Review Reconciliation

The single MAUI expert pass found no actionable file:line defect in the submitted Android permission implementation or regression test; inline-findings.json is []. It identified one worthwhile non-blocking improvement: directly exercise cleanup when ActivityCompat.RequestPermissions throws synchronously. pr-plus-reviewer implements exactly that suggestion without adding a production hook, changing public API, or altering the submitted fix.

Why the Submitted PR Still Needs Changes

The raw PR is behaviorally sound, and the trusted Gate proves the reported fix. However, pr-plus-reviewer is the stronger candidate because its validated test locks in the second cleanup invariant introduced by the production change. Since the winning test is not present in submitted PR HEAD, the required recommendation is REQUEST CHANGES.

Validation and Uncertainty

The candidate command was run exactly once from /home/vsts/work/_temp/pr-37393-pr-plus-reviewer; no iterative repair/retest loop or full-suite run was performed. This comparative recommendation is bounded to the Android permissions fix selected in STEP 5a and does not claim to audit the unrelated 1,541-file SR11 rollup.


📱 UI Tests — Border,Brush,Button,CarouselView,CollectionView,DatePicker,Editor,Entry,Image,ImageButton,IndicatorView,Label,Layout,ListView,Material3,Page,RadioButton,SafeAreaEdges,ScrollView,Shape,Shell,SoftInput,SwipeView,Switch,TableView,TimePicker,ViewBaseTests,WebView

Detected UI test categories: Border,Brush,Button,CarouselView,CollectionView,DatePicker,Editor,Entry,Image,ImageButton,IndicatorView,Label,Layout,ListView,Material3,Page,RadioButton,SafeAreaEdges,ScrollView,Shape,Shell,SoftInput,SwipeView,Switch,TableView,TimePicker,ViewBaseTests,WebView

❌ Deep UI tests — 1019 passed, 2 failed, 13 skipped across 12 categories on platform-pool agent (replaces in-process counts above). The build failed to compile for 1 category (ListView), so those deep UI tests could not run. The compiler reported: error NU1801: Warning As Error: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-aspnetcore-58518050/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.Andro…. The cause is undetermined: the diagnostic is reported in unchanged file src/Essentials/src/Essentials.csproj, but a PR API/signature change can break an unchanged consumer. Compare the same build against the reviewed base before attributing it; inspect build-output.log, then re-comment /review after the build issue is resolved. The deep UI run for 1 category (CollectionView) exceeded the per-category time budget (a very long-running category or a slow build/deploy) and was stopped before finishing. This is an infrastructure/timeout issue, not a code problem; re-run the review, and if a category consistently needs more time the per-category budget can be raised (DEEP_UITEST_CATEGORY_CAP_MIN / DEEP_UITEST_HARDSTOP_MIN). See the build-output.log in the drop-deep-uitests artifact.

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
Border 65/65 ✓ —
Brush 42/42 ✓ —
Button 104/106 (2 skipped) ✓ —
CarouselView 98/99 (1 ❌) 1 diff PNG
DatePicker 45/45 ✓ —
Editor 85/88 (3 skipped) ✓ —
Entry 111/113 (1 ❌, 1 skipped) —
Image 50/52 (2 skipped) ✓ —
ImageButton 58/58 ✓ —
IndicatorView 43/43 ✓ —
Label 123/125 (2 skipped) ✓ —
Layout 195/198 (3 skipped) ✓ —
🔍 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.

Mixed / uncertain: see the grouped assessment below.

  • ℹ Uncertain — Android CarouselView visual comparison (~1 test): the 4.87% snapshot mismatch could be a baseline-only issue, but the unavailable changed-file and diff evidence prevents ruling out a PR change to shared or Android CarouselView rendering.
  • ● Unrelated — UI automation stale-element failure (~1 test): the Entry test failed because the cached ThemeButton disappeared from the DOM, a non-deterministic Selenium synchronization pattern rather than evidence of a product regression.

Strongest signal: the stale-element exception is clearly test automation flakiness; the CarouselView result needs PR scope or snapshot comparison evidence for attribution.

❌ CarouselView — 1 failed test
VerticalCarouselMandatorySingleSnapAdvancesOneCard
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerticalCarouselMandatorySingleSnapAdvancesOneCard.png (4.87% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.Issues.Issue33308.VerticalCarouselMandatorySingleSnapAdvancesOneCard() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33308.cs:line 26
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, Bi
...
❌ Entry — 1 failed test
EntryClearButtonColorShouldUpdateOnThemeChange
OpenQA.Selenium.StaleElementReferenceException : Cached elements 'By.id: com.microsoft.maui.uitests:id/ThemeButton' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
   at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumElement.Execute(String commandName, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumElement._GetAttribute(String attributeName)
   at OpenQA.Selenium.Appium.AppiumElement.<>c__DisplayClass21_0.<GetAttribute>b__0()
   at OpenQA.Selenium.Appium.AppiumElement.Ca
...

📎 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 submitted production fix is sound and passes the trusted Gate, while pr-plus-reviewer adds direct coverage for synchronous native-dispatch rollback without changing product code. Its candidate-root Android validation passed both bounded permission tests with no failures.

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 Sep 11, 2026
@kubaflo

kubaflo commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

all tests are green

@kubaflo
kubaflo marked this pull request as ready for review September 11, 2026 16:32
Copilot AI lite review requested due to automatic review settings September 11, 2026 16:32
@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 merged commit e047434 into main Sep 11, 2026
189 of 195 checks passed
@kubaflo
kubaflo deleted the inflight/candidate branch September 11, 2026 16:33
Copilot stopped reviewing on behalf of kubaflo due to an error September 11, 2026 16:53

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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

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

## Summary

Advance `main` to the .NET 10 SR12 development cycle now that
`release/10.0.1xx-sr11` has been cut from the merge of #37393
(`e047434fd22752a3e96187d55cd554a4a1c200b5`).

Change only `PatchVersion` in `eng/Versions.props` from `110` to `120`.
Keep `SdkBandVersion=10.0.100`, `PreReleaseVersionLabel=ci.main`
(including the inflight override), and `StabilizePackageVersion=false`
unchanged.

This is separate from the SR11 servicing-flip PR and does not change the
release branch.

Related: #38210.

## Validation

- Evaluated `eng/Versions.props` using `dotnet msbuild -getProperty`:
version `10.0.120`, SDK band `10.0.100`, prerelease label `ci.main`,
stable versions disabled.
- Ran the repository formatter scoped to `eng/Versions.props` and
confirmed the final diff is exactly one changed line.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-general General issues that span multiple controls, or common base classes such as View or Element platform/android platform/ios platform/macos macOS / Mac Catalyst platform/windows s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) 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.