Skip to content

[iOS & Mac] Fix and Re-enable "Handler Does Not Leak" Device Tests for Stepper control#36741

Draft
BagavathiPerumal wants to merge 2 commits into
dotnet:mainfrom
BagavathiPerumal:fix-35985-Stepper
Draft

[iOS & Mac] Fix and Re-enable "Handler Does Not Leak" Device Tests for Stepper control#36741
BagavathiPerumal wants to merge 2 commits into
dotnet:mainfrom
BagavathiPerumal:fix-35985-Stepper

Conversation

@BagavathiPerumal

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!

Issue Details

The device memory leak validation tests began failing in the Candidate branch after the test-related changes introduced in PR #35487, which exposed actual memory leaks that were previously undetected. The regressions affected the Cells Do Not Leak and Handler Does Not Leak tests on Windows, the Cells Do Not Leak test on iOS, the Handler Does Not Leak test on iOS 26.0, and the Handler Does Not Leak test on macOS 26.0, indicating platform-specific memory leak issues that require fixes.

Root cause

The issue occurs because of iOS 26's new "Liquid Glass" rendering, under which the native stepper control holds an internal, private native retain that isn't reachable or releasable through any public API — confirmed by diagnostics showing the retain count unchanged even after removing the view from its hierarchy and clearing animations.

This was specific to iOS/MacCatalyst 26 only and did not occur on earlier OS versions. A contributing factor was that subview realization and animation were being forced unconditionally, even for the common case where no directional mirroring was needed, adding an extra, avoidable native retain.

Solution description

The fix involves explicitly disposing the native stepper on disconnect, but only for iOS/MacCatalyst 26+ (guarded against being run twice on an already-disposed instance), forcing the interop layer to release its native handle immediately instead of waiting on the garbage collector.

It also involves skipping the subview realization and transform/animation work entirely unless directional mirroring is actually required, while still correctly restoring subviews that were previously mirrored if the layout reverts. The leak test was also updated to explicitly disconnect each control's handler before checking for collection, mirroring how a real app tears down views.

Validated the behavior in the following platforms

  • iOS
  • Mac
  • Android
  • Windows

Regression PR

PR #35487

Issues Fixed

Fixes #35985

Output ScreenShot

Platform Before After
Mac
BeforeFix-Stepper-MemoryLeak.Mac.1.1.mov
AfterFix-Handler.Does.not.leak.-.Stepper.Mac.26.1.mov
iOS
BeforeFix-Stepper-MemoryLeak.iOS.1.mov
AfterFix-Handler.Does.not.leak.-.Stepper.iOS.26.1.mov

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

Or

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

@BagavathiPerumal BagavathiPerumal added the community ✨ Community Contribution label Jul 23, 2026
@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.

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Jul 23, 2026
@vishnumenon2684

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 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 Jul 23, 2026
@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-gate-failed AI could not verify tests catch the bug s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Jul 24, 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

@BagavathiPerumal — new AI review results are available based on this last commit: ad6607a.

Gate Failed Confidence Low Platform iOS


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

Gate Result: ❌ FAILED

Platform: IOS · Base: main · Merge base: ee21c935

🩺 Fix does not pass the tests — every test still fails after applying the fix. The PR's change does not resolve the failure(s).

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 MemoryTests MemoryTests ✅ FAIL — 7516s ❌ FAIL — 3376s
🔴 Without fix — 📱 MemoryTests: FAIL ✅ · 7516s

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

�[40m�[37mdbug�[39m�[22m�[49m: [17:21:39.9186380] 2026-07-23 17:21:39.882890-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:43.8557450] 2026-07-23 17:21:43.855396-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:48.3217080] 2026-07-23 17:21:48.321394-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:48.3604980] 2026-07-23 17:21:48.360176-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:48.4330710] 2026-07-23 17:21:48.432701-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:22:18.4045050] 2026-07-23 17:22:18.365248-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:22:18.4393710] 2026-07-23 17:22:18.404678-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:22:48.3121690] 2026-07-23 17:22:48.311666-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
🟢 With fix — 📱 MemoryTests: FAIL ❌ · 3376s

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

�[40m�[37mdbug�[39m�[22m�[49m: [18:42:18.6797070] 2026-07-23 18:42:18.679361-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:18.7165390] 2026-07-23 18:42:18.716210-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:21.1101190] 2026-07-23 18:42:21.109817-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:21.1466670] 2026-07-23 18:42:21.146285-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:21.1814430] 2026-07-23 18:42:21.181053-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:51.2342890] 2026-07-23 18:42:51.233644-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:51.2344020] 2026-07-23 18:42:51.234053-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:43:21.2087820] 2026-07-23 18:43:21.208270-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}

⚠️ Failure Details

  • MemoryTests FAILED with fix (should pass)
    • Device tests: 2 of 1454 failed
📁 Fix files reverted (2 files)
  • src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs
  • src/Core/src/Platform/iOS/StepperExtensions.cs

📱 UI Tests — ViewBaseTests

Detected UI test categories: ViewBaseTests

Deep UI tests — 112 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
ViewBaseTests 112/112 ✓
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

📋 Pre-Flight — Context & Validation

Issue: #35985 - [Testing] Re-enable Cells and Handler Memory Leak Device Tests for June 08 Candidate Branch
PR: #36741 - [iOS & Mac] Fix and Re-enable "Handler Does Not Leak" Device Tests for Stepper control
Platforms Affected: iOS, MacCatalyst
Files Changed: 2 implementation, 1 test

Key Findings

  • PR targets Stepper memory leaks on iOS/macOS 26 after device memory leak validation began catching real leaks exposed by #35487.
  • Implementation changes StepperHandler.iOS.cs disconnect cleanup and StepperExtensions.cs iOS 26 flow-direction/subview handling; test change re-enables HandlerDoesNotLeak(typeof(Stepper)).
  • No PR inline review comments were present. PR issue comments only requested CI/test runs and review.
  • Gate result was provided as skipped because no tests were detected by the prior gate phase; this pre-flight did not rerun gate verification.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: low
Errors: 1 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs:81_platformViewDisposed is a sticky handler-level boolean; after a handler reconnects with a fresh UIStepper, a second disconnect returns before _proxy.Disconnect(platformView) and iOS 26 cleanup/disposal.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #36741 Dispose iOS/MacCatalyst 26 UIStepper on disconnect, avoid iOS 26 subview realization unless RTL mirroring is required, and re-enable Stepper handler leak test. ⚠️ Gate skipped src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs, src/Core/src/Platform/iOS/StepperExtensions.cs, src/Controls/tests/DeviceTests/Memory/MemoryTests.cs Original PR; code review found reconnect lifecycle issue.

🔬 Code Review — Deep Analysis

Code Review — PR #36741

Independent Assessment

What this changes: Re-enables Stepper handler leak coverage and changes iOS/MacCatalyst Stepper teardown/flow-direction behavior to avoid iOS 26 Liquid Glass native retains.
Inferred motivation: Fix Stepper memory leaks exposed by device memory tests on iOS/MacCatalyst 26.

Reconciliation with PR Narrative

Author claims: iOS/Mac 26 Stepper has private native retain; dispose on disconnect and avoid unnecessary subview realization.
Agreement/disagreement: Mostly matches, but the reconnect lifecycle path breaks the proposed disposal guard.

Prior Review Reconciliation

No prior ❌ Error findings found.

Blast Radius Assessment

  • Runs for all instances: Yes, all iOS/MacCatalyst Stepper handlers.
  • Startup impact: No; disconnect/flow-direction paths only.
  • Static/shared state: Yes, a ConditionalWeakTable scoped per UIStepper.

CI Status

  • Required-check result: undetermined via gh (gh auth login required); public check-runs show failures.
  • Classification: undetermined / red.
  • Action taken: invoked azdo-build-investigator; confidence capped low.

Findings

❌ Error — Reconnected Stepper handlers skip cleanup after first disconnect

src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs:81

_platformViewDisposed is set after the first iOS/MacCatalyst 26 disconnect and is never reset. The same handler instance can later reconnect with a fresh platform view because ElementHandler.SetVirtualView() creates a new platform element when PlatformView is null. On the next disconnect, this early return skips _proxy.Disconnect(platformView) and the iOS 26 disposal workaround for the new UIStepper, reintroducing the leak and leaving the event hookup uncleared. Reset the flag when a new platform view is created/connected, or key the guard to the actual disposed platform view instance.

Failure-Mode Probing

  • Handler disconnect/reconnect: broken; the second platform view is not disconnected/disposed.
  • Non-iOS 26 platforms: flag is never set; existing behavior is mostly unchanged.
  • LTR-only steppers: subviews are not realized, which supports the leak fix.
  • RTL then LTR: CWT path appears to reset previously mirrored subviews.

Verdict: NEEDS_CHANGES

Confidence: low, capped by unavailable required-check query/red public checks; code finding itself is high-confidence.
Summary: The main leak fix is vulnerable to MAUI’s normal reconnect lifecycle. Please reset or instance-scope the disposal guard before merge.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix-1 Instance-scope the disposed-platform-view guard using a weak reference and ReferenceEquals, instead of a sticky handler-level boolean. ❌ FAILED 1 file Fixed the reconnect guard concern, but Stepper still leaked on iOS 26.5.
2 try-fix-2 Avoid platformView.Subviews during iOS 26 disconnect cleanup to prevent lazy Liquid Glass subview realization. ⚠️ BLOCKED 1 file Local iOS run hit AOT/native aborts and stale failed-test metadata; no reliable fresh xUnit XML.
3 try-fix-3 Use a UIStepper subclass to defer disposal until UIKit detaches the view, avoiding all disconnect-time graph/layer cleanup. ⚠️ BLOCKED 1 file Built after a scoping fix, but test produced no xUnit results and captured a crash report.
4 try-fix-4 Prevention-only flow-direction change: do not set iOS 26 Stepper semantic/transform state for untouched identity/default flow direction. ⚠️ BLOCKED 2 files Built with nullable warning, but test produced no xUnit results and captured a crash report.
PR PR #36741 Dispose iOS/MacCatalyst 26 UIStepper on disconnect and avoid iOS 26 subview realization unless RTL mirroring is required. ⚠️ Gate skipped 3 files Original PR; code review found reconnect lifecycle issue.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 Yes Candidate 1: key the double-disconnect guard to the exact UIStepper instance.
maui-expert-reviewer 2 Yes Candidate 2: remove the disconnect-time subview walk because reading UIStepper.Subviews may be the remaining leak trigger.
maui-expert-reviewer 3 Yes Candidate 3: defer native disposal until UIKit detaches a custom UIStepper subclass.
maui-expert-reviewer 4 Yes Candidate 4: avoid touching SemanticContentAttribute/transform for virgin identity/default iOS 26 steppers.
maui-expert-reviewer 5 No After candidates covered guard scoping, disconnect subview avoidance, deferred disposal, and prevention-only flow-direction mapping, remaining variants were either trivial combinations or riskier UIKit lifecycle hacks without a clear new hypothesis.

Exhausted: Yes
Selected Fix: PR #36741 with required changes — none of the alternatives passed all tests. The strongest actionable improvement is to fix the PR's reconnect lifecycle bug by replacing or resetting the sticky _platformViewDisposed guard, but candidate 1 alone did not fix the iOS 26 Stepper leak.


📝 Recommended PR Title & Description

Assessment: ✏️ Recommend updating — the current title is specific and worth keeping, but the description has a stale test-change claim that is not present in the actual diff.

Recommended title

[iOS & Mac] Stepper: Fix and re-enable Handler Does Not Leak device test

Recommended description

### Issue Details

The device memory leak validation tests began failing in the Candidate branch after the test-related changes introduced in PR https://github.com/dotnet/maui/pull/35487, which exposed actual memory leaks that were previously undetected. The regressions affected the Cells Do Not Leak and Handler Does Not Leak tests on Windows, the Cells Do Not Leak test on iOS, the Handler Does Not Leak test on iOS 26.0, and the Handler Does Not Leak test on macOS 26.0, indicating platform-specific memory leak issues that require fixes.

### Root cause

The Stepper-specific issue occurs because of iOS 26's new "Liquid Glass" rendering, under which the native `UIStepper` can hold an internal, private native retain that is not reachable or releasable through public API. Diagnostics showed the retain count unchanged even after removing the view from its hierarchy and clearing animations.

This was specific to iOS/MacCatalyst 26 only and did not occur on earlier OS versions. A contributing factor was that subview realization and animation were being forced unconditionally, even for the common case where no directional mirroring was needed, adding an extra, avoidable native retain.

### Solution description

The fix explicitly disposes the native `UIStepper` on disconnect, but only for iOS/MacCatalyst 26+ and with a guard against repeating disposal for the same already-disposed native instance. This forces the interop layer to release its native handle immediately instead of waiting on the garbage collector.

It also skips the iOS 26 subview realization and transform/animation work unless directional mirroring is actually required, while still correctly restoring subviews that were previously mirrored if the layout reverts. The Stepper `Handler Does Not Leak` device test is re-enabled by removing the `TESTS_FAILS_ON_IOS && TESTS_FAILS_ON_MACCATALYST` exclusion around `InlineData(typeof(Stepper))`.

### Key files

- `src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs` - disconnect cleanup/disposal for iOS/MacCatalyst 26 `UIStepper`.
- `src/Core/src/Platform/iOS/StepperExtensions.cs` - iOS 26 flow-direction handling that avoids unnecessary `UIStepper.Subviews` realization.
- `src/Controls/tests/DeviceTests/Memory/MemoryTests.cs` - re-enables `HandlerDoesNotLeak(typeof(Stepper))`.

### Platforms validated

- [x] iOS
- [x] Mac
- [ ] Android
- [ ] Windows

### Regression PR

PR https://github.com/dotnet/maui/pull/35487

### Issues Fixed

Fixes https://github.com/dotnet/maui/issues/35985

### Output ScreenShot

|Platform|Before|After|
|--|--|--|
|Mac|<video src="https://github.com/user-attachments/assets/b5c05b33-1d68-4834-8c04-51ddfa7e687f"> |<video src="https://github.com/user-attachments/assets/de4d7267-314c-49a9-b105-7d4929a4e1f9">|
|iOS|<video src="https://github.com/user-attachments/assets/eac0b2d9-b3e4-475c-92cd-48b6833396cc">|<video src="https://github.com/user-attachments/assets/0ee28ad0-2cf3-4759-9506-9a254ad27e0e">|

🏁 Report — Final Recommendation

Comparative report - PR #36741

Candidate ranking

Rank Candidate Result evidence Assessment
1 pr-plus-reviewer Not independently validated in this phase; equivalent lifecycle-guard idea failed the Stepper leak test in try-fix-1 Best code path among available candidates because it keeps the PR's targeted iOS/MacCatalyst 26 fix and applies the expert reviewer's required reconnect/disconnect lifecycle correction. Still needs reliable iOS regression validation.
2 pr Gate skipped; expert review found a major lifecycle issue Keeps the author's targeted fix, but the sticky _platformViewDisposed guard can skip _proxy.Disconnect(platformView) and iOS 26 cleanup/disposal for a fresh platform view after handler reuse.
3 try-fix-2 Blocked/inconclusive: local iOS runs produced AOT/native aborts, stale metadata, and no reliable fresh xUnit XML Plausible technical direction because it combines the instance-scoped guard with removal of the disconnect-time Subviews walk, but it has no trustworthy passing test evidence.
4 try-fix-3 Blocked/invalid: build eventually succeeded, but no xUnit results were produced and a crash report was captured Riskier UIKit lifecycle approach using a custom UIStepper subclass and deferred disposal. The crash/no-results behavior makes it less suitable than the localized PR path.
5 try-fix-4 Blocked/invalid: no xUnit results, crash report captured, and introduced nullable warning CS8602 Distinct prevention-only hypothesis, but it removes the PR's disconnect disposal, lacks validation, and introduces a warning.
6 try-fix-1 Failed iOS 26.5 memory regression test Correctly fixes the reconnect guard issue, but Handler Does Not Leak(type: typeof(Microsoft.Maui.Controls.Stepper)) still failed. Per ranking rule, a candidate with a failed regression test ranks below candidates without passing evidence only when no passing candidate exists; it cannot outrank the PR-based candidate selected for code correctness.

No candidate has a clean, reliable passing iOS regression result. The gate result supplied for this run was skipped because no tests were detected, and the try-fix runs either failed the Stepper leak test or produced invalid/no-result runs.

Why pr-plus-reviewer wins

The raw PR is directionally correct and localized: it avoids unnecessary iOS 26 UIStepper.Subviews realization for the normal LTR flow-direction path, explicitly tears down iOS/MacCatalyst 26 native UIStepper instances, and re-enables the Stepper handler leak test. The expert review found one concrete defect in that implementation: _platformViewDisposed is handler-scoped rather than platform-view-scoped.

pr-plus-reviewer applies the smallest safe correction for that defect by tracking the disposed UIStepper instance instead of using a sticky handler boolean. This preserves the double-disconnect guard without suppressing cleanup for fresh platform views after reconnect.

The strongest competing direction is try-fix-2, which also avoids the disconnect-time Subviews walk. That idea remains plausible because try-fix-1 still failed after only fixing the guard, but try-fix-2 did not produce trustworthy test results. try-fix-3 and try-fix-4 are less attractive because they introduce broader lifecycle behavior or warnings and also lack valid test evidence.

Required follow-up before merge

The winning candidate should be treated as "best available candidate", not as a fully validated fix. Because the only reliable try-fix regression result was a failure for the same guard-only improvement, the PR should rerun the iOS Stepper memory device test after applying the reviewer feedback, and the author should investigate whether the disconnect-time platformView.Subviews cleanup still forces the iOS 26 Liquid Glass retain.


🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@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 — 1 findings

See inline comments for details.

Comment thread src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs
@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 24, 2026
…and disconnect the handler before verifying collection in the memory leak test.
@BagavathiPerumal

Copy link
Copy Markdown
Contributor Author

AI Review Summary

@BagavathiPerumal — new AI review results are available based on this last commit: ad6607a.

Gate Failed Confidence Low Platform iOS

🗂️ Review Sessions — click to expand

🚦 Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: IOS · Base: main · Merge base: ee21c935

🩺 Fix does not pass the tests — every test still fails after applying the fix. The PR's change does not resolve the failure(s).

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 MemoryTests MemoryTests ✅ FAIL — 7516s ❌ FAIL — 3376s
🔴 Without fix — 📱 MemoryTests: FAIL ✅ · 7516s
Error-relevant lines (filtered from the build log):

�[40m�[37mdbug�[39m�[22m�[49m: [17:21:39.9186380] 2026-07-23 17:21:39.882890-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:43.8557450] 2026-07-23 17:21:43.855396-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:48.3217080] 2026-07-23 17:21:48.321394-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:48.3604980] 2026-07-23 17:21:48.360176-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:48.4330710] 2026-07-23 17:21:48.432701-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:22:18.4045050] 2026-07-23 17:22:18.365248-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:22:18.4393710] 2026-07-23 17:22:18.404678-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:22:48.3121690] 2026-07-23 17:22:48.311666-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}

🟢 With fix — 📱 MemoryTests: FAIL ❌ · 3376s
Error-relevant lines (filtered from the build log):

�[40m�[37mdbug�[39m�[22m�[49m: [18:42:18.6797070] 2026-07-23 18:42:18.679361-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:18.7165390] 2026-07-23 18:42:18.716210-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:21.1101190] 2026-07-23 18:42:21.109817-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:21.1466670] 2026-07-23 18:42:21.146285-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:21.1814430] 2026-07-23 18:42:21.181053-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:51.2342890] 2026-07-23 18:42:51.233644-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:51.2344020] 2026-07-23 18:42:51.234053-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:43:21.2087820] 2026-07-23 18:43:21.208270-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}

⚠️ Failure Details

  • MemoryTests FAILED with fix (should pass)

    • Device tests: 2 of 1454 failed

📁 Fix files reverted (2 files)

  • src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs
  • src/Core/src/Platform/iOS/StepperExtensions.cs

📱 UI Tests — ViewBaseTests

Detected UI test categories: ViewBaseTests

Deep UI tests — 112 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
ViewBaseTests 112/112 ✓ —
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)
📋 Pre-Flight — Context & Validation

Issue: #35985 - [Testing] Re-enable Cells and Handler Memory Leak Device Tests for June 08 Candidate Branch PR: #36741 - [iOS & Mac] Fix and Re-enable "Handler Does Not Leak" Device Tests for Stepper control Platforms Affected: iOS, MacCatalyst Files Changed: 2 implementation, 1 test

Key Findings

  • PR targets Stepper memory leaks on iOS/macOS 26 after device memory leak validation began catching real leaks exposed by Fix device test memory #35487.
  • Implementation changes StepperHandler.iOS.cs disconnect cleanup and StepperExtensions.cs iOS 26 flow-direction/subview handling; test change re-enables HandlerDoesNotLeak(typeof(Stepper)).
  • No PR inline review comments were present. PR issue comments only requested CI/test runs and review.
  • Gate result was provided as skipped because no tests were detected by the prior gate phase; this pre-flight did not rerun gate verification.

Code Review Summary

Verdict: NEEDS_CHANGES Confidence: low Errors: 1 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs:81_platformViewDisposed is a sticky handler-level boolean; after a handler reconnects with a fresh UIStepper, a second disconnect returns before _proxy.Disconnect(platformView) and iOS 26 cleanup/disposal.

Fix Candidates

Source Approach Test Result Files Changed Notes

PR PR #36741 Dispose iOS/MacCatalyst 26 UIStepper on disconnect, avoid iOS 26 subview realization unless RTL mirroring is required, and re-enable Stepper handler leak test. ⚠️ Gate skipped src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs, src/Core/src/Platform/iOS/StepperExtensions.cs, src/Controls/tests/DeviceTests/Memory/MemoryTests.cs Original PR; code review found reconnect lifecycle issue.
🔬 Code Review — Deep Analysis

Code Review — PR #36741

Independent Assessment

What this changes: Re-enables Stepper handler leak coverage and changes iOS/MacCatalyst Stepper teardown/flow-direction behavior to avoid iOS 26 Liquid Glass native retains. Inferred motivation: Fix Stepper memory leaks exposed by device memory tests on iOS/MacCatalyst 26.

Reconciliation with PR Narrative

Author claims: iOS/Mac 26 Stepper has private native retain; dispose on disconnect and avoid unnecessary subview realization. Agreement/disagreement: Mostly matches, but the reconnect lifecycle path breaks the proposed disposal guard.

Prior Review Reconciliation

No prior ❌ Error findings found.

Blast Radius Assessment

  • Runs for all instances: Yes, all iOS/MacCatalyst Stepper handlers.
  • Startup impact: No; disconnect/flow-direction paths only.
  • Static/shared state: Yes, a ConditionalWeakTable scoped per UIStepper.

CI Status

  • Required-check result: undetermined via gh (gh auth login required); public check-runs show failures.
  • Classification: undetermined / red.
  • Action taken: invoked azdo-build-investigator; confidence capped low.

Findings

❌ Error — Reconnected Stepper handlers skip cleanup after first disconnect

src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs:81

_platformViewDisposed is set after the first iOS/MacCatalyst 26 disconnect and is never reset. The same handler instance can later reconnect with a fresh platform view because ElementHandler.SetVirtualView() creates a new platform element when PlatformView is null. On the next disconnect, this early return skips _proxy.Disconnect(platformView) and the iOS 26 disposal workaround for the new UIStepper, reintroducing the leak and leaving the event hookup uncleared. Reset the flag when a new platform view is created/connected, or key the guard to the actual disposed platform view instance.

Failure-Mode Probing

  • Handler disconnect/reconnect: broken; the second platform view is not disconnected/disposed.
  • Non-iOS 26 platforms: flag is never set; existing behavior is mostly unchanged.
  • LTR-only steppers: subviews are not realized, which supports the leak fix.
  • RTL then LTR: CWT path appears to reset previously mirrored subviews.

Verdict: NEEDS_CHANGES

Confidence: low, capped by unavailable required-check query/red public checks; code finding itself is high-confidence. Summary: The main leak fix is vulnerable to MAUI’s normal reconnect lifecycle. Please reset or instance-scope the disposal guard before merge.

🛠️ Fix — Analysis & Comparison

Fix Candidates

Source Approach Test Result Files Changed Notes

1 try-fix-1 Instance-scope the disposed-platform-view guard using a weak reference and ReferenceEquals, instead of a sticky handler-level boolean. ❌ FAILED 1 file Fixed the reconnect guard concern, but Stepper still leaked on iOS 26.5.
2 try-fix-2 Avoid platformView.Subviews during iOS 26 disconnect cleanup to prevent lazy Liquid Glass subview realization. ⚠️ BLOCKED 1 file Local iOS run hit AOT/native aborts and stale failed-test metadata; no reliable fresh xUnit XML.
3 try-fix-3 Use a UIStepper subclass to defer disposal until UIKit detaches the view, avoiding all disconnect-time graph/layer cleanup. ⚠️ BLOCKED 1 file Built after a scoping fix, but test produced no xUnit results and captured a crash report.
4 try-fix-4 Prevention-only flow-direction change: do not set iOS 26 Stepper semantic/transform state for untouched identity/default flow direction. ⚠️ BLOCKED 2 files Built with nullable warning, but test produced no xUnit results and captured a crash report.
PR PR #36741 Dispose iOS/MacCatalyst 26 UIStepper on disconnect and avoid iOS 26 subview realization unless RTL mirroring is required. ⚠️ Gate skipped 3 files Original PR; code review found reconnect lifecycle issue.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 Yes Candidate 1: key the double-disconnect guard to the exact UIStepper instance.
maui-expert-reviewer 2 Yes Candidate 2: remove the disconnect-time subview walk because reading UIStepper.Subviews may be the remaining leak trigger.
maui-expert-reviewer 3 Yes Candidate 3: defer native disposal until UIKit detaches a custom UIStepper subclass.
maui-expert-reviewer 4 Yes Candidate 4: avoid touching SemanticContentAttribute/transform for virgin identity/default iOS 26 steppers.
maui-expert-reviewer 5 No After candidates covered guard scoping, disconnect subview avoidance, deferred disposal, and prevention-only flow-direction mapping, remaining variants were either trivial combinations or riskier UIKit lifecycle hacks without a clear new hypothesis.
Exhausted: Yes Selected Fix: PR #36741 with required changes — none of the alternatives passed all tests. The strongest actionable improvement is to fix the PR's reconnect lifecycle bug by replacing or resetting the sticky _platformViewDisposed guard, but candidate 1 alone did not fix the iOS 26 Stepper leak.

📝 Recommended PR Title & Description

Assessment: ✏️ Recommend updating — the current title is specific and worth keeping, but the description has a stale test-change claim that is not present in the actual diff.

Recommended title

[iOS & Mac] Stepper: Fix and re-enable Handler Does Not Leak device test

Recommended description

### Issue Details

The device memory leak validation tests began failing in the Candidate branch after the test-related changes introduced in PR https://github.com/dotnet/maui/pull/35487, which exposed actual memory leaks that were previously undetected. The regressions affected the Cells Do Not Leak and Handler Does Not Leak tests on Windows, the Cells Do Not Leak test on iOS, the Handler Does Not Leak test on iOS 26.0, and the Handler Does Not Leak test on macOS 26.0, indicating platform-specific memory leak issues that require fixes.

### Root cause

The Stepper-specific issue occurs because of iOS 26's new "Liquid Glass" rendering, under which the native `UIStepper` can hold an internal, private native retain that is not reachable or releasable through public API. Diagnostics showed the retain count unchanged even after removing the view from its hierarchy and clearing animations.

This was specific to iOS/MacCatalyst 26 only and did not occur on earlier OS versions. A contributing factor was that subview realization and animation were being forced unconditionally, even for the common case where no directional mirroring was needed, adding an extra, avoidable native retain.

### Solution description

The fix explicitly disposes the native `UIStepper` on disconnect, but only for iOS/MacCatalyst 26+ and with a guard against repeating disposal for the same already-disposed native instance. This forces the interop layer to release its native handle immediately instead of waiting on the garbage collector.

It also skips the iOS 26 subview realization and transform/animation work unless directional mirroring is actually required, while still correctly restoring subviews that were previously mirrored if the layout reverts. The Stepper `Handler Does Not Leak` device test is re-enabled by removing the `TESTS_FAILS_ON_IOS && TESTS_FAILS_ON_MACCATALYST` exclusion around `InlineData(typeof(Stepper))`.

### Key files

- `src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs` - disconnect cleanup/disposal for iOS/MacCatalyst 26 `UIStepper`.
- `src/Core/src/Platform/iOS/StepperExtensions.cs` - iOS 26 flow-direction handling that avoids unnecessary `UIStepper.Subviews` realization.
- `src/Controls/tests/DeviceTests/Memory/MemoryTests.cs` - re-enables `HandlerDoesNotLeak(typeof(Stepper))`.

### Platforms validated

- [x] iOS
- [x] Mac
- [ ] Android
- [ ] Windows

### Regression PR

PR https://github.com/dotnet/maui/pull/35487

### Issues Fixed

Fixes https://github.com/dotnet/maui/issues/35985

### Output ScreenShot

|Platform|Before|After|
|--|--|--|
|Mac|<video src="https://github.com/user-attachments/assets/b5c05b33-1d68-4834-8c04-51ddfa7e687f"> |<video src="https://github.com/user-attachments/assets/de4d7267-314c-49a9-b105-7d4929a4e1f9">|
|iOS|<video src="https://github.com/user-attachments/assets/eac0b2d9-b3e4-475c-92cd-48b6833396cc">|<video src="https://github.com/user-attachments/assets/0ee28ad0-2cf3-4759-9506-9a254ad27e0e">|

🏁 Report — Final Recommendation

Comparative report - PR #36741

Candidate ranking

Rank Candidate Result evidence Assessment
1 pr-plus-reviewer Not independently validated in this phase; equivalent lifecycle-guard idea failed the Stepper leak test in try-fix-1 Best code path among available candidates because it keeps the PR's targeted iOS/MacCatalyst 26 fix and applies the expert reviewer's required reconnect/disconnect lifecycle correction. Still needs reliable iOS regression validation.
2 pr Gate skipped; expert review found a major lifecycle issue Keeps the author's targeted fix, but the sticky _platformViewDisposed guard can skip _proxy.Disconnect(platformView) and iOS 26 cleanup/disposal for a fresh platform view after handler reuse.
3 try-fix-2 Blocked/inconclusive: local iOS runs produced AOT/native aborts, stale metadata, and no reliable fresh xUnit XML Plausible technical direction because it combines the instance-scoped guard with removal of the disconnect-time Subviews walk, but it has no trustworthy passing test evidence.
4 try-fix-3 Blocked/invalid: build eventually succeeded, but no xUnit results were produced and a crash report was captured Riskier UIKit lifecycle approach using a custom UIStepper subclass and deferred disposal. The crash/no-results behavior makes it less suitable than the localized PR path.
5 try-fix-4 Blocked/invalid: no xUnit results, crash report captured, and introduced nullable warning CS8602 Distinct prevention-only hypothesis, but it removes the PR's disconnect disposal, lacks validation, and introduces a warning.
6 try-fix-1 Failed iOS 26.5 memory regression test Correctly fixes the reconnect guard issue, but Handler Does Not Leak(type: typeof(Microsoft.Maui.Controls.Stepper)) still failed. Per ranking rule, a candidate with a failed regression test ranks below candidates without passing evidence only when no passing candidate exists; it cannot outrank the PR-based candidate selected for code correctness.
No candidate has a clean, reliable passing iOS regression result. The gate result supplied for this run was skipped because no tests were detected, and the try-fix runs either failed the Stepper leak test or produced invalid/no-result runs.

Why pr-plus-reviewer wins

The raw PR is directionally correct and localized: it avoids unnecessary iOS 26 UIStepper.Subviews realization for the normal LTR flow-direction path, explicitly tears down iOS/MacCatalyst 26 native UIStepper instances, and re-enables the Stepper handler leak test. The expert review found one concrete defect in that implementation: _platformViewDisposed is handler-scoped rather than platform-view-scoped.

pr-plus-reviewer applies the smallest safe correction for that defect by tracking the disposed UIStepper instance instead of using a sticky handler boolean. This preserves the double-disconnect guard without suppressing cleanup for fresh platform views after reconnect.

The strongest competing direction is try-fix-2, which also avoids the disconnect-time Subviews walk. That idea remains plausible because try-fix-1 still failed after only fixing the guard, but try-fix-2 did not produce trustworthy test results. try-fix-3 and try-fix-4 are less attractive because they introduce broader lifecycle behavior or warnings and also lack valid test evidence.

Required follow-up before merge

The winning candidate should be treated as "best available candidate", not as a fully validated fix. Because the only reliable try-fix regression result was a failure for the same guard-only improvement, the PR should rerun the iOS Stepper memory device test after applying the reviewer feedback, and the author should investigate whether the disconnect-time platformView.Subviews cleanup still forces the iOS 26 Liquid Glass retain.

🧭 Next Steps — review latest findings

I have reset _platformViewDisposed = false in ConnectHandler so the guard scopes to the current UIStepper instead of leaking across reconnects. Updated the field comment accordingly and addressed the failure.

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

Labels

area-controls-stepper Stepper community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/ios platform/macos macOS / Mac Catalyst 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-gate-failed AI could not verify tests catch the bug 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.

[Testing] Re-enable Cells and Handler Memory Leak Device Tests for June 08 Candidate Branch

5 participants