Skip to content

[Windows/Android] FlexLayout: Fix wrap misalignment due to floating-point precision#31341

Open
SuthiYuvaraj wants to merge 13 commits intodotnet:mainfrom
SuthiYuvaraj:fix-30957
Open

[Windows/Android] FlexLayout: Fix wrap misalignment due to floating-point precision#31341
SuthiYuvaraj wants to merge 13 commits intodotnet:mainfrom
SuthiYuvaraj:fix-30957

Conversation

@SuthiYuvaraj
Copy link
Copy Markdown
Contributor

@SuthiYuvaraj SuthiYuvaraj commented Aug 26, 2025

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!

Root Cause

FlexLayout wrap detection uses a floating-point comparison to decide whether a child item fits on the current line: flex_dim < child_size. On Windows (especially at non-integer DPI scaling factors like 1.25x) and on Android (at densities like 2.625x on Pixel 7), the available flex dimension can be computed fractionally smaller than the child's measured size due to rounding during DPI/density conversion. This causes children that should fit on the current line to be incorrectly moved to a new line.

The bug manifests most visibly when buttons dynamically change their FontFamily at runtime, which triggers re-measurement with slightly different sizes.

Description of Change

Introduced a small floating-point tolerance constant (FlexWrapTolerance = 0.1f) in src/Core/src/Layouts/Flex.cs. The wrap line detection check is updated
from:
if (layout.flex_dim < child_size)

to:

float flex_tolerance = layout.flex_dim + FlexWrapTolerance;
if (flex_tolerance < child_size)

This tolerance is applied unconditionally across all platforms. Since the value (0.1f) is well below any intentional layout gap (always ≥ 1 device-independent unit), it has no adverse effect on correct wrapping behavior on any platform.

Key Technical Details

Affected file: src/Core/src/Layouts/Flex.cs — the shared cross-platform flex layout engine
Constant: FlexWrapTolerance = 0.1f — accounts for sub-pixel rounding from DPI/density scaling
Platforms affected: Windows (DPI scaling, e.g., 1.25x) and Android (density, e.g., 2.625 on Pixel 7). iOS/Mac may benefit as a safe cross-platform fix.

Issues Fixed

Fixes #30957

Tested the behaviour in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Output Screenshot

Before Issue Fix After Issue Fix
Before Fix After Fix

@dotnet-policy-service dotnet-policy-service bot added community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration labels Aug 26, 2025
@jsuarezruiz
Copy link
Copy Markdown
Contributor

/azp run MAUI-UITests-public

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@jsuarezruiz jsuarezruiz added the layout-flex FlexLayout issues label Aug 27, 2025
@SuthiYuvaraj SuthiYuvaraj marked this pull request as ready for review August 27, 2025 08:32
Copilot AI review requested due to automatic review settings August 27, 2025 08:32
@SuthiYuvaraj SuthiYuvaraj requested a review from a team as a code owner August 27, 2025 08:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a FlexLayout wrapping issue specific to Windows where buttons with dynamic font changes don't wrap properly due to floating-point precision issues. The fix introduces a small tolerance value (0.1f) to the FlexLayout dimension calculation on Windows to ensure consistent wrapping behavior across platforms.

Key changes:

  • Added platform-specific floating-point tolerance in FlexLayout wrapping logic
  • Created comprehensive UI tests to validate the fix with dynamic button sizing
  • Implemented test page with font family toggling to reproduce the original issue

Reviewed Changes

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

File Description
src/Core/src/Layouts/Flex.cs Adds Windows-specific tolerance to fix FlexLayout wrapping precision issues
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs Implements NUnit UI test to verify FlexLayout wrapping behavior before/after font changes
src/Controls/tests/TestCases.HostApp/Issues/Issue30957.cs Creates test page with FlexLayout and buttons that toggle font families to reproduce the issue

Comment on lines +536 to +539
float flex_tolerance = layout.flex_dim;
#if WINDOWS
// Windows requires tolerance for floating-point precision issues in flex wrapping
flex_tolerance += 0.1f;
Copy link

Copilot AI Aug 27, 2025

Choose a reason for hiding this comment

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

The magic number 0.1f lacks explanation for why this specific value was chosen. Consider defining this as a named constant with documentation explaining how this tolerance value was determined and whether it relates to specific Windows scaling factors or precision limits.

Suggested change
float flex_tolerance = layout.flex_dim;
#if WINDOWS
// Windows requires tolerance for floating-point precision issues in flex wrapping
flex_tolerance += 0.1f;
flex_tolerance += WindowsFlexTolerance;

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +21

App.WaitForElement("Issue30957ToggleButton");

Copy link

Copilot AI Aug 27, 2025

Choose a reason for hiding this comment

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

There's an empty line after the WaitForElement call that serves no purpose and should be removed to improve code readability.

Suggested change
App.WaitForElement("Issue30957ToggleButton");

Copilot uses AI. Check for mistakes.
@DavidIDCI
Copy link
Copy Markdown

#30957 (comment)

I was able to reproduce this issue on Android. This fix only addresses Windows.

public void FlexLayoutWrappingWithToleranceWorksCorrectly()
{

App.WaitForElement("Issue30957ToggleButton");
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.

Seems can be still be reproduced on Android. #31341 (comment)
Could the test verify the rendering with snapshots?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @jsuarezruiz , The issue could not be reproduced on my end. Based on the reported issue on windows, it appears to be related to device density or another rendering factor.
Also, the test case has been updated to capture a screenshot for verification. Please review and confirm if any additional concerns remain.

Copy link
Copy Markdown

@DavidIDCI DavidIDCI Oct 31, 2025

Choose a reason for hiding this comment

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

Yes, it does seem to be related to device density or other device display factor.

I tested with a pixel 5 emulator that has a density of 2.75, and the issue does not reproduce with the provided sample in the linked issue ( #30957 ). I then tested with a pixel 7, density 2.625, and it does reproduce the issue with the provided sample. 2.625 is a pretty common density among Android devices, and all of them with that density reproduce this issue with the sample.

I will note that almost every Android device I have tested will reproduce this issue, but in slightly different cases (different text in the buttons) depending on the device's density. I can confirm that the "Button1/Button2/Button3" text in the buttons used in the UI test doesn't reproduce on any of the Android device's I've tested. I don't know the details of what the UI tests are run on, but if there are details somewhere I can try to find a better repro for the automated test cases. Does the text in the #30957 sample also pass automated tests?

I also will note that because my team was experiencing this issue mostly on Android, we applied the workaround in the code here to our own branch of the FlexLayout and just removed the #if WINDOWS conditionals, and the issue seems to be mitigated on both platforms. I understand you probably can't merge code in without a valid A/B test case, but I do think this fix is best applied to all platforms, not just Windows. Again I am willing to help find better repro steps -- and I recommend testing with the emulator config below (and the text in the original issue) to validate the linked issue repros on Android.

config.ini

disk.dataPartition.size=6442450944
fastboot.forceColdBoot=no
fastboot.forceFastBoot=yes
hw.accelerometer=yes
hw.arc=no
hw.audioInput=yes
hw.battery=yes
hw.camera.back=virtualscene
hw.camera.front=emulated
hw.cpu.ncore=4
hw.dPad=no
hw.gps=yes
hw.gpu.mode=auto
hw.keyboard=yes
hw.lcd.density=420
hw.lcd.height=1200
hw.lcd.width=540
hw.mainKeys=no
hw.ramSize=1536
hw.sdCard=yes
hw.sensors.orientation=yes
hw.sensors.proximity=yes
hw.trackBall=no
sdcard.size=512M
skin.dynamic=yes
skin.name=1080x2400
vm.heapSize=256
hw.device.hash2=MD5:9cfa1e95ac9cd7b57d7aac913d0b34ca17a9d277d6036111b5bd65a8291598a3
hw.device.name=pixel_7
hw.device.manufacturer=Google
showDeviceFrame=no
tag.id=google_apis_playstore
tag.display=Google Play
PlayStore.enabled=true
abi.type=x86_64
hw.cpu.arch=x86_64
hw.gpu.enabled=yes
avd.ini.displayname=Pixel 7 - API 36
image.sysdir.1=system-images\android-36\google_apis_playstore\x86_64\
AvdId=pixel_7_-_api_36

@rmarinho
Copy link
Copy Markdown
Member

rmarinho commented Feb 16, 2026

🤖 AI Summary

📊 Expand Full Review
🔍 Pre-Flight — Context & Validation
📝 Review SessionUpdate Flex.cs · 52ffd1d

Issue: #30957 - FlexLayout Wrap Misalignment with Dynamically-Sized Buttons in .NET MAUI
PR: #31341 by @SuthiYuvaraj
Platforms Affected: Windows (originally reported), Android (confirmed by @DavidIDCI), likely all platforms
Files Changed: 1 fix file, 4 test files

Files Changed

Fix files (1):

  • src/Core/src/Layouts/Flex.cs (+18/-1): Adds FlexWrapTolerance = 0.1f constant, uses it in wrap decision to prevent premature wrapping due to floating-point rounding

Test files (4):

  • src/Controls/tests/TestCases.HostApp/Issues/Issue30957.cs (new, +126): Test page with FlexLayout and font family toggling
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs (new, +28): NUnit UI test with screenshot verification
  • src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlexLayoutWrappingWithToleranceWorksCorrectly.png (new snapshot)
  • src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlexLayoutWrappingWithToleranceWorksCorrectly.png (new snapshot)

Test Type: UI Tests (screenshot-based, VerifyScreenshot)

Key Findings

Bug: FlexLayout Wrap="true" logic in Flex.cs uses layout.flex_dim < child_size to decide if an item wraps. Due to floating-point precision from DPI scaling, flex_dim can be calculated slightly smaller than the actual child size, causing premature wrapping even when sufficient space exists.

PR's Fix: Adds FlexWrapTolerance = 0.1f constant and applies it unconditionally via flex_tolerance = layout.flex_dim + FlexWrapTolerance in the wrap comparison.

Prior Agent Review: PR was previously reviewed — Gate FAILED on Android. Tests used "Button1/Button2/Button3" text that did NOT trigger the precision issue on tested Android devices.

Critical Concern from Prior Review: Reviewer @DavidIDCI confirmed the button text in the test ("Button1/Button2/Button3") does NOT reproduce the precision issue on Android. The issue is density-sensitive — reproduces on Pixel 7 (density 2.625) but not on Pixel 5 (density 2.75). The test essentially passes regardless of whether the fix is present.

Reviewer Comments

File:Line Reviewer Says Author Says Status
Flex.cs:539 Magic number 0.1f lacks explanation Named constant + XML doc added Addressed
Issue30957.cs:20 (jsuarezruiz) Test should verify rendering with snapshots Updated test to use VerifyScreenshot Addressed
Issue30957.cs:20 (DavidIDCI) "Button1/Button2/Button3" text doesn't reproduce issue on any tested Android device; need repro that actually triggers precision issue No response ⚠️ UNRESOLVED - critical gap
Issue30957.cs:21 (Copilot) Empty line after WaitForElement should be removed Not addressed Minor - unresolved
Flex.cs comment "Hence, minimum tolerance for floating-point precision issues in flex wrapping" runs on awkwardly; missing space Not addressed Minor - unresolved

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31341 Add FlexWrapTolerance = 0.1f to flex_dim comparison (unconditional, all platforms) ⏳ PENDING (Gate) Flex.cs (+18/-1) Applied unconditionally; tolerance value 0.1f basis not fully documented

🚦 Gate — Test Verification
📝 Review SessionUpdate Flex.cs · 52ffd1d

Result: ❌ FAILED
Platform: ios
Mode: Full Verification

  • Tests WITHOUT fix: ✅ PASS (unexpected — should FAIL to catch the bug)
  • Tests WITH fix: ✅ PASS (expected)

Verification Output

╔═══════════════════════════════════════════════════════════╗
║              VERIFICATION FAILED ❌                       ║
╠═══════════════════════════════════════════════════════════╣
║  Tests PASSED without fix (should fail)                   ║
║  - Tests don't actually detect the bug                    ║
╚═══════════════════════════════════════════════════════════╝

Root Cause of Gate Failure

The test in Issue30957.cs uses buttons with text "Button1", "Button2", "Button3". This specific button text does NOT trigger the floating-point precision issue on iOS (or Android, as confirmed by reviewer @DavidIDCI). The precision issue is sensitive to button text length, font metrics, and device DPI density.

The test verifies that three buttons exist in a FlexLayout, toggles their font family, and takes a screenshot — but because the button widths with this text don't land near the FlexLayout boundary where floating-point errors manifest, the test always passes regardless of whether the tolerance fix is present.

Fix File Identified

  • src/Core/src/Layouts/Flex.cs (the FlexWrapTolerance = 0.1f fix)

🔧 Fix — Analysis & Comparison
📝 Review SessionUpdate Flex.cs · 52ffd1d

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31341 Add FlexWrapTolerance = 0.1f to flex_dim comparison ✅ PASS with fix (test not reliable) Flex.cs (+18/-1) Gate FAILED - test passes without fix too

Exhausted: N/A (Gate failed — Fix phase skipped per workflow)
Selected Fix: PR's fix — Gate failed so no try-fix was run; the PR fix code change is technically sound but tests don't validate it

Gate FAILED reason: Tests pass both with and without the fix because the "Button1/Button2/Button3" button text does not trigger the precision issue on tested devices.


📋 Report — Final Recommendation
📝 Review SessionUpdate Flex.cs · 52ffd1d

⚠️ Final Recommendation: REQUEST CHANGES

Summary

PR #31341 addresses a genuine bug: floating-point precision errors in FlexLayout's wrap algorithm cause items to wrap prematurely when DPI scaling causes flex_dim to be calculated slightly smaller than the actual child size. The fix — adding FlexWrapTolerance = 0.1f to the comparison — is technically sound and applied unconditionally across all platforms.

However, Gate FAILED on both iOS (this review) and Android (prior review): the UI test does not actually catch the bug. The test uses buttons with short text ("Button1", "Button2", "Button3"), which does not trigger the floating-point precision issue on the test devices. The test passes both with and without the fix, making it unable to serve as a regression guard.

Root Cause

Flex.cs wrap decision logic in layout_item():

// Before (buggy):
if (layout.flex_dim < child_size)  // flex_dim can be slightly smaller due to DPI scaling
// After (fixed):
float flex_tolerance = layout.flex_dim + FlexWrapTolerance;
if (flex_tolerance < child_size)

DPI scaling causes sub-pixel rounding differences between flex_dim and child_size. When flex_dim is calculated slightly smaller than the actual child size, items are incorrectly wrapped to a new line despite sufficient space. The 0.1f tolerance absorbs this rounding error.

Gate Status: ❌ FAILED

Verified on iOS (this review) and previously on Android (prior review):

  • Tests PASS without fix (unexpected — tests don't catch the bug)
  • Tests PASS with fix (expected)

The test button text "Button1/Button2/Button3" does not reproduce the floating-point precision issue on tested iOS/Android devices. Reviewer @DavidIDCI confirmed this on Android: the issue reproduces on Pixel 7 (density 2.625) but not with this specific button text on tested devices.

Issues Found

🔴 Critical: Test does not catch the bug (Gate FAILED)

  • Files: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs, src/Controls/tests/TestCases.HostApp/Issues/Issue30957.cs
  • Problem: Button text "Button1/Button2/Button3" does not trigger the sub-pixel rounding discrepancy on tested devices. Confirmed FAILED on both Android (prior agent review) and iOS (this review).
  • Fix needed: The test must use button text or layout dimensions that actually land near the FlexLayout boundary where floating-point errors manifest. Options:
    1. Use longer button text that makes buttons wider, closer to the wrapping boundary
    2. Set the FlexLayout to a fixed narrow width that creates a boundary condition for the precision issue
    3. Add a unit test in Controls.Core.UnitTests that directly tests Flex.cs layout logic with crafted float values exposing the rounding error (more reliable, not device-density-dependent)
    4. Find the exact text from the original issue sample that @DavidIDCI confirmed reproduces on Android density 2.625 devices

🟡 Minor: Malformed inline comment in Flex.cs

  • File: src/Core/src/Layouts/Flex.cs (line ~550)
  • Problem: Comment reads: "...behavior.Hence, minimum tolerance for..." — missing space before "Hence" and sentence structure is broken
  • Fix: "...behavior. Hence, a minimum tolerance is applied for floating-point precision issues in flex wrapping."

🟡 Minor: Empty line in test method

  • File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs:19
  • Problem: Blank line after opening brace of test method before WaitForElement call
  • Fix: Remove the blank line

🟡 Minor: No newline at end of file

  • Files: Issue30957.cs (both HostApp and Shared.Tests)
  • Problem: Files end without trailing newline
  • Fix: Add trailing newline

Fix Quality Assessment (Code Itself)

The fix itself is well-structured:

  • FlexWrapTolerance = 0.1f named constant (not a raw magic number)
  • ✅ Well-documented with XML doc comments
  • ✅ Applied unconditionally across all platforms (correct, since Android is also affected)
  • ⚠️ The value 0.1f lacks a documented basis for why this specific value was chosen over alternatives like 0.01f or 0.5f. The comment should mention that 0.1f represents approximately the maximum sub-pixel rounding error expected from common DPI scaling factors.

Title Assessment

Current: [Windows/Android] FlexLayout: Fix wrap misalignment due to floating-point precision
Assessment: The title says [Windows/Android] but the fix is applied unconditionally to all platforms. Consider removing the platform prefix or changing it to reflect all platforms.
Recommended: FlexLayout: Fix wrap misalignment due to floating-point precision (all platforms)

Description Assessment

The PR description is adequate — it has the NOTE block, root cause, description of change, and issue links. The description of "scaling" correctly identifies the root cause.


📋 Expand PR Finalization Review
Title: ⚠️ Needs Update

Current: [Windows/Android] FlexLayout: Fix wrap misalignment due to floating-point precision

Recommended: FlexLayout: Fix wrap misalignment due to floating-point precision (Windows/Android primary)

Description: ⚠️ Needs Update

Description needs updates. See details below.

✨ Suggested PR Description

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

Root Cause

FlexLayout wrap detection uses a floating-point comparison to decide whether a child item fits on the current line: flex_dim < child_size. On Windows (especially at non-integer DPI scaling factors like 1.25x) and on Android (at densities like 2.625x on Pixel 7), the available flex dimension can be computed fractionally smaller than the child's measured size due to rounding during DPI/density conversion. This causes children that should fit on the current line to be incorrectly moved to a new line.

The bug manifests most visibly when buttons dynamically change their FontFamily at runtime, which triggers re-measurement with slightly different sizes.

Description of Change

Introduced a small floating-point tolerance constant (FlexWrapTolerance = 0.1f) in src/Core/src/Layouts/Flex.cs. The wrap line detection check is updated from:

if (layout.flex_dim < child_size)

to:

float flex_tolerance = layout.flex_dim + FlexWrapTolerance;
if (flex_tolerance < child_size)

This tolerance is applied unconditionally across all platforms. Since the value (0.1f) is well below any intentional layout gap (always ≥ 1 device-independent unit), it has no adverse effect on correct wrapping behavior on any platform.

Key Technical Details

  • Affected file: src/Core/src/Layouts/Flex.cs — the shared cross-platform flex layout engine
  • Constant: FlexWrapTolerance = 0.1f — accounts for sub-pixel rounding from DPI/density scaling
  • Platforms affected: Windows (DPI scaling, e.g., 1.25x) and Android (density, e.g., 2.625 on Pixel 7). iOS/Mac may benefit as a safe cross-platform fix.

Issues Fixed

Fixes #30957

Platforms Tested

  • Android
  • Windows
  • iOS
  • Mac
Code Review: ✅ Passed

Code Review Findings — PR #31341

🟡 Suggestions

1. Tolerance Value Choice Is Not Documented

File: src/Core/src/Layouts/Flex.cs
Location: private const float FlexWrapTolerance = 0.1f;

Problem: The constant 0.1f is arbitrary and its origin is unexplained. The XML doc comment says "A small tolerance value" but doesn't justify why 0.1f specifically (vs 0.01f, 0.5f, 1.0f). The original Copilot reviewer flagged this as well. The constant name FlexWrapTolerance is good, but the value selection needs rationale.

Recommendation: Add a comment near the constant explaining the rationale — e.g., "0.1f represents sub-pixel precision threshold; typical DPI scaling factors on Windows (e.g., 1.25x, 1.5x) can introduce rounding errors up to ~0.1 device-independent units."

// 0.1f accounts for sub-pixel rounding errors introduced by DPI scaling factors
// (e.g., 1.25x on Windows, 2.625x density on Android). Values below 1.0f have
// no meaningful effect on intentional layout gaps which are always >= 1dp.
private const float FlexWrapTolerance = 0.1f;

2. Malformed Code Comment in layout_item

File: src/Core/src/Layouts/Flex.cs
Location: Lines 546–551 (the multi-line comment before flex_tolerance)

Problem: The comment ends with a sentence fragment: "Hence, minimum tolerance for floating-point precision issues in flex wrapping". This is grammatically incomplete and was apparently appended accidentally.

Current:

// The issue was originally reported on Windows and related to device
// density or scaling factor. In a few scenarios, the same issue has also
// been reproduced on Android due to device density variations. The tolerance value
// is applied unconditionally across all platforms as a safer fix, since it has no
// adverse effects on layout behavior.Hence, minimum tolerance for floating-point precision issues in flex wrapping
float flex_tolerance = layout.flex_dim + FlexWrapTolerance;

Recommended:

// The issue was originally reported on Windows, related to device density or
// scaling factor. It has also been reproduced on Android due to density variations.
// The tolerance is applied unconditionally across all platforms as a conservative fix;
// it has no adverse effects on layout behavior.
float flex_tolerance = layout.flex_dim + FlexWrapTolerance;

3. Unnecessary Empty Line in UI Test

File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs
Location: After App.WaitForElement("Issue30957ToggleButton");

Problem: There is a blank line between WaitForElement and Tap that serves no purpose (also flagged by Copilot reviewer).

Current:

App.WaitForElement("Issue30957ToggleButton");
App.Tap("Issue30957ToggleButton");

(with blank line between them in the actual code)

Recommended: Remove the blank line.


4. UI Test Does Not Verify Wrap Behavior

File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs

Problem: The test taps the toggle button, waits for the 3 buttons to exist, and takes a screenshot. However, it does not verify that the buttons are actually on one line (not wrapped). WaitForElement just checks existence, not position. The screenshot test relies entirely on visual comparison — if no baseline exists for the Android emulator used in CI, the test may be silently passing without actually checking the layout.

Additionally: The reviewer comment from DavidIDCI (October 2025) indicates the specific text "Button1/Button2/Button3" does NOT reproduce the issue on many Android devices — the test may be testing a scenario that doesn't actually trigger the bug.

Recommendation: Consider adding element position/rect assertions to verify buttons are on a single row:

var rect1 = App.WaitForElement("Issue30957Button1").GetRect();
var rect2 = App.WaitForElement("Issue30957Button2").GetRect();
// Buttons should be on same row (same Y coordinate within tolerance)
Assert.That(Math.Abs(rect1.Y - rect2.Y), Is.LessThan(5), "Buttons should be on the same line");

5. Missing Newline at End of Files

Files:

  • src/Controls/tests/TestCases.HostApp/Issues/Issue30957.cs
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs

Both files are missing a newline at end of file (indicated by \ No newline at end of file in the diff). This is a minor formatting issue but inconsistent with repository conventions.


✅ Looks Good

  • Constant is properly named (FlexWrapTolerance) — descriptive and searchable
  • XML doc comment on the constant is helpful for future readers
  • Fix logic is correct — comparing layout.flex_dim + FlexWrapTolerance < child_size is the right predicate
  • Cross-platform application of the fix is the right call (the fix is in shared Flex.cs, no #if guards needed)
  • UI test structure follows repository conventions (_IssuesUITest, [Category(UITestCategories.Layout)], AutomationIds on elements)
  • HostApp page follows [Issue] attribute conventions and uses C# (not XAML), per guidelines
  • Snapshot images added for Android and iOS

@rmarinho rmarinho added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Feb 16, 2026
@rmarinho rmarinho added s/agent-gate-failed AI could not verify tests catch the bug and removed s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Feb 16, 2026
@kubaflo kubaflo added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-lose Author adopted the agent's fix and it turned out to be bad labels Feb 20, 2026
@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels Mar 19, 2026
@dotnet dotnet deleted a comment from MauiBot Mar 19, 2026
@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels Mar 20, 2026
@dotnet dotnet deleted a comment from MauiBot Mar 20, 2026
@dotnet dotnet deleted a comment from MauiBot Mar 20, 2026
@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels Mar 20, 2026
@dotnet dotnet deleted a comment from MauiBot Mar 21, 2026
@dotnet dotnet deleted a comment from MauiBot Mar 21, 2026
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 22, 2026

🤖 AI Summary

📊 Expand Full Review243f142 · commit for review
🔍 Pre-Flight — Context & Validation
📝 Review SessionUpdate Flex.cs · 52ffd1d

Issue: #30957 - FlexLayout Wrap Misalignment with Dynamically-Sized Buttons in .NET MAUI
PR: #31341 by @SuthiYuvaraj
Platforms Affected: Windows (originally reported), Android (confirmed by @DavidIDCI), likely all platforms
Files Changed: 1 fix file, 4 test files

Files Changed

Fix files (1):

  • src/Core/src/Layouts/Flex.cs (+18/-1): Adds FlexWrapTolerance = 0.1f constant, uses it in wrap decision to prevent premature wrapping due to floating-point rounding

Test files (4):

  • src/Controls/tests/TestCases.HostApp/Issues/Issue30957.cs (new, +126): Test page with FlexLayout and font family toggling
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs (new, +28): NUnit UI test with screenshot verification
  • src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlexLayoutWrappingWithToleranceWorksCorrectly.png (new snapshot)
  • src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlexLayoutWrappingWithToleranceWorksCorrectly.png (new snapshot)

Test Type: UI Tests (screenshot-based, VerifyScreenshot)

Key Findings

Bug: FlexLayout Wrap="true" logic in Flex.cs uses layout.flex_dim < child_size to decide if an item wraps. Due to floating-point precision from DPI scaling, flex_dim can be calculated slightly smaller than the actual child size, causing premature wrapping even when sufficient space exists.

PR's Fix: Adds FlexWrapTolerance = 0.1f constant and applies it unconditionally via flex_tolerance = layout.flex_dim + FlexWrapTolerance in the wrap comparison.

Prior Agent Review: PR was previously reviewed — Gate FAILED on Android. Tests used "Button1/Button2/Button3" text that did NOT trigger the precision issue on tested Android devices.

Critical Concern from Prior Review: Reviewer @DavidIDCI confirmed the button text in the test ("Button1/Button2/Button3") does NOT reproduce the precision issue on Android. The issue is density-sensitive — reproduces on Pixel 7 (density 2.625) but not on Pixel 5 (density 2.75). The test essentially passes regardless of whether the fix is present.

Reviewer Comments

File:Line Reviewer Says Author Says Status
Flex.cs:539 Magic number 0.1f lacks explanation Named constant + XML doc added Addressed
Issue30957.cs:20 (jsuarezruiz) Test should verify rendering with snapshots Updated test to use VerifyScreenshot Addressed
Issue30957.cs:20 (DavidIDCI) "Button1/Button2/Button3" text doesn't reproduce issue on any tested Android device; need repro that actually triggers precision issue No response ⚠️ UNRESOLVED - critical gap
Issue30957.cs:21 (Copilot) Empty line after WaitForElement should be removed Not addressed Minor - unresolved
Flex.cs comment "Hence, minimum tolerance for floating-point precision issues in flex wrapping" runs on awkwardly; missing space Not addressed Minor - unresolved

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31341 Add FlexWrapTolerance = 0.1f to flex_dim comparison (unconditional, all platforms) ⏳ PENDING (Gate) Flex.cs (+18/-1) Applied unconditionally; tolerance value 0.1f basis not fully documented

Issue: #30957 - FlexLayout Wrap Misalignment with Dynamically-Sized Buttons in .NET MAUI
PR: #31341 - [Windows/Android] FlexLayout: Fix wrap misalignment due to floating-point precision
Platforms Affected: Windows per issue body; Android additionally reported in issue and PR discussion
Files Changed: 1 implementation, 2 test

Key Findings

  • The linked issue describes a FlexLayout wrapping bug triggered by runtime font-family changes that slightly alter button size, causing an item to wrap unexpectedly despite available horizontal space.
  • The PR changes src/Core/src/Layouts/Flex.cs to compare child size against layout.flex_dim + 0.1f on ANDROID || WINDOWS, broadening the original Windows-focused fix to Android as well.
  • Test coverage consists of one HostApp repro page and one shared UI test (Issue30957) asserting that three buttons remain on the same row after toggling font family.
  • Review discussion raises a key concern for Android: multiple commenters reported the issue still reproduces on Android depending on device density, and one review thread explicitly asks whether the test really validates rendering on Android.
  • I did not find a prior agent review comment body to import, but the PR already has s/agent-reviewed, s/agent-changes-requested, s/agent-gate-failed, and s/agent-fix-win labels, indicating a previous automated run reached a negative recommendation.

Edge Cases From Discussion

  • Android reproduction appears density-sensitive; a Pixel 7 configuration with density 2.625 was reported to reproduce while a Pixel 5 at 2.75 did not.
  • The exact button texts used in the PR's UI test were reported not to reproduce on several Android devices even when the original sample does.
  • A commenter reported success by applying the tolerance on Android too, but also noted the current automated repro may not be representative enough to validate that change.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31341 Add a small 0.1f wrap tolerance to layout.flex_dim for Android and Windows before deciding whether an item must wrap to the next line ⏳ PENDING (Gate) src/Core/src/Layouts/Flex.cs, src/Controls/tests/TestCases.HostApp/Issues/Issue30957.cs, src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs Original PR uses a constant tolerance plus a UI test that checks row alignment after a font toggle

🚦 Gate — Test Verification
📝 Review SessionUpdate Flex.cs · 52ffd1d

Result: ❌ FAILED
Platform: ios
Mode: Full Verification

  • Tests WITHOUT fix: ✅ PASS (unexpected — should FAIL to catch the bug)
  • Tests WITH fix: ✅ PASS (expected)

Verification Output

╔═══════════════════════════════════════════════════════════╗
║              VERIFICATION FAILED ❌                       ║
╠═══════════════════════════════════════════════════════════╣
║  Tests PASSED without fix (should fail)                   ║
║  - Tests don't actually detect the bug                    ║
╚═══════════════════════════════════════════════════════════╝

Root Cause of Gate Failure

The test in Issue30957.cs uses buttons with text "Button1", "Button2", "Button3". This specific button text does NOT trigger the floating-point precision issue on iOS (or Android, as confirmed by reviewer @DavidIDCI). The precision issue is sensitive to button text length, font metrics, and device DPI density.

The test verifies that three buttons exist in a FlexLayout, toggles their font family, and takes a screenshot — but because the button widths with this text don't land near the FlexLayout boundary where floating-point errors manifest, the test always passes regardless of whether the tolerance fix is present.

Fix File Identified

  • src/Core/src/Layouts/Flex.cs (the FlexWrapTolerance = 0.1f fix)

Gate Result: ✅ PASSED

Platform: android
Mode: Full Verification

  • Tests FAIL without fix: ✅
  • Tests PASS with fix: ✅

Notes


🔧 Fix — Analysis & Comparison
📝 Review SessionUpdate Flex.cs · 52ffd1d

Result: ❌ FAILED
Platform: ios
Mode: Full Verification

  • Tests WITHOUT fix: ✅ PASS (unexpected — should FAIL to catch the bug)
  • Tests WITH fix: ✅ PASS (expected)

Verification Output

╔═══════════════════════════════════════════════════════════╗
║              VERIFICATION FAILED ❌                       ║
╠═══════════════════════════════════════════════════════════╣
║  Tests PASSED without fix (should fail)                   ║
║  - Tests don't actually detect the bug                    ║
╚═══════════════════════════════════════════════════════════╝

Root Cause of Gate Failure

The test in Issue30957.cs uses buttons with text "Button1", "Button2", "Button3". This specific button text does NOT trigger the floating-point precision issue on iOS (or Android, as confirmed by reviewer @DavidIDCI). The precision issue is sensitive to button text length, font metrics, and device DPI density.

The test verifies that three buttons exist in a FlexLayout, toggles their font family, and takes a screenshot — but because the button widths with this text don't land near the FlexLayout boundary where floating-point errors manifest, the test always passes regardless of whether the tolerance fix is present.

Fix File Identified

  • src/Core/src/Layouts/Flex.cs (the FlexWrapTolerance = 0.1f fix)

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) Relative epsilon based on layout.flex_dim/child_size magnitude ✅ PASS src/Core/src/Layouts/Flex.cs Adaptive, cross-platform tolerance
2 try-fix (claude-sonnet-4.6) 1/8-unit fixed-point quantization before wrap comparison ✅ PASS src/Core/src/Layouts/Flex.cs Discrete bucket comparison
3 try-fix (gpt-5.3-codex) Android-only one-ULP boundary with MathF.BitIncrement(layout.flex_dim) ✅ PASS src/Core/src/Layouts/Flex.cs Minimal representable-float slack
4 try-fix (gemini-3-pro-preview) Round both operands to fixed decimal precision before comparison ✅ PASS src/Core/src/Layouts/Flex.cs Simple normalization
5 try-fix (claude-opus-4.6, round 2) Snap comparison to physical pixels using display density threaded from FlexLayout into the Flex engine ✅ PASS src/Core/src/Layouts/Flex.cs, src/Controls/src/Core/Layout/FlexLayout.cs Principled, density-aware, but invasive
6 try-fix (gemini-3-pro-preview, round 2) Fix the accumulation site by tracking used space against the original container size ✅ PASS src/Core/src/Layouts/Flex.cs Root-cause-oriented, but broader behavioral change
7 try-fix (claude-sonnet-4.6, round 3) Use a double-precision running accumulator for wrap-boundary arithmetic while keeping final frames as float ✅ PASS src/Core/src/Layouts/Flex.cs Eliminates source precision loss with a surgical arithmetic-only change
PR PR #31341 Add a fixed 0.1f tolerance to layout.flex_dim under `#if ANDROID WINDOWS` before wrap comparison ✅ PASSED (Gate)

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 1 Yes Device-pixel snapping using display density
claude-sonnet-4.6 1 Yes Physical-pixel quantization using density
gpt-5.3-codex 1 Yes Eliminate repeated subtraction by computing against original size
gemini-3-pro-preview 1 Yes Track running sum rather than mutated remaining-space float
claude-opus-4.6 2 Yes Compensated or double-precision accumulation
claude-sonnet-4.6 2 Yes Double-precision running sum only
gpt-5.3-codex 2 Yes Promote intermediate layout accumulation to double
gemini-3-pro-preview 2 Yes Widen accumulator fields to double
claude-opus-4.6 3 Yes Integer pixel-domain accumulation end-to-end
claude-sonnet-4.6 3 Yes Kahan compensated summation in float
gpt-5.3-codex 3 Yes Kahan compensated subtraction for flex_dim
gemini-3-pro-preview 3 Yes Proportional error distribution/compression

Exhausted: Yes — reached the skill's maximum 3 cross-pollination rounds.
Selected Fix: Candidate #7 — It passed the Android regression, stays within a single core file, removes magic tolerances and platform guards, and addresses the precision problem at the arithmetic source more directly than the PR's fixed 0.1f threshold.


📋 Report — Final Recommendation
📝 Review SessionUpdate Flex.cs · 52ffd1d

Result: ❌ FAILED
Platform: ios
Mode: Full Verification

  • Tests WITHOUT fix: ✅ PASS (unexpected — should FAIL to catch the bug)
  • Tests WITH fix: ✅ PASS (expected)

Verification Output

╔═══════════════════════════════════════════════════════════╗
║              VERIFICATION FAILED ❌                       ║
╠═══════════════════════════════════════════════════════════╣
║  Tests PASSED without fix (should fail)                   ║
║  - Tests don't actually detect the bug                    ║
╚═══════════════════════════════════════════════════════════╝

Root Cause of Gate Failure

The test in Issue30957.cs uses buttons with text "Button1", "Button2", "Button3". This specific button text does NOT trigger the floating-point precision issue on iOS (or Android, as confirmed by reviewer @DavidIDCI). The precision issue is sensitive to button text length, font metrics, and device DPI density.

The test verifies that three buttons exist in a FlexLayout, toggles their font family, and takes a screenshot — but because the button widths with this text don't land near the FlexLayout boundary where floating-point errors manifest, the test always passes regardless of whether the tolerance fix is present.

Fix File Identified

  • src/Core/src/Layouts/Flex.cs (the FlexWrapTolerance = 0.1f fix)

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue/PR context gathered; Android density-sensitive repro concerns captured from discussion
Gate ✅ PASSED Android; expected FAIL without fix / PASS with fix outcome reported
Try-Fix ✅ COMPLETE 7 passing alternatives explored across 3 cross-pollination rounds; best candidate selected
Report ✅ COMPLETE

Summary

The PR's current fix works against the added Android regression test, but Phase 3 found a stronger alternative. Multiple independent approaches passed, and the best one was a single-file change that fixes the precision loss at its source by using a double-precision running accumulator for the wrap-boundary arithmetic. Compared with the PR's hardcoded 0.1f tolerance behind #if ANDROID || WINDOWS, that alternative is more principled, removes magic thresholds and platform guards, and still passes the same regression.

Root Cause

FlexLayout's wrap decision relies on a float remaining-space value (layout.flex_dim) that is updated through repeated subtraction. Small measurement changes from font-family toggles can accumulate float drift until layout.flex_dim < child_size becomes true even though the child should still fit on the current line.

Fix Quality

The PR fix is simple and validated by Gate, but it compensates for the symptom using a platform-gated absolute tolerance (0.1f). Candidate #7 instead keeps the existing logic shape while improving the arithmetic precision used for the wrap boundary, which is a better long-term fit for a floating-point accumulation bug. I would request changes and prefer that direction before merge.


📋 Expand PR Finalization Review
Title: ⚠️ Needs Update

Current: [Windows/Android] FlexLayout: Fix wrap misalignment due to floating-point precision

Recommended: FlexLayout: Fix wrap misalignment due to floating-point precision (Windows/Android primary)

Description: ⚠️ Needs Update

Description needs updates. See details below.

✨ Suggested PR Description

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

Root Cause

FlexLayout wrap detection uses a floating-point comparison to decide whether a child item fits on the current line: flex_dim < child_size. On Windows (especially at non-integer DPI scaling factors like 1.25x) and on Android (at densities like 2.625x on Pixel 7), the available flex dimension can be computed fractionally smaller than the child's measured size due to rounding during DPI/density conversion. This causes children that should fit on the current line to be incorrectly moved to a new line.

The bug manifests most visibly when buttons dynamically change their FontFamily at runtime, which triggers re-measurement with slightly different sizes.

Description of Change

Introduced a small floating-point tolerance constant (FlexWrapTolerance = 0.1f) in src/Core/src/Layouts/Flex.cs. The wrap line detection check is updated from:

if (layout.flex_dim < child_size)

to:

float flex_tolerance = layout.flex_dim + FlexWrapTolerance;
if (flex_tolerance < child_size)

This tolerance is applied unconditionally across all platforms. Since the value (0.1f) is well below any intentional layout gap (always ≥ 1 device-independent unit), it has no adverse effect on correct wrapping behavior on any platform.

Key Technical Details

  • Affected file: src/Core/src/Layouts/Flex.cs — the shared cross-platform flex layout engine
  • Constant: FlexWrapTolerance = 0.1f — accounts for sub-pixel rounding from DPI/density scaling
  • Platforms affected: Windows (DPI scaling, e.g., 1.25x) and Android (density, e.g., 2.625 on Pixel 7). iOS/Mac may benefit as a safe cross-platform fix.

Issues Fixed

Fixes #30957

Platforms Tested

  • Android
  • Windows
  • iOS
  • Mac
Code Review: ✅ Passed

Code Review Findings — PR #31341

🟡 Suggestions

1. Tolerance Value Choice Is Not Documented

File: src/Core/src/Layouts/Flex.cs
Location: private const float FlexWrapTolerance = 0.1f;

Problem: The constant 0.1f is arbitrary and its origin is unexplained. The XML doc comment says "A small tolerance value" but doesn't justify why 0.1f specifically (vs 0.01f, 0.5f, 1.0f). The original Copilot reviewer flagged this as well. The constant name FlexWrapTolerance is good, but the value selection needs rationale.

Recommendation: Add a comment near the constant explaining the rationale — e.g., "0.1f represents sub-pixel precision threshold; typical DPI scaling factors on Windows (e.g., 1.25x, 1.5x) can introduce rounding errors up to ~0.1 device-independent units."

// 0.1f accounts for sub-pixel rounding errors introduced by DPI scaling factors
// (e.g., 1.25x on Windows, 2.625x density on Android). Values below 1.0f have
// no meaningful effect on intentional layout gaps which are always >= 1dp.
private const float FlexWrapTolerance = 0.1f;

2. Malformed Code Comment in layout_item

File: src/Core/src/Layouts/Flex.cs
Location: Lines 546–551 (the multi-line comment before flex_tolerance)

Problem: The comment ends with a sentence fragment: "Hence, minimum tolerance for floating-point precision issues in flex wrapping". This is grammatically incomplete and was apparently appended accidentally.

Current:

// The issue was originally reported on Windows and related to device
// density or scaling factor. In a few scenarios, the same issue has also
// been reproduced on Android due to device density variations. The tolerance value
// is applied unconditionally across all platforms as a safer fix, since it has no
// adverse effects on layout behavior.Hence, minimum tolerance for floating-point precision issues in flex wrapping
float flex_tolerance = layout.flex_dim + FlexWrapTolerance;

Recommended:

// The issue was originally reported on Windows, related to device density or
// scaling factor. It has also been reproduced on Android due to density variations.
// The tolerance is applied unconditionally across all platforms as a conservative fix;
// it has no adverse effects on layout behavior.
float flex_tolerance = layout.flex_dim + FlexWrapTolerance;

3. Unnecessary Empty Line in UI Test

File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs
Location: After App.WaitForElement("Issue30957ToggleButton");

Problem: There is a blank line between WaitForElement and Tap that serves no purpose (also flagged by Copilot reviewer).

Current:

App.WaitForElement("Issue30957ToggleButton");
App.Tap("Issue30957ToggleButton");

(with blank line between them in the actual code)

Recommended: Remove the blank line.


4. UI Test Does Not Verify Wrap Behavior

File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs

Problem: The test taps the toggle button, waits for the 3 buttons to exist, and takes a screenshot. However, it does not verify that the buttons are actually on one line (not wrapped). WaitForElement just checks existence, not position. The screenshot test relies entirely on visual comparison — if no baseline exists for the Android emulator used in CI, the test may be silently passing without actually checking the layout.

Additionally: The reviewer comment from DavidIDCI (October 2025) indicates the specific text "Button1/Button2/Button3" does NOT reproduce the issue on many Android devices — the test may be testing a scenario that doesn't actually trigger the bug.

Recommendation: Consider adding element position/rect assertions to verify buttons are on a single row:

var rect1 = App.WaitForElement("Issue30957Button1").GetRect();
var rect2 = App.WaitForElement("Issue30957Button2").GetRect();
// Buttons should be on same row (same Y coordinate within tolerance)
Assert.That(Math.Abs(rect1.Y - rect2.Y), Is.LessThan(5), "Buttons should be on the same line");

5. Missing Newline at End of Files

Files:

  • src/Controls/tests/TestCases.HostApp/Issues/Issue30957.cs
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs

Both files are missing a newline at end of file (indicated by \ No newline at end of file in the diff). This is a minor formatting issue but inconsistent with repository conventions.


✅ Looks Good

  • Constant is properly named (FlexWrapTolerance) — descriptive and searchable
  • XML doc comment on the constant is helpful for future readers
  • Fix logic is correct — comparing layout.flex_dim + FlexWrapTolerance < child_size is the right predicate
  • Cross-platform application of the fix is the right call (the fix is in shared Flex.cs, no #if guards needed)
  • UI test structure follows repository conventions (_IssuesUITest, [Category(UITestCategories.Layout)], AutomationIds on elements)
  • HostApp page follows [Issue] attribute conventions and uses C# (not XAML), per guidelines
  • Snapshot images added for Android and iOS

Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

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

Could you please review the AI's summary?

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

Or

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

@MauiBot MauiBot added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels Mar 24, 2026
@dotnet dotnet deleted a comment from MauiBot Mar 24, 2026
@dotnet dotnet deleted a comment from MauiBot Mar 24, 2026
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 29, 2026

🚦 Gate - Test Before and After Fix

📊 Expand Full Gate784a5f7 · Review changes

Gate Result: ❌ FAILED

Platform: ANDROID · Base: main · Merge base: 720a9d4a

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue30957 Issue30957 ❌ PASS — 1633s ✅ PASS — 512s
🔴 Without fix — 🖥️ Issue30957: PASS ❌ · 1633s

(truncated to last 15,000 chars)

: error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]

Build FAILED.

/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Failure calling service package: Broken pipe (32) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:  [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:15:50.89
* daemon not running; starting now at tcp:5037
* daemon started successfully
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:08:16.95
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 1.69 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 1.64 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 7 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 1.44 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 4.9 sec).
  Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 1.63 sec).
  Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 5 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 2.56 sec).
  5 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.10]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.30]   Discovered:  Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 04/02/2026 12:29:46 FixtureSetup for Issue30957(Android)
>>>>> 04/02/2026 12:29:48 FlexLayoutWrappingWithToleranceWorksCorrectly Start
>>>>> 04/02/2026 12:29:52 FlexLayoutWrappingWithToleranceWorksCorrectly Stop
  Passed FlexLayoutWrappingWithToleranceWorksCorrectly [3 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 25.2338 Seconds

🟢 With fix — 🖥️ Issue30957: PASS ✅ · 512s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:06:37.74
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723874
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.16]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.40]   Discovered:  Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 04/02/2026 12:38:18 FixtureSetup for Issue30957(Android)
>>>>> 04/02/2026 12:38:21 FlexLayoutWrappingWithToleranceWorksCorrectly Start
>>>>> 04/02/2026 12:38:25 FlexLayoutWrappingWithToleranceWorksCorrectly Stop
  Passed FlexLayoutWrappingWithToleranceWorksCorrectly [4 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 20.4157 Seconds

⚠️ Issues found
  • Issue30957 PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (2 files)
  • eng/pipelines/ci-copilot.yml
  • src/Core/src/Layouts/Flex.cs

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 29, 2026

🤖 AI Summary

📊 Expand Full Review784a5f7 · Review changes
🔍 Pre-Flight — Context & Validation

Issue: #30957 - FlexLayout Wrap Misalignment with Dynamically-Sized Buttons in .NET MAUI
PR: #31341 - [Windows/Android] FlexLayout: Fix wrap misalignment due to floating-point precision
Platforms Affected: Windows (originally reported), Android (confirmed by @DavidIDCI), potentially all platforms
Files Changed: 1 implementation, 2 test

Key Findings

  • Bug root cause: Flex.cs layout_item() uses layout.flex_dim < child_size to decide if a child wraps to a new line. Due to DPI/density-scale rounding, flex_dim can be computed fractionally smaller than child_size (e.g., sub-0.1 unit discrepancy), causing items to wrap prematurely even when space exists.
  • PR fix: Adds const float FlexWrapTolerance = 0.1f inside #if ANDROID || WINDOWS block, then uses flex_tolerance = layout.flex_dim + FlexWrapTolerance in the wrap comparison.
  • Platform guard concern: Fix is restricted to #if ANDROID || WINDOWS despite the floating-point precision issue being theoretically possible on all platforms. @DavidIDCI confirmed that removing the #if conditionals also works. Prior reviewer @jsuarezruiz noted the issue may reproduce on Android too.
  • Test weakness (critical): The UI test uses buttons with text "Button1/Button2/Button3" which @DavidIDCI confirmed does NOT reproduce the precision issue on any tested Android devices. The Gate has FAILED twice now — the test passes regardless of whether the fix is present. The test effectively tests nothing.
  • Prior agent review: Gate FAILED. Tests were not catching the bug before the fix, confirming the test design is the primary blocker.
  • Snapshot approach: The test as written uses positional assertions (Y coordinate equality, X ordering). No VerifyScreenshot() calls in the current version despite reviewer request.

Reviewer Discussion

File Reviewer Comment Status
Flex.cs:539 copilot-pr-reviewer Magic number 0.1f needs explanation Addressed (constant named + commented)
Issue30957.cs jsuarezruiz Should verify rendering with snapshots Partially addressed — author says screenshot added
Issue30957.cs jsuarezruiz Bug still reproducible on Android Author disputes; @DavidIDCI confirms Android repro
Issue30957.cs DavidIDCI Button text doesn't repro on test devices; fix should be all platforms Open / unresolved

Files Changed

Fix files (1):

  • src/Core/src/Layouts/Flex.cs (+20/-1): Adds FlexWrapTolerance = 0.1f in #if ANDROID || WINDOWS block, changes wrap check from layout.flex_dim < child_size to flex_tolerance < child_size

Test files (2):

  • src/Controls/tests/TestCases.HostApp/Issues/Issue30957.cs (+126): Test page with 3 buttons in FlexLayout, toggle button to switch font family
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs (+35): NUnit UI test asserting same Y position and ordered X positions after font toggle

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31341 Add FlexWrapTolerance = 0.1f in #if ANDROID || WINDOWS block ❌ Gate FAILED (test doesn't catch bug) Flex.cs Original PR fix

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) Unconditional 0.1f tolerance (remove #if ANDROID || WINDOWS) + improved test with explicit-sized labels ✅ PASS Flex.cs, Issue30957.cs (both) Simpler than PR; unconditional; test improved
2 try-fix (claude-sonnet-4.6) Round both flex_dim and child_size to 3 decimal places (MathF.Round) before comparison — symmetric noise elimination, no platform guards ✅ PASS Flex.cs Symmetric vs PR's one-sided bias; no magic constant
3 try-fix (gpt-5.3-codex) Scale-aware relative epsilon: NeedsWrap(flex_dim, child_size) helper using `max( a , b
4 try-fix (gemini-3-pro-preview) ⏳ IN PROGRESS
PR PR #31341 Add FlexWrapTolerance = 0.1f in #if ANDROID || WINDOWS block ❌ Gate FAILED (test doesn't catch bug) Flex.cs Platform-conditional only

Cross-Pollination

Model Round New Ideas? Details
2 ⏳ PENDING

Exhausted: No (2 of 4 models remaining)
Selected Fix: TBD


@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 29, 2026

🤖 AI Summary

📊 Expand Full Review784a5f7 · Review changes
🔍 Pre-Flight — Context & Validation

Issue: #30957 - FlexLayout Wrap Misalignment with Dynamically-Sized Buttons in .NET MAUI
PR: #31341 - [Windows/Android] FlexLayout: Fix wrap misalignment due to floating-point precision
Platforms Affected: Windows (originally reported), Android (confirmed by @DavidIDCI), all platforms apply
Files Changed: 1 fix file, 2 test files

Key Findings

  • Bug: Flex.cs layout_items() uses layout.flex_dim < child_size to decide if a child wraps to a new line. DPI scaling (e.g. 1.25x on Windows, 2.625 density on Pixel 7 Android) causes flex_dim to be computed fractionally smaller than child_size, triggering premature wrap even when the item fits.
  • PR Fix: Adds FlexWrapTolerance = 0.1f constant applied via #if ANDROID || WINDOWS (not unconditional as PR description states — discrepancy with description).
  • Gate FAILED (android, this review): Test uses "Button1/Button2/Button3" text — does NOT trigger the precision issue on test device. Tests pass identically with or without the fix.
  • Prior agent review also FAILED: Same root cause — test doesn't catch the bug.
  • Reviewer @DavidIDCI confirmed: issue reproduces on Pixel 7 (density 2.625) but NOT with "Button1/Button2/Button3" text. Reviewers confirmed the fix approach is correct but the test is inadequate.
  • PR description says "unconditional across all platforms" but actual code uses #if ANDROID || WINDOWS — iOS/Mac will NOT get the tolerance fix.

Reviewer Comments (Unresolved)

File:Line Reviewer Issue Status
Flex.cs:539 Copilot Magic number 0.1f explanation Addressed (named const + XML docs added)
Issue30957.cs:20 jsuarezruiz Test should use snapshots Addressed (VerifyScreenshot removed, Y/X position checks used instead)
Issue30957.cs:20 DavidIDCI "Button1/Button2/Button3" text doesn't reproduce on any Android device tested ⚠️ UNRESOLVED — critical gap
Issue30957.cs:21 Copilot Empty line after WaitForElement ⚠️ UNRESOLVED — minor

Files Changed

Fix files (1):

  • src/Core/src/Layouts/Flex.cs (+20/-1): Adds FlexWrapTolerance = 0.1f constant under #if ANDROID || WINDOWS, applies it to wrap boundary comparison

Test files (2):

  • src/Controls/tests/TestCases.HostApp/Issues/Issue30957.cs (new, +126): Test page with FlexLayout, font-family toggle trigger
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs (new, +35): NUnit UI test checking button Y-positions match after toggle

Test Type: UI Tests (position assertions, no screenshot verification)

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31341 Add FlexWrapTolerance = 0.1f under #if ANDROID || WINDOWS, add to flex_dim comparison ❌ Gate FAILED (test doesn't catch bug) Flex.cs (+20/-1) Fix code is correct; test is inadequate; iOS/Mac not covered

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (opus) Unconditional 0.1f tolerance (no #if) + FixedMeasureBox 300dp/100.07dp test ✅ PASS Flex.cs (+8/-1), HostApp (+69/-122), Shared (+38/-32) Removes platform conditional; test reliable but needs custom ContentView
2 try-fix (sonnet) Unit test Core.UnitTests + relative tolerance child_size * 0.001f (no #if) ✅ PASS Flex.cs (+4/-1), new unit test (+62) Device-free; deterministic; simplest fix overall
3 try-fix (codex) ULP-based ShouldWrap() + FlexBasis(1/3 relative) 100dp test ✅ PASS Flex.cs (+ULP method), HostApp (FlexBasis) Works but ULP comparison in layout engine is overcomplicated
4 try-fix (gpt-5.4) Round remaining-space to 4 decimals + "MMMMMMMM" text ❌ FAIL Flex.cs, HostApp Test still passed without fix
5 try-fix (sonnet cross-poll R1) Pixel-snap child sizes in FlexLayout.cs via DeviceDisplay.MainDisplayInfo.Density ✅ PASS FlexLayout.cs (+21) Fixes root cause at boundary; but couples to DeviceDisplay + try/catch smell
6 try-fix (sonnet cross-poll R2) Recompute flex_dim = size_dim - occupied_dim (single subtraction) ❌ FAIL Flex.cs (+6/-4) Mathematically equivalent; root cause is dp/pixel mismatch not accumulation
PR PR #31341 FlexWrapTolerance = 0.1f under #if ANDROID || WINDOWS ❌ Gate FAILED Flex.cs (+20/-1) Test doesn't catch bug; #if unnecessarily excludes iOS/Mac

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 Yes Integer pixel comparison in wrap check (covered by Attempt 5 approach)
claude-sonnet-4.6 2 Yes → Attempt 6 Recompute fresh — FAILED (equivalent math)
gpt-5.3-codex 2 Yes Kahan/double summation — equivalent failure mode to Attempt 6
claude-opus-4.6 3 NO NEW IDEAS Root cause fully explored; only tolerance or pixel-snapping viable

Exhausted: Yes — problem space covered; all fix strategies (tolerance vs pixel-snapping) attempted
Selected Fix: Attempt 2 (sonnet) — unit test + child_size * 0.001f relative tolerance (no #if)

Rationale: Attempt 2 is the best overall:

  1. Simplest Flex.cs change (4 lines, no platform #if, no new struct fields)
  2. Best test (device-free unit test, deterministic, ~66ms)
  3. Clean relative tolerance scales correctly for any child size
  4. No custom ContentView, no DeviceDisplay coupling, no ULP complexity

Compared to PR's fix: same strategy (tolerance) but universal (no #if ANDROID || WINDOWS) and with a test that actually catches the bug.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #30957, FlexLayout float precision wrap bug
Gate ❌ FAILED Android — tests pass both with and without fix
Try-Fix ✅ COMPLETE 6 attempts: 4 passing, 2 failing; Attempt 2 selected as best
Report ✅ COMPLETE

Summary

PR #31341 addresses a genuine and confirmed bug in FlexLayout's wrap algorithm: floating-point precision from DPI density causes flex_dim to be computed slightly smaller than child_size, triggering premature wrapping. The fix strategy (adding a small tolerance to the wrap comparison) is correct. However, Gate FAILED on Android: the tests added by this PR pass identically with and without the fix, providing zero regression protection.

Try-Fix exploration found a better alternative (Attempt 2) that:

  1. Removes the unnecessary #if ANDROID || WINDOWS conditional — makes the fix universal
  2. Adds a properly-behaved relative tolerance (child_size * 0.001f) rather than a magic absolute constant
  3. Includes a device-free unit test in Core.UnitTests that deterministically catches the bug in ~66ms

Root Cause

Flex.cs layout_items() uses layout.flex_dim < child_size to decide if a child wraps. DPI density scaling creates an asymmetry:

  • Container width (from platform): 300dp → 788px → 300.19dp (pixel-rounded)
  • Child widths (from MeasureOverride): exact dp values, e.g., 100.07dp
  • After 2 children: flex_dim = 300.19 - 100.07 - 100.07 = 100.05dp
  • 100.05 < 100.07 → 3rd child wraps despite fitting

The fix must absorb the ~0.02dp discrepancy. Approach verified empirically: flex_dim + child_size * 0.001f < child_size (i.e., remaining space must be less than 99.9% of child size to wrap).

Gate Status: ❌ FAILED

Verified on Android (this review):

  • Tests PASS without fix — tests do not catch the bug
  • Tests PASS with fix — expected

Root cause: button text "Button1/Button2/Button3" does not create a dimension near the FlexLayout wrap boundary on the test device. Confirmed by reviewer @DavidIDCI: issue density-sensitive, reproduces on Pixel 7 (2.625) but not with this text.

Issues Found

🔴 Critical: Tests don't catch the bug (Gate FAILED)

  • Problem: Issue30957.cs (HostApp) uses short button text that doesn't trigger the precision issue on test devices. Confirmed failing on Android (this review) and iOS (prior review).
  • Better approach found: Unit test in Core.UnitTests using MeasureOverride to inject exact float values (300.19f container, 100.07f children) — deterministic, device-free, reliably fails without fix and passes with fix.
  • Suggested fix (from Attempt 2):
    // In src/Controls/tests/Core.UnitTests/Layouts/Issue30957.cs (new file):
    // Uses CrossPlatformMeasure(300.19f, 200) + CrossPlatformArrange()
    // to directly exercise the Flex.cs wrap check with exact precision-triggering values

🔴 Critical: #if ANDROID || WINDOWS unnecessarily restricts the fix

  • Problem: iOS and macCatalyst can also experience float precision issues at non-integer densities, yet the fix is excluded via #if ANDROID || WINDOWS. The PR description says "applied unconditionally" but the code contradicts this.
  • Better approach (from Attempt 2): Remove the conditional entirely. The tolerance child_size * 0.001f (0.1% of child size) is safe on all platforms — it only prevents wrapping when remaining space is within 0.1% of child size, which is always a rounding artifact, never intentional.

🟡 Minor: PR description vs implementation mismatch

  • PR description says "This tolerance is applied unconditionally across all platforms" but Flex.cs uses #if ANDROID || WINDOWS. Should be corrected.

🟡 Minor: Empty line in test method

  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30957.cs:21 — blank line after WaitForElement call. Remove.

🟡 Minor: Malformed comment in Flex.cs

  • Line ~549: "...behavior.Hence, minimum tolerance..." — missing space before "Hence".

Fix Quality Assessment

The code fix in Flex.cs is technically sound — adding tolerance to the wrap comparison is the correct approach for floating-point geometry. The named constant and XML documentation are good. However:

Aspect PR's Fix Attempt 2 (Better)
Platform coverage #if ANDROID || WINDOWS only Universal (all platforms)
Tolerance type Absolute 0.1f Relative child_size * 0.001f
Test reliability ❌ Doesn't catch bug ✅ Deterministic unit test
Test speed Minutes (device required) ~66ms (no device needed)
Lines changed (fix) +20/-1 +4/-1

Suggested Changes for PR Author

  1. Replace the #if ANDROID || WINDOWS block with unconditional tolerance:

    // Replace platform conditional with universal tolerance
    if (layout.flex_dim + child.Frame[layout.frame_size_i] * 0.001f < child.Frame[layout.frame_size_i])

    Or keep named constant for readability:

    const float wrapTolerance = child_size * 0.001f;
    if (layout.flex_dim + wrapTolerance < child_size)
  2. Replace the UI test with a unit test in src/Controls/tests/Core.UnitTests/Layouts/Issue30957.cs that:

    • Uses CrossPlatformMeasure(300.19f, 200) + CrossPlatformArrange() on a FlexLayout
    • Injects exact 100.07f values via MeasureOverride in a custom FixedSizeLabel
    • Asserts child.Frame.Y == 0 for all 3 children (all on row 0)
    • Runs in ~66ms with no device dependency
  3. Update PR title to remove [Windows/Android] prefix since fix is universal: FlexLayout: Fix wrap misalignment due to floating-point precision rounding


@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Mar 29, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

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

The test couldn't catch a bug - can you please verify?

@SuthiYuvaraj
Copy link
Copy Markdown
Contributor Author

@kubaflo , While working on this issue,I have observed that it does not reproduce locally when display scaling is set to 100%. The behavior occurs only when scaling is increased to 125% or higher, which cannot be replicated in CI environments. As a result, the Gate failure was environment‑specific and not indicative of a regression under normal scaling. To ensure coverage, a UITest has been added and validated manually for high display‑scaling scenarios.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Apr 2, 2026

@SuthiYuvaraj okay, thanks for letting me know :)

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

Labels

community ✨ Community Contribution layout-flex FlexLayout issues partner/syncfusion Issues / PR's with Syncfusion collaboration 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-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.

FlexLayout Wrap Misalignment with Dynamically-Sized Buttons in .NET MAUI

9 participants