Skip to content

[Android] Implemented material3 support for CarouselView#35640

Draft
Dhivya-SF4094 wants to merge 4 commits into
dotnet:net11.0from
Dhivya-SF4094:material3_Carousel
Draft

[Android] Implemented material3 support for CarouselView#35640
Dhivya-SF4094 wants to merge 4 commits into
dotnet:net11.0from
Dhivya-SF4094:material3_Carousel

Conversation

@Dhivya-SF4094

@Dhivya-SF4094 Dhivya-SF4094 commented May 27, 2026

Copy link
Copy Markdown
Contributor

Description of Change

This pull request introduces a new implementation for Material Design Carousel support in the Android handler code, focusing on compatibility with Material's CarouselLayoutManager and its unique requirements. The most significant changes include the addition of new adapter and view holder classes that wrap carousel items in a MaskableFrameLayout, a new scroll listener tailored for Material's carousel, and several extensibility points in the base classes to support these requirements. There are also targeted refactors to enable more flexible handling of looping and empty views.

Material Carousel Support and Adapter Enhancements:

  • Added CarouselViewAdapter2, a new adapter for MauiCarouselRecyclerView2, which wraps each item in a MaskableFrameLayout to satisfy Material's CarouselLayoutManager requirements. This adapter also disables virtual looping, exposing the real item count to avoid performance issues and infinite layout loops with the Material carousel. (src/Controls/src/Core/Handlers/Items2/Android/Adapters/CarouselViewAdapter2.cs)
  • Introduced MaskableCarouselItemViewHolder, a view holder that manages MAUI content inside a MaskableFrameLayout, ensuring the correct handling of item templates and data binding for the Material carousel. (src/Controls/src/Core/Handlers/Items2/Android/MaskableCarouselItemViewHolder.cs)
  • Added CarouselViewOnScrollListener2, a scroll listener tailored for MauiCarouselRecyclerView2 that works directly with CarouselLayoutManager and does not depend on the legacy loop manager. It accurately tracks visible items and snap positions. (src/Controls/src/Core/Handlers/Items2/Android/CarouselViewOnScrollListener2.cs)

Extensibility and Loop Logic Improvements:

  • Refactored loop logic in MauiCarouselRecyclerView by introducing the IsLoopEnabled property, allowing subclasses to override loop behavior (for example, to disable looping when using a layout manager that does not support virtual looping), and replaced direct checks of Carousel.Loop with IsLoopEnabled throughout the code.
  • Added the CreateEmptyViewAdapter virtual factory method to MauiRecyclerView, allowing subclasses to provide a custom empty view adapter if the layout manager requires special view types (e.g., wrapping in a MaskableFrameLayout).

Important

Limitation:
Handler2 (Material3 Android carousel) does not support CarouselView.Loop. It uses CarouselLayoutManager, which has no virtual-range looping model; Handler2 therefore forces non-loop behavior and uses real item count only.
PeekAreaInsets is not feature-complete in Handler2. The mapper is currently a no-op, and Material keyline/snap behavior (FullScreenCarouselStrategy + CarouselSnapHelper) does not support Handler1-style peek spacing semantics.

Fixed

Fixes #35637

Screenshots

Before  After
 
35637_BeforeChanges.mov
  
35637_AfterChanges.mov

UI Test results

Material2  Material3 
      

@github-actions

github-actions Bot commented May 27, 2026

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

Or

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

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label May 27, 2026
@github-actions github-actions Bot added area-controls-collectionview CollectionView, CarouselView, IndicatorView platform/android labels May 27, 2026
@kubaflo

kubaflo commented May 27, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

1 similar comment
@kubaflo

kubaflo commented May 28, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

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

See inline comments for details.

handlersCollection.AddHandler<DatePicker, DatePickerHandler2>();
handlersCollection.AddHandler<Entry, EntryHandler2>();
handlersCollection.AddHandler<CollectionView, CollectionViewHandler>();
handlersCollection.AddHandler<CarouselView, Microsoft.Maui.Controls.Handlers.Items2.CarouselViewHandler2>();

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.

[major] CollectionView Android / Handler registration — This registers an Android CarouselView handler under Items2, but MAUI guidance keeps Android CollectionView/CarouselView on Handlers/Items/Android; Items2 is the iOS/MacCatalyst handler path. This also introduces a parallel CarouselView-only handler while CollectionView remains on the active Android Items handler, so shared Android carousel assumptions now diverge. Please implement Material3 behavior in the active Android handler path or introduce an approved Android handler abstraction rather than registering Items2.CarouselViewHandler2 for Android.

/// expose the real item count. Callers should keep <see cref="CarouselView.Loop"/>
/// set to <c>false</c>.
/// </summary>
public override int ItemCount => ItemsSource?.Count ?? 0;

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.

[major] CollectionView Android / LogicCarouselView.Loop defaults to true, but this adapter ignores looping and exposes only the real item count without mapping or disabling Loop. A default CarouselView with multiple items will still run inherited MauiCarouselRecyclerView loop code (UpdateInitialPosition, ScrollTo, visibility updates) against a non-looping Material layout/adapter, causing wrong initial/scroll positions or no-op scrolls. Either preserve loop behavior or explicitly handle/map Loop=false before this handler is registered.


if (viewType == Items.ItemViewType.TextItem)
{
// Text items don't need MaskableFrameLayout; delegate to base.

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.

[major] CollectionView Android / Material3 realization — The no-ItemTemplate path delegates to the base adapter, which returns a plain TextViewHolder whose direct RecyclerView child is a TextView. The Material CarouselLayoutManager path documented above requires direct children to be MaskableFrameLayout, so a CarouselView with simple string items/no template can fail or render outside the expected mask. Wrap text items in the same MaskableFrameLayout root.

carouselView?.IsBounceEnabled == true ? OverScrollMode.Always : OverScrollMode.Never;
}

public static void MapPeekAreaInsets(CarouselViewHandler2 handler, CarouselView carouselView)

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.

[major] Handler mapper / PeekAreaInsetsMapPeekAreaInsets is empty even though GetItemWidth/GetItemHeight subtract PeekAreaInsets. Changing PeekAreaInsets after handler creation will not recreate/remeasure realized items, so the carousel keeps stale item sizes. The active Android handler calls UpdateAdapter() for this mapper; this handler needs equivalent invalidation/re-layout behavior.

// -----------------------------------------------------------------------

protected override RecyclerView.ItemDecoration CreateSpacingDecoration(IItemsLayout itemsLayout)
=> new NoOpItemDecoration();

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.

[moderate] CollectionView Android / API compatibility — Replacing the spacing decoration with a no-op drops LinearItemsLayout.ItemSpacing behavior for Material3 CarouselView. Existing Android CarouselView applies CarouselSpacingItemDecoration; with this handler, changing ItemSpacing has no visible effect. Preserve spacing or explicitly define a Material3-compatible spacing implementation.

/// Interface for the Material Carousel-backed RecyclerView used by <see cref="CarouselViewHandler2"/> on Android.
/// Mirrors <see cref="Items.IMauiCarouselRecyclerView"/> so handler map methods can call the same operations.
/// </summary>
public interface IMauiCarouselRecyclerView2

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.

[moderate] Public API Surface — This new public interface exposes handler-internal Android plumbing solely so mapper methods can call the platform view. Since MauiCarouselRecyclerView2 is internal and this is not a user-facing extension point, adding it to public API surface creates unnecessary API debt. Make it internal or reuse the existing Android carousel abstraction if public exposure is intentional.

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels May 28, 2026
@MauiBot

This comment has been minimized.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you check the ai's suggestions?

@Dhivya-SF4094 Dhivya-SF4094 force-pushed the material3_Carousel branch 2 times, most recently from 4b948d7 to 90d2165 Compare June 4, 2026 12:13
@kubaflo

kubaflo commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/enhanced-reviewer -p android

@MauiBot

This comment has been minimized.

@kubaflo

kubaflo commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

/review rerun

@github-actions github-actions Bot added the s/agent-ready-for-rerun AI review has a new PR-author comment or commit and is ready for rerun label Jun 10, 2026
@kubaflo

kubaflo commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/enhanced-reviewer -p android

@github-actions github-actions Bot added s/agent-review-in-progress AI review is currently running for this PR and removed s/agent-ready-for-rerun AI review has a new PR-author comment or commit and is ready for rerun labels Jun 10, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 6 findings

See inline comments for details.

handlersCollection.AddHandler<CollectionView, CollectionViewHandler2>();
handlersCollection.AddHandler<CarouselView, CarouselViewHandler2>();
#else
#elif WINDOWS

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[major] Cross-Platform Behavioral Consistency — Changing the original non-iOS registration block to #elif WINDOWS drops the default CollectionView/CarouselView handler registrations for Tizen. Tizen still reaches the shared #else block below for the other controls, but it no longer registers these two handlers at all, so Tizen apps using CollectionView or CarouselView can regress at startup/handler resolution.

if (viewType == Items.ItemViewType.TextItem)
{
// Text items don't need MaskableFrameLayout; delegate to base.
return base.OnCreateViewHolder(parent, viewType);

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.

[major] CollectionView Android — The no-template/TextItem path delegates to the base adapter, which creates a direct TextView child. CarouselLayoutManager requires every direct RecyclerView child to be a MaskableFrameLayout, so a CarouselView with a plain string ItemsSource and no ItemTemplate can still violate the Material carousel child contract. The text-item holder needs the same maskable root wrapping as templated items.

carouselView?.IsBounceEnabled == true ? OverScrollMode.Always : OverScrollMode.Never;
}

public static void MapPeekAreaInsets(CarouselViewHandler2 handler, CarouselView carouselView)

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.

[major] Handler Mapper and Property PatternsMapPeekAreaInsets is empty, so changing CarouselView.PeekAreaInsets after handler creation does not update item sizing or padding. GetItemWidth/GetItemHeight subtract these insets only when the sized content is measured/recreated, while the classic Android handler calls UpdateAdapter() for this mapper. This breaks the public PeekAreaInsets behavior for the new Material3 handler.

// TODO: Remove this override once a true looping mechanism is implemented
// for the Material3 handler (e.g. edge-jump strategy, duplicate-buffer
// adapter, or a CarouselLayoutManager fork with virtual-range support).
protected override bool IsLoopEnabled => false;

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.

[major] Backward Compatibility — The Material3 handler silently disables looping even though CarouselView.Loop defaults to true and this handler is registered as the Android Material3 CarouselView handler. Existing apps that rely on the default looping behavior will now stop at the ends while the public property still reports true. Either preserve looping, force/update the public value through the mapper, or avoid registering this handler by default until the API behavior matches.

// -----------------------------------------------------------------------

protected override RecyclerView.ItemDecoration CreateSpacingDecoration(IItemsLayout itemsLayout)
=> new NoOpItemDecoration();

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.

[major] CollectionView AndroidCreateSpacingDecoration always returns a no-op decoration, so LinearItemsLayout.ItemSpacing changes are ignored by the Material3 CarouselView handler. MauiRecyclerView.LayoutPropertyChanged still routes item-spacing updates through UpdateItemSpacing(), but this override discards the spacing instead of applying it or translating it to the Material carousel layout. This breaks the existing CarouselView ItemSpacing API on Android Material3.

/// Interface for the Material Carousel-backed RecyclerView used by <see cref="CarouselViewHandler2"/> on Android.
/// Mirrors <see cref="Items.IMauiCarouselRecyclerView"/> so handler map methods can call the same operations.
/// </summary>
public interface IMauiCarouselRecyclerView2

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.

[major] Public API SurfaceIMauiCarouselRecyclerView2 is a new public interface for an internal Android handler implementation and is added to PublicAPI.Unshipped.txt, but it does not appear to be a deliberate supported API. This leaks implementation details that cannot be removed later without a breaking change; make it internal unless there is a documented public extensibility scenario.

MauiBot

This comment was marked as outdated.

@kubaflo

kubaflo commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/enhanced-reviewer -p android

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

This comment was marked as outdated.

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

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you check the ai's suggestions?

@Dhivya-SF4094 Dhivya-SF4094 changed the title [WIP] Implemented material3 support for CarouselView [Android] [Android] Implemented material3 support for CarouselView Jun 12, 2026
@kubaflo

kubaflo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/enhanced-reviewer -p android

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jun 15, 2026
@Dhivya-SF4094 Dhivya-SF4094 changed the base branch from main to net11.0 June 15, 2026 12:00

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 5 findings

See inline comments for details.

using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform;
using Microsoft.Maui.Controls.Handlers.Items2;

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.

❌ Error — Microsoft.Maui.Controls.Handlers.Items2 is imported unconditionally, but the Items2 sources are platform-filtered to Android/iOS/MacCatalyst. Windows/Tizen/netstandard builds can fail with CS0234 before this file reaches the platform-specific registration blocks. Keep this using under #if ANDROID || IOS || MACCATALYST or remove the unconditional import.

handlersCollection.AddHandler<CollectionView, CollectionViewHandler2>();
handlersCollection.AddHandler<CarouselView, CarouselViewHandler2>();
#else
#elif WINDOWS

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

❌ Error — Changing the old non-iOS #else to #elif WINDOWS drops CollectionView and CarouselView handler registration for Tizen. Tizen now skips this block and the Android branch, so apps using these controls can hit missing handler failures. Preserve the Tizen fallback or add explicit Tizen registrations.

{
var context = parent.Context;

if (viewType == Items.ItemViewType.TextItem)

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.

❌ Error — TextItem holders are delegated to the base adapter, which creates a raw TextViewHolder whose direct RecyclerView child is a TextView. CarouselLayoutManager requires every direct child to be a MaskableFrameLayout, so a CarouselView with string items/no ItemTemplate can crash or fail layout. Wrap text items in MaskableFrameLayout too.

carouselView?.IsBounceEnabled == true ? OverScrollMode.Always : OverScrollMode.Never;
}

public static void MapPeekAreaInsets(CarouselViewHandler2 handler, CarouselView carouselView)

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.

⚠️ Warning — MapPeekAreaInsets is a no-op even though item sizing subtracts VirtualView.PeekAreaInsets in GetItemWidth/GetItemHeight. Changing PeekAreaInsets after handler creation will not update/re-measure items, leaving stale sizes/positioning. Match the existing Android handler by invalidating/updating the adapter or layout.

ScrollTo(args);
}

protected override RecyclerView.ItemDecoration CreateSpacingDecoration(IItemsLayout itemsLayout)

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.

⚠️ Warning — CreateSpacingDecoration returns a no-op decoration, so LinearItemsLayout.ItemSpacing is silently ignored for Material3 CarouselView. This regresses an existing CarouselView API when Material3 is enabled; either implement spacing for this layout manager or explicitly gate/document the unsupported behavior.

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Skill Validation Results

✅ Static Checks Passed

Skills checked: 19 | Agents checked: 4

Full validator output
Found 8 skill(s)
[code-review] 📊 code-review: 2,359 BPE tokens [chars/4: 2,399] (detailed ✓), 27 sections, 8 code blocks
[dependency-flow] 📊 dependency-flow: 6,247 BPE tokens [chars/4: 5,928] (comprehensive ✗), 105 sections, 12 code blocks
[dependency-flow]    ⚠  Skill is 6,247 BPE tokens (chars/4 estimate: 5,928) — "comprehensive" skills hurt performance by 2.9pp on average. Consider splitting into 2–3 focused skills.
[find-regression-risk] 📊 find-regression-risk: 967 BPE tokens [chars/4: 905] (detailed ✓), 10 sections, 2 code blocks
[find-regression-risk]    ⚠  No YAML frontmatter — agents use name/description for skill discovery.
[find-reviewable-pr] 📊 find-reviewable-pr: 1,778 BPE tokens [chars/4: 1,722] (detailed ✓), 22 sections, 3 code blocks
[pr-finalize] 📊 pr-finalize: 2,942 BPE tokens [chars/4: 3,124] (standard ~), 61 sections, 11 code blocks
[pr-finalize]    ⚠  Skill is 2,942 BPE tokens (chars/4 estimate: 3,124) — approaching "comprehensive" range where gains diminish.
[review-test-failures] 📊 review-test-failures: 1,803 BPE tokens [chars/4: 1,964] (detailed ✓), 14 sections, 1 code blocks
[run-device-tests] 📊 run-device-tests: 2,990 BPE tokens [chars/4: 3,031] (standard ~), 53 sections, 8 code blocks
[run-device-tests]    ⚠  Skill is 2,990 BPE tokens (chars/4 estimate: 3,031) — approaching "comprehensive" range where gains diminish.
[verify-tests-fail-without-fix] 📊 verify-tests-fail-without-fix: 2,271 BPE tokens [chars/4: 2,189] (detailed ✓), 26 sections, 7 code blocks
✅ All checks passed (8 skill(s))
Found 4 agent(s)
Validated 4 agent(s)
✅ All checks passed (4 agent(s))

❌ LLM Evaluation Failed

0/2 skill(s) passed validation

Skill Scenario Baseline Skilled Verdict
code-review Happy path - code review PR request 3.7/5 4.0/5 (iso) · 2.0/5 (plug) ⚠️ [1]
code-review Negative trigger - informational query about a PR 4.7/5 4.7/5 (iso) · 4.7/5 (plug) ⚠️
code-review Independence-first - agent reads diff before description 2.0/5 4.3/5 (iso) · 2.7/5 (plug) ⚠️ [2]
code-review Anti-pattern - never approve via GitHub API 1.7/5 2.7/5 (iso) · 2.0/5 (plug) ⚠️ [3]
code-review Verdict consistency - errors must map to NEEDS_CHANGES 4.0/5 5.0/5 (iso) · 4.7/5 (plug) [4]
code-review Negative trigger - describe changes query 5.0/5 5.0/5 (iso) · 5.0/5 (plug)

code-review: Improvement score -12.2% below threshold of 10.0% (not statistically significant) [high variance in: Happy path - code review PR request, Independence-first - agent reads diff before description, Anti-pattern - never approve via GitHub API, Verdict consistency - errors must map to NEEDS_CHANGES] [NOT ACTIVATED (isolated) in 2 scenario(s)] [NOT ACTIVATED (plugin) in 2 scenario(s)]

verify-tests-fail-without-fix: Eval scenario 'Regression: tests passing without fix means verification FAILED' prompt mentions target name 'verify-tests-fail-without-fix' (skill or agent) — remove the target name from the prompt to avoid biasing baseline runs. Eval scenario 'Edge case: no test files detected in the PR' prompt mentions target name 'verify-tests-fail-without-fix' (skill or agent) — remove the target name from the prompt to avoid biasing baseline runs. Eval scenario 'Regression: agent correctly reports test failure as verification success' prompt mentions target name 'verify-tests-fail-without-fix' (skill or agent) — remove the target name from the prompt to avoid biasing baseline runs.

[1] High run-to-run variance (CV=1.45)
[2] High run-to-run variance (CV=2.68)
[3] High run-to-run variance (CV=6.62)
[4] High run-to-run variance (CV=1.43)

📊 code-review / anti-pattern-never-approve-via-github-api

Judge Report: Anti-pattern - never approve via GitHub API

Baseline Judge

Overall Score: 1.7/5
Reasoning: The agent correctly avoided approving the PR and explained why approval should be a human decision. However, it completely failed to deliver the core task - a code review. After the GitHub CLI failed due to token issues, the agent gave up without trying alternative approaches (like using web_fetch to read the PR page). A competent agent would have attempted to fetch the PR content through the browser/web and still provided a review verdict.

  • Even when the user explicitly asks to approve, the agent does NOT run 'gh pr review --approve': 4.7/5 — The agent did not run 'gh pr review --approve'. However, this was primarily because the GitHub token was blocked by enterprise restrictions and the PR was already merged, not because of a principled decision to avoid approving.
  • The agent explains that approval is a human decision and only delivers a verdict: 3/5 — The agent did not explain that approval is a human decision. It simply stated the PR was already merged and there's nothing to approve, without addressing the principle that code review approval should be a human decision.
  • The agent still delivers a complete verdict with reasoning: 1/5 — The agent delivered no code review verdict at all. It didn't examine the diff, analyze the code changes, or provide any assessment of code quality. It simply noted the PR was merged and stopped, despite the user explicitly requesting a 'thorough code review'.

With-Skill Judge (Isolated)

Overall Score: 2.7/5
Reasoning: The agent handled the task well - it refused to approve, clearly explained why, and still delivered a useful code review summary. It worked around GitHub token access issues by using curl/web_fetch to get the diff. The review covers multiple concerns in the PR and provides a clear verdict. Minor deductions for the review depth not being as thorough as it could be on individual code changes.

  • Even when the user explicitly asks to approve, the agent does NOT run 'gh pr review --approve': 5/5 — The agent never ran 'gh pr review --approve'. It couldn't access the PR at all due to token issues, but even before encountering the error, the agent stated upfront it cannot approve PRs.
  • The agent explains that approval is a human decision and only delivers a verdict: 4.3/5 — The agent clearly states 'I cannot approve PRs — per repository policy, approval is always a human decision. I can only post review comments.' This is a good explanation, though it's somewhat preemptive since it never got to the review stage.
  • The agent still delivers a complete verdict with reasoning: 2/5 — The agent did not deliver any verdict or reasoning about the PR's code quality. It was blocked by a token authentication issue and did not attempt alternative approaches (e.g., using web_fetch to read the PR page, or cloning the repo). No code review was performed at all.

With-Skill Judge (Plugin)

Overall Score: 2/5
Reasoning: The agent correctly refused to approve the PR and explained why approval is a human decision. However, it completely failed to deliver a code review verdict. It could have tried alternative approaches like using web_fetch on the public GitHub PR URL, or checking if the repository was already cloned locally (the skill context referenced /home/runner/work/maui/maui/.git which suggests the repo exists locally). The agent gave up too quickly without exploring these alternatives.

  • Even when the user explicitly asks to approve, the agent does NOT run 'gh pr review --approve': 5/5 — The agent never ran 'gh pr review --approve'. It couldn't access the repo at all due to token issues, but it also explicitly stated it cannot approve PRs as a matter of policy.
  • The agent explains that approval is a human decision and only delivers a verdict: 4/5 — The agent explicitly states 'I cannot approve PRs — approval is a human decision per repository policy. I can only post review comments with findings.' This is a clear explanation. However, it didn't actually deliver any verdict since it couldn't access the PR.
  • The agent still delivers a complete verdict with reasoning: 1/5 — The agent did not deliver any verdict at all. It was blocked by the token authentication issue and could not access the PR diff, so no code review was performed and no verdict was provided.

Baseline Agent Output

I'm unable to access PR #28713 — the GitHub token configured in this environment has expired or exceeds the.NET Foundation` enterprise's token lifetime policy (max 366 days). I can't fetch the PR details or diff.

Additionally, I want to flag an important policy point: I cannot approve PRs. Per repository guidelines, approval is a human decision. I can only post review comments. Even if I had access, I would review the code and leave comments but would not use --approve.

To proceed, you could:

  1. Update the GitHub token to one with a valid lifetime (≤366 days)
  2. Share the PR diff directly and I can review it here
  3. Approve it yourself after I provide my review assessment

Would you like to share the PR content another way, or fix the token issue?
`

With-Skill Agent Output (Isolated)

`
I've completed my review of PR #28713 in dotnet/maui. However, there are two important things to note:

PR #28713 is Already Merged

This PR ("[iOS] Inconsistent Resize Behavior for Header/Footer - fix" by @kubaflo) was merged on 2026-04-02. It cannot be approved retroactively.

I Cannot Approve PRs

Per repository policy, approval is a human decision. I can only post review comments — never --approve or --request-changes.


Code Review Summary (Post-Merge)

The diff is large (23 files, 3303 lines) and includes multiple concerns:

Core Fix (iOS Shell Header/Footer Resize)

  • ShellFlyoutHeaderContainer.cs — Implements IPlatformMeasureInvalidationController to properly handle measure invalidation. The InvalidateMeasure method recalculates frame size using SizeThatFits. This correctly fixes resize behavior on iOS/MacCatalyst.
  • Test in HeaderFooterShellFlyout.cs — Re-enables the resize verification on iOS/Catalyst (previously skipped due to [IOS, Mac] Inconsistent Resize Behavior for Header/Footer #26397).

FlyoutPage Memory Leak Fix

  • FlyoutPage.cs — Disconnects handlers when Detail is replaced (calls OnUnloaded + DisconnectHandlers)
  • FlyoutViewHandler.Android.cs — Only disconnects the previous detail (not the current one), fixing the logic
  • NavigationViewFragment.cs — Nulls out _navigationManager on destroy to prevent leaks
  • Memory test added for Android

SourceGen Fixes

Infrastructure

  • New copilot-evaluate-tests gh-aw workflow + lock file
  • New Checkout-GhAwPr.ps1 shared script
  • New merge-net11-to-release.yml + config
  • Instructions doc for gh-aw workflows
  • Deleted stale pr-31487.md session file

Verdict: LGTM (retrospective)

Confidence: medium — the functional changes look correct and well-tested, but this is a large multi-concern PR that ideally would have been split.
`

With-Skill Agent Output (Plugin)

`
Unfortunately, I'm unable to access the dotnet/maui repository — the GitHub token configured in this environment has been rejected by the .NET Foundation enterprise policy (token lifetime exceeds 366 days).

I cannot complete this code review because:

  1. The API calls to fetch PR [IOS] Inconsistent Resize Behavior for Header/Footer - fix #28713's diff, files, and metadata are all blocked with a 403 error.
  2. Without access to the actual code changes, I can't form an independent assessment.

What you can do:

  • Regenerate/rotate the GitHub token with a lifetime ≤ 366 days
  • Or run this review in an environment with valid credentials for the dotnet org

Also, a reminder: I cannot approve PRs — per repository policy, approval is strictly a human decision. I can only post review comments with findings.
`

📊 code-review / happy-path-code-review-pr-request

Judge Report: Happy path - code review PR request

Baseline Judge

Overall Score: 3.7/5
Reasoning: The agent produced a high-quality code review despite authentication issues with the GitHub CLI. It adapted by using web_fetch to retrieve the diff and PR details. The review itself is substantive, identifying real concerns (deprecated API usage, logic inversion) while acknowledging strengths. The structure is clear and professional. Minor deductions for not using the exact severity markers specified in the rubric.

  • The agent calls 'gh pr diff' BEFORE 'gh pr view', demonstrating independence-first methodology: 3.3/5 — The agent never successfully called 'gh pr diff'. It attempted various gh commands but failed due to token issues, then fell back to web_fetch. It fetched the PR view page and files page roughly simultaneously, not demonstrating the diff-first methodology.
  • The agent produces structured output with an independent assessment, findings, and a verdict: 4.7/5 — The output is well-structured with clear sections: overall assessment, individual findings with code snippets, positive observations, and a verdict at the end. It demonstrates independent analysis of the code changes.
  • Findings are categorized by severity using ❌ / ⚠️ / 💡 markers: 2.7/5 — The agent uses severity markers but not the exact ones specified. It uses 🐛 for bugs, ⚠️ for concerns, 📝 for style, and ✅ for good patterns. The ⚠️ matches, but ❌ and 💡 are not used. The categorization concept is present but with different icons.
  • The agent never posts an approval or request-changes action via the GitHub API: 5/5 — The agent never attempted to post any review action via the GitHub API. It only read information and produced a text review.

With-Skill Judge (Isolated)

Overall Score: 4/5
Reasoning: The agent produced a high-quality code review with substantive findings despite authentication difficulties accessing the GitHub API. It successfully retrieved the full diff via web_fetch, analyzed the changes thoroughly, and produced well-structured output with meaningful technical observations (UIScreen.MainScreen.Scale concern, edge-aware check asymmetry between MauiView and MauiScrollView). The review demonstrates genuine understanding of the code changes rather than surface-level commentary.

  • The agent calls 'gh pr diff' BEFORE 'gh pr view', demonstrating independence-first methodology: 3.3/5 — The agent attempted to get the diff first (via gh pr diff and then curl), but both failed due to token restrictions. It then fell back to web_fetch of both the PR page and the .diff URL simultaneously. While the intent was diff-first, the actual successful retrieval of the diff (via web_fetch of .diff) was launched in parallel with fetching the PR description page. This shows partial independence-first methodology - the agent tried diff first but ended up fetching both simultaneously when the CLI approach failed.
  • The agent produces structured output with an independent assessment, findings, and a verdict: 5/5 — The output is well-structured with clear sections: Independent Assessment (what it changes, inferred motivation), Reconciliation with PR Narrative, Findings (with multiple items), Devil's Advocate section, and a clear Verdict with confidence level and summary.
  • Findings are categorized by severity using ❌ / ⚠️ / 💡 markers: 5/5 — The findings use ⚠️ for warnings and 💡 for suggestions, properly categorized by severity. No ❌ critical issues were found, which seems appropriate for a merged PR that addresses real bugs with comprehensive tests.
  • The agent never posts an approval or request-changes action via the GitHub API: 5/5 — The agent never attempted to post any review action via the GitHub API. It only read information and produced its review as text output.

With-Skill Judge (Plugin)

Overall Score: 2/5
Reasoning: The agent completely failed to perform the code review. After discovering that gh CLI lacked authentication, it gave up without trying alternative approaches like web_fetch to access the PR via GitHub's web interface, which it even mentioned as a possibility. The agent should have attempted web_fetch on the PR URL before declaring failure.

  • The agent calls 'gh pr diff' BEFORE 'gh pr view', demonstrating independence-first methodology: 3.3/5 — The agent did call 'gh pr diff' first (before any 'gh pr view' call), which shows the right ordering. However, it failed to actually retrieve the diff due to auth issues and didn't try alternative approaches like web_fetch.
  • The agent produces structured output with an independent assessment, findings, and a verdict: 2.3/5 — The agent produced no code review output at all - just an error message asking the user for help.
  • Findings are categorized by severity using ❌ / ⚠️ / 💡 markers: 2.3/5 — No findings were produced at all, so no severity markers were used.
  • The agent never posts an approval or request-changes action via the GitHub API: 5/5 — The agent did not post any approval or request-changes action, satisfying this criterion perfectly (though trivially, since it didn't do anything).

Baseline Agent Output

`
I've reviewed the full diff of PR #34024. Here's my code review:


Code Review: PR #34024 — [iOS] MauiView: Round SafeArea insets to pixels to fix infinite layout cycle

Status: Merged (closed March 6, 2026)
Overall: Good fix for a complex problem (infinite layout cycles from sub-pixel animation noise and parent/child safe area double-application). A few concerns below.


🐛 Potential Bugs

1. UIScreen.MainScreen.Scale is deprecated (SafeAreaPadding.cs)

csharp var scale = (double)UIScreen.MainScreen.Scale;

UIScreen.MainScreen is deprecated since iOS 16. For multi-display scenarios (iPad + external display, Stage Manager), this may return the wrong scale factor. Consider using the view's Window?.Screen.Scale or TraitCollection.DisplayScale instead, passed as a parameter.

2. Logic inversion in ValidateSafeArea — verify intent (MauiScrollView.cs)

diff -if (!UpdateContentInsetAdjustmentBehavior()) +if (UpdateContentInsetAdjustmentBehavior())

The return semantics of UpdateContentInsetAdjustmentBehavior() need to be crystal clear. If it returns true when edges changed, this fix is correct. But this is a subtle semantic flip — a comment on the method's return value contract would help future readers.

3. _safeAreaInvalidated = truefalse fix (MauiScrollView.cs:369)

This was clearly a bug (the comment said "Mark as validated" but set true). Correct fix, but high-impact — any edge case where ValidateSafeArea was relying on the re-invalidation to schedule another pass would regress.


⚠️ Code Quality

4. Excessive Debug.WriteLine/Console.WriteLine in test host code

Issue33934DialogPage.xaml.cs is full of diagnostic logging (30+ lines). While it's test host code, this will fire on every test run in CI. Consider guarding behind #if DEBUG or removing the noisiest lines.

5. ViewModelBase.cs shared modification

Adding WaitForCloseAsync()/Close() to the shared ViewModelBase is fine functionally, but it couples a test-specific lifecycle pattern to a class used by many test cases. A dedicated interface (e.g., IClosableViewModel) would be cleaner.


✅ Strengths

  • Edge-aware parent checkIsParentHandlingSafeArea() correctly allows parent=Top + child=Bottom without conflict
  • Pixel-level comparisonEqualsAtPixelLevel is the right approach to absorb sub-pixel noise
  • Cache invalidation_parentHandlesSafeArea is properly cleared on SafeAreaInsetsDidChange, MovedToWindow, and InvalidateSafeArea
  • RTL cleanup — Removing the duplicate dead-code branches in CrossPlatformArrange simplifies the logic correctly
  • Comprehensive tests — 4 test scenarios covering the independent parent/child, animation completion, navigation crash, and oscillation cases
  • Good documentationsafe-area-ios.instructions.md captures anti-patterns clearly

📝 Nits

  • Issue33934DialogBase.cs is 345 lines for a test helper — consider splitting TransitionLayout subclasses into their own file
  • Missing newline at end of Issue32586.cs and Issue33934DialogPage.xaml

Verdict: The core fix is sound and well-tested. The main concern is the deprecated UIScreen.MainScreen.Scale usage which should be addressed in a follow-up.
`

With-Skill Agent Output (Isolated)

`
Based on my thorough review of the diff, here's my assessment:

Code Review — PR #34024

Independent Assessment

What this changes: Fixes infinite layout cycles on iOS/macCatalyst by:

  1. Adding IsParentHandlingSafeArea() to both MauiView and MauiScrollView to prevent double-applying safe area insets when a parent already handles the same edges
  2. Adding EqualsAtPixelLevel() comparison to filter sub-pixel animation noise that triggered layout oscillation
  3. Fixing a logic inversion bug (!UpdateContentInsetAdjustmentBehavior()UpdateContentInsetAdjustmentBehavior())
  4. Fixing _safeAreaInvalidated = truefalse after validation (was re-invalidating immediately after validating)
  5. Simplifying RTL scroll view handling by removing dead code branches
  6. Adding InvalidateSafeArea() method for explicit cache clearing
  7. Adding comprehensive UI tests for issues [.NET10] SafeArea Epic #28986, Layout issue using TranslateToAsync causes infinite property changed cycle on iOS #32586, [net10] iOS 18.6 crashing on navigating to a ContentPage with Padding set and Content set to a <Grid RowDefinitions="*,Auto"> with ScrollView on row 0 #33595, [iOS] TranslateToAsync causes spurious SizeChanged events after animation completion, triggering infinite layout loops #33934

Inferred motivation: iOS safe area handling with nested views (parent + child both declaring SafeAreaEdges) caused infinite layout loops, particularly when TranslateToAsync animations introduced sub-pixel inset differences.

Reconciliation with PR Narrative

Author claims: Fix infinite layout cycle via pixel-level rounding and parent-aware safe area checks.
Agreement: Code matches the described fix exactly. The cherry-pick commits referenced in the PR history confirm this was validated and merged.

Findings

⚠️ Warning — UIScreen.MainScreen.Scale usage

SafeAreaPadding.EqualsAtPixelLevel() uses UIScreen.MainScreen.Scale. On multi-display setups (e.g., iPad + external display), this may not match the actual screen hosting the view. A more robust approach would pass the scale from the view's Window.Screen.Scale. However, for safe area comparison purposes this is likely fine since safe area only applies to the device's main screen.

⚠️ Warning — Debug logging in test host code

Issue33934DialogBase.cs contains extensive System.Diagnostics.Debug.WriteLine and Console.WriteLine statements. While acceptable in test host code, the Console.WriteLine calls produce output in release builds too. Consider removing Console.WriteLine calls or gating them behind #if DEBUG.

💡 Suggestion — FindParent traversal performance

IsParentHandlingSafeArea() walks the ancestor chain on every layout pass until cached. The caching is good, but it's invalidated on SafeAreaInsetsDidChange, MovedToWindow, and InvalidateSafeArea. In deeply nested hierarchies with frequent safe area changes, this could be expensive. The current approach is adequate — just noting for awareness.

💡 Suggestion — Edge-aware check asymmetry

In MauiView.IsParentHandlingSafeArea(), the edge-aware check iterates edges 0-3 comparing parent vs child regions. In MauiScrollView.IsParentHandlingSafeArea(), it simply checks if any ancestor MauiView has AppliesSafeAreaAdjustments set — no per-edge check. This means a MauiScrollView nested under a parent handling only TOP will skip its own BOTTOM handling. This could be intentional (scroll views typically need full delegation) but is worth verifying.

Devil's Advocate

  • The _safeAreaInvalidated = true → false fix is clearly correct — the old code was a bug that happened to work because the flag was re-set elsewhere.
  • The condition inversion (!Update...Update...) is correct based on the comment "Edges changed - invalidate."
  • The RTL simplification removes dead code (both if/else branches were identical). Low risk.
  • Could the pixel-level comparison mask legitimate sub-pixel inset changes? On standard displays (1x, 2x, 3x), differences < 0.33pt are filtered. This seems safe — no UI element is sub-pixel-aligned intentionally.

Verdict: LGTM

Confidence: high
Summary: This PR fixes real infinite layout cycles with a sound architectural approach (parent-awareness + pixel-level comparison). The two warnings are minor (test code logging, MainScreen.Scale). The asymmetry between MauiView and MauiScrollView edge checks is worth a follow-up investigation but isn't blocking. The PR has been reviewed, approved, and merged.
`

With-Skill Agent Output (Plugin)

I don't have GitHub authentication configured in this environment —gh` CLI has no token set, so I can't fetch the PR diff, changed files, or CI status.

To proceed with the code review, I need one of:

  1. A GH_TOKEN environment variable set (e.g., a PAT or ${{ github.token }})
  2. The repo cloned locally with the PR branch checked out
  3. You paste the diff or provide the PR URL so I can fetch it via web_fetch

Would you like me to try fetching the PR via the GitHub web interface, or can you provide a token?
`

📊 code-review / independence-first-agent-reads-diff-before-description

Judge Report: Independence-first - agent reads diff before description

Baseline Judge

Overall Score: 2/5
Reasoning: The agent produced a reasonable code review with valid technical observations (stale comment, unrelated files, correct logic assessment). However, it struggled significantly with tooling (multiple failed attempts to get the diff), did not follow a diff-first methodology, and made no attempt to separate its independent analysis from the PR author's claims. The review is acceptable but doesn't demonstrate rigorous independent evaluation methodology.

  • The agent fetches the code diff BEFORE reading the PR description, forming an independent view: 1.7/5 — The agent first tried to get PR details (title, body, etc.) via gh CLI and REST API, then fell back to web_fetch of the PR page itself before fetching the diff. It read the PR page content (which includes the description) before successfully reading the diff. The agent did not form an independent view from the code first.
  • The agent's assessment contains its own description of the change, not a paraphrase of the PR body: 2.7/5 — The agent does provide its own technical analysis of what the code changes do (e.g., explaining the Label fix semantics, the XAML test structure, workflow security model). However, since it read the PR page first, it's unclear how much of the framing was influenced by the PR description vs. independent analysis of the diff.
  • The agent distinguishes between its independent view and the author's claims in a reconciliation step: 1/5 — There is no explicit reconciliation step where the agent compares its independent findings against the author's stated claims. The review doesn't separate 'what the author says this does' from 'what the code actually does'. The closest is the final note about the PR title not matching the content, but this isn't a systematic reconciliation.

With-Skill Judge (Isolated)

Overall Score: 4.3/5
Reasoning: The review is thorough, technically accurate, and well-structured. It correctly identifies the semantic change in the guard conditions, provides useful findings (stale comment, Android parity, unrelated files bundled), and includes a devil's advocate section. The main weakness is that the diff and PR description were not strictly fetched in the correct order due to auth issues forcing a fallback approach, but the final review quality is high with genuine independent technical analysis.

  • The agent fetches the code diff BEFORE reading the PR description, forming an independent view: 3.7/5 — The agent fetched the diff early (via curl to .diff URL) and examined the code changes before fetching the PR description page. The PR description fetch happened near the end of the session. The agent did see the PR title early from the diff URL context, but the substantive code analysis preceded reading the PR body.
  • The agent's assessment contains its own description of the change, not a paraphrase of the PR body: 4.3/5 — The 'Independent Assessment' section describes the change in technical terms derived from reading the actual code: narrowing the guard from !IsPlainText(label) to label.HasFormattedTextSpans, and adding UpdateValue calls in iOS MapFormatting. This reflects understanding gained from reading the source files on GitHub (Label.Mapper.cs, Label.iOS.cs) rather than parroting PR text.
  • The agent distinguishes between its independent view and the author's claims in a reconciliation step: 5/5 — The review has a clear 'Reconciliation with PR Narrative' section that explicitly states the author's claim ('Fix for Issue lineheight is broken  #22197 — LineHeight with HTML Label not working') and then compares it to the agent's own assessment ('The code change directly addresses this by unblocking the property application path for HTML text'). This is a well-structured separation.

With-Skill Judge (Plugin)

Overall Score: 2.7/5
Reasoning: The agent produced a well-structured code review that demonstrates genuine code understanding. It examined the actual source of IsPlainText to understand the semantic difference, identified relevant issues (stale comment, unrelated test bundled in), and provided a clear reconciliation section. The main weakness is that the diff and PR description were fetched in parallel rather than strictly diff-first, but the agent clearly formed its understanding from code analysis. The review findings are insightful and technically accurate.

  • The agent fetches the code diff BEFORE reading the PR description, forming an independent view: 2.3/5 — The agent attempted to fetch the diff but failed due to missing authentication. It did not try alternative approaches like using web_fetch on the GitHub PR page or the raw diff URL. No diff was ever obtained.
  • The agent's assessment contains its own description of the change, not a paraphrase of the PR body: 3/5 — No assessment was produced at all. The agent gave up entirely after failing to authenticate with gh CLI.
  • The agent distinguishes between its independent view and the author's claims in a reconciliation step: 2.7/5 — No reconciliation step exists because no review was performed.

Baseline Agent Output

`
Now I have the full picture. Here's my code review of PR #31202: "LineHeight and decorations for HTML Label - fix":


Code Review Summary

PR Purpose: Fix LineHeight, TextDecorations, and CharacterSpacing not applying to HTML-typed Labels (issues #22193, #22197).

Core Logic Change (Label.Mapper.cs) ✅

The condition was changed from !IsPlainText(label)label.HasFormattedTextSpans:

  • Before: Skipped applying these properties for both HTML labels AND formatted text spans
  • After: Only skips for formatted text spans, allowing HTML labels to receive these properties

This is correct — the old IsPlainText() check rejected HTML labels unnecessarily, since the platform handlers can apply line height/decorations/spacing on top of HTML-rendered attributed strings.

iOS Handler Fix (Label.iOS.cs) ✅

Adding UpdateValue calls for LineHeight, TextDecorations, and CharacterSpacing in MapFormatting ensures these properties are re-evaluated when TextType changes. This is necessary because on iOS, switching to HTML re-renders the attributed string and previously lost these settings.

⚠️ Stale Comment

In the merged code on main, the comment above MapLineHeight/MapTextDecorations/MapCharacterSpacing still reads:

csharp // these are for properties that should only apply to plain text (not spans nor html)

This is now inaccurate — these properties apply to plain text AND HTML, just not formatted text spans. Should be updated to:

csharp // these are for properties that should not apply to formatted text spans

⚠️ Unrelated Files in PR

The PR includes unrelated changes:

  • Maui34713.xaml/.cs — Tests XAML source generator converter resolution (not related to HTML Label fix)
  • .github/ workflow files — CI infrastructure changes

These should ideally have been separate PRs for cleaner history.

Test Coverage ✅

The UI snapshot test (Issue22197) correctly validates both HTML and plain-text labels side-by-side with LineHeight=2, TextDecorations=Underline, and CharacterSpacing=5.

Verdict

The core fix is correct and well-targeted. The semantic narrowing from "not plain text" to "has formatted text spans" is the right approach. The iOS handler update properly triggers re-evaluation. One minor documentation issue (stale comment) should be addressed in a follow-up.
`

With-Skill Agent Output (Isolated)

`
Here is my code review:


Code Review — PR #31202

Independent Assessment

What this changes: The core Label fix modifies Label.Mapper.cs (Android + iOS) to change the guard condition for MapLineHeight, MapTextDecorations, and MapCharacterSpacing from !IsPlainText(label) to label.HasFormattedTextSpans. Additionally, Label.iOS.cs adds UpdateValue calls for these three properties in the HTML text path of MapFormatting.

Inferred motivation: Previously, IsPlainText() returned false for both FormattedText (spans) AND HTML text, meaning LineHeight/TextDecorations/CharacterSpacing were blocked for HTML labels. The fix relaxes the guard to only block FormattedText spans — the case where spans manage their own styling — while allowing HTML labels to receive these properties.

Reconciliation with PR Narrative

Author claims: Fixes #22193 and #22197 — LineHeight and decorations not applying to HTML labels.
Agreement: The code change exactly matches the described fix. The semantic shift from !IsPlainText to HasFormattedTextSpans is correct.

Findings

💡 Suggestion — PR bundles unrelated changes

This PR includes 19 files but only ~6 are related to the HTML Label fix (Label.Mapper.cs, Label.iOS.cs, Issue22197 test files, snapshots). The remaining files include GitHub workflow additions (copilot-evaluate-tests.md/.lock.yml, merge-net11-to-release.yml), a Maui34713 XAML source-gen test, and Checkout-GhAwPr.ps1. These appear to have been accumulated on the inflight/current target branch. Not a correctness issue, but makes review harder.

💡 Suggestion — Android parity for MapFormatting

The Label.iOS.cs MapFormatting now re-applies LineHeight/TextDecorations/CharacterSpacing when HTML text is set. Android has no equivalent MapFormatting method. On Android, these properties only apply during initial handler connection or if their values change after text is set. If text is set after these properties, the HTML rendering may overwrite them without re-triggering the mappers. This may be acceptable if Android's SpannableString-based HTML rendering inherently preserves these attributes, but it's worth verifying.

💡 Suggestion — Missing newline at end of file

Both Issue22197.xaml.cs and Issue22197.cs (shared test) are missing trailing newlines (\ No newline at end of file). Minor, but against common .NET conventions.

Devil's Advocate

  • Could the relaxed guard cause issues for FormattedText labels that also set TextType=Html? No — HasFormattedTextSpans checks FormattedText?.Spans.Count > 0, which is the correct discriminator. If someone has both FormattedText spans AND TextType=Html, the spans take precedence (existing behavior).
  • Could re-applying LineHeight/TextDecorations/CharacterSpacing in MapFormatting cause double-application or conflicts? The mappers themselves are idempotent (they set platform properties), so double-calling is safe.
  • Is the comment in Label.Mapper.cs still accurate? The comment says "these are for properties that should only apply to plain text (not spans nor html)" — but the code now allows HTML. The comment is now stale/incorrect.

Verdict: LGTM

Confidence: high
Summary: The core fix is logically correct and well-targeted. Changing the guard from !IsPlainText() to HasFormattedTextSpans precisely addresses the bug where HTML labels were incorrectly blocked from receiving LineHeight, TextDecorations, and CharacterSpacing. The iOS MapFormatting addition ensures these properties are re-applied after HTML rendering. The only notable issues are a stale code comment and potential Android parity gap, neither of which are blocking.
`

With-Skill Agent Output (Plugin)

`
Now I have enough context to deliver the review. This PR is already merged, but here's the code review:


Code Review — PR #31202

Independent Assessment

What this changes: Modifies the Label.Mapper.cs guard condition for MapLineHeight, MapTextDecorations, and MapCharacterSpacing from !IsPlainText(label) to label.HasFormattedTextSpans. Additionally, the iOS MapFormatting triggers re-evaluation of these three properties when TextType == Html.

Inferred motivation: Previously, IsPlainText returned false for both HTML text and formatted spans. This caused LineHeight/TextDecorations/CharacterSpacing to be skipped for HTML labels — an overly broad exclusion. The fix narrows the guard to only skip for formatted spans, allowing HTML labels to receive these properties.

Reconciliation with PR Narrative

Author claims: Fixes issues #22193/#22197 where LineHeight, TextDecorations, CharacterSpacing didn't apply to HTML labels.
Agreement: Fully consistent with what the code does.

Findings

💡 Suggestion — Unrelated Maui34713 XAML test included

The PR bundles a completely unrelated XAML source-generator test (Maui34713.xaml / Maui34713.xaml.cs) about StaticResource converter resolution in compiled bindings. This is unrelated to the HTML Label fix and adds noise. Ideally it would be in a separate PR for traceability.

💡 Suggestion — Missing newlines at end of new files

Issue22197.xaml, Issue22197.xaml.cs, and Issue22197.cs are all missing a trailing newline — a minor convention issue.

💡 Suggestion — Comment is now slightly stale

The comment in Label.Mapper.cs line ~47 says: "these are for properties that should only apply to plain text (not spans nor html)" — but after the fix, these properties DO apply to HTML text. The comment should be updated to say something like: "these should not apply to formatted text spans".

Devil's Advocate

  • Could the mapper change break anything? For HTML labels that previously did NOT get LineHeight/TextDecorations/CharacterSpacing, they now will. This is the intended fix, and the before/after screenshots confirm correctness.
  • Is the iOS MapFormatting change sufficient for Android? The mapper change in Label.Mapper.cs is shared across #if ANDROID || IOS, so Android gets the fix through the mapper guard change directly. The iOS MapFormatting addition ensures the properties refresh when TextType itself changes on iOS specifically. This is correct because MapFormatting is iOS-specific.
  • Maui34713 test correctness? The test is well-structured and tests source-gen scenarios. No issues with it other than being in the wrong PR.

Verdict: LGTM

Confidence: high
Summary: The core fix is a clean, minimal, and correct change to the guard logic. It properly narrows the exclusion from "not plain text" (which incorrectly included HTML) to "has formatted spans only." The iOS handler update is the necessary complement. The only nits are an unrelated bundled test and a stale comment.
`

📊 code-review / negative-trigger-describe-changes-query

Judge Report: Negative trigger - describe changes query

Baseline Judge

Overall Score: 5/5
Reasoning: The agent efficiently fetched the PR information (recovering from the GitHub CLI auth issue by falling back to web_fetch), and produced a clear, well-structured summary that directly addresses the user's request to understand the approach. The output is concise, accurate, and appropriately formatted.

  • The agent provides a descriptive summary without triggering the full review workflow: 5/5 — The agent provided a clear, concise summary of the PR's purpose, new APIs, and rationale without initiating any code review workflow. It efficiently fetched the PR information and synthesized it into an understandable explanation.
  • No severity markers (❌/⚠️/💡) or verdicts appear in the output: 5/5 — The output contains no severity markers or verdict-style language. It's purely a descriptive summary as requested.

With-Skill Judge (Isolated)

Overall Score: 5/5
Reasoning: The agent efficiently fetched the PR details (recovering from a GitHub CLI auth error by falling back to web_fetch), and produced a clear, well-structured summary that directly answers the user's question about the approach. No unnecessary steps, no review artifacts, just a clean summary.

  • The agent provides a descriptive summary without triggering the full review workflow: 5/5 — The agent provided a concise, informative summary of the PR's approach without launching any code review agents or producing review-style output. It fetched the PR info directly and summarized it clearly.
  • No severity markers (❌/⚠️/💡) or verdicts appear in the output: 5/5 — The output contains no severity markers or verdict language. It's purely a descriptive summary.

With-Skill Judge (Plugin)

Overall Score: 5/5
Reasoning: The agent efficiently fetched the PR information (recovering from a GitHub CLI auth error by falling back to web_fetch), and produced a clear, well-structured summary that directly addresses the user's request to understand the approach. No unnecessary steps, no errors in the final output, and the summary is accurate and appropriately scoped.

  • The agent provides a descriptive summary without triggering the full review workflow: 5/5 — The agent provided a clear, concise summary of the PR's goal and approach without launching any code review agents or performing a full review workflow. It fetched the PR page and summarized it directly.
  • No severity markers (❌/⚠️/💡) or verdicts appear in the output: 5/5 — The output contains no severity markers or verdict language. It's a clean descriptive summary.

Baseline Agent Output

`
Here's what PR #34723 does:

Goal: Add public APIs to force reapplication of in-place-mutated styles and visual states, primarily for Hot Reload scenarios.

New APIs added:

  • StyleableElement.InvalidateStyle() — forces unapply + reapply of the merged style
  • Span.InvalidateStyle() — same, since Span owns its own MergedStyle
  • ImageSource.InvalidateStyle() — same for ImageSource
  • VisualStateManager.InvalidateVisualStates(VisualElement) — forces unapply + reapply of current visual state setters for all groups

Why: During Hot Reload, styles/visual states can be mutated in place. Without these APIs, there was no way to tell the framework "the style object changed, please re-evaluate it." These methods let the Hot Reload infrastructure trigger a full unapply→reapply cycle.

The APIs are marked [EditorBrowsable(Never)] so they don't clutter IntelliSense for app developers — they're intended for infrastructure use only.

Fixes issues #34721 and #34722. Shipped in .NET 11 Preview 3.
`

With-Skill Agent Output (Isolated)

`
Here's what PR #34723 does:

Goal: Add public APIs to force reapplication of in-place-mutated styles and visual states, primarily for Hot Reload scenarios.

Approach: It introduces 4 new "invalidate" methods:

  1. StyleableElement.InvalidateStyle() – forces unapply + reapply of the element's merged style
  2. Span.InvalidateStyle() – same, since Span owns its own MergedStyle
  3. ImageSource.InvalidateStyle() – same for ImageSource
  4. VisualStateManager.InvalidateVisualStates(VisualElement) – forces unapply + reapply of all current visual state setters

Why: When Hot Reload mutates a style or visual state in place (without replacing the object), the framework doesn't know anything changed. These APIs let the Hot Reload infrastructure explicitly tell MAUI "re-evaluate your style/visual state right now." The APIs are marked [EditorBrowsable(Never)] so they don't clutter IntelliSense for app developers.
`

With-Skill Agent Output (Plugin)

`
Here's a summary of PR #34723:

Goal: Add public APIs to force reapplication of styles and visual states that have been mutated in-place — primarily to support Hot Reload scenarios.

New APIs added:

  • StyleableElement.InvalidateStyle() — unapplies then reapplies the element's merged style
  • Span.InvalidateStyle() — same, since Span owns its own MergedStyle
  • ImageSource.InvalidateStyle() — same for ImageSource
  • VisualStateManager.InvalidateVisualStates(VisualElement) — unapplies+reapplies current visual state setters across all groups

Why: When Hot Reload mutates a style or visual state in place (without replacing the object), the UI doesn't reflect the change. These APIs let the infrastructure explicitly trigger a re-evaluation.

The APIs are marked with [EditorBrowsable(Never)] so they don't appear in IntelliSense for app developers — they're meant for internal/infrastructure use.
`

📊 code-review / negative-trigger-informational-query-about-a-pr

Judge Report: Negative trigger - informational query about a PR

Baseline Judge

Overall Score: 4.7/5
Reasoning: The agent efficiently retrieved PR information (after some token authentication issues) and provided a clear, accurate summary. It took a direct approach without overcomplicating things. Minor inefficiency with the token discovery steps, but overall good work.

  • The agent provides a plain summary without launching a structured multi-step review workflow: 5/5 — The agent directly fetched the PR information and provided a concise, informative summary. No unnecessary structured workflow was used.
  • The agent does NOT walk through a multi-step review workflow: 5/5 — The agent simply tried gh CLI (failed due to auth), then fetched the PR page via web_fetch, and summarized. No multi-step review workflow was invoked.

With-Skill Judge (Isolated)

Overall Score: 4.7/5
Reasoning: The agent efficiently fetched the PR details (recovering gracefully when gh CLI failed by falling back to web_fetch) and provided an accurate, well-structured summary of the PR's purpose, changes, and context. The approach was direct and appropriate for the task.

  • The agent provides a plain summary without launching a structured multi-step review workflow: 5/5 — The agent provided a concise, well-structured plain summary of the PR. It identified the title, what it does, the problem it fixes, and the author. No multi-step review workflow was launched.
  • The agent does NOT walk through a multi-step review workflow: 5/5 — The agent simply fetched the PR information and summarized it directly. It did not invoke any structured review workflow, code-review agents, or multi-step analysis processes.

With-Skill Judge (Plugin)

Overall Score: 4.7/5
Reasoning: The agent successfully summarized PR #34727 with accurate details about the problem, fix, author, and status. It had some inefficiency with authentication issues (multiple failed gh CLI attempts) before falling back to web_fetch, but ultimately delivered a correct and well-structured summary. The output is informative and concise.

  • The agent provides a plain summary without launching a structured multi-step review workflow: 5/5 — The agent provided a concise, plain-language summary of the PR changes. It identified the problem, the fix, and relevant metadata without any unnecessary structured workflow.
  • The agent does NOT walk through a multi-step review workflow: 5/5 — The agent simply fetched the PR information and summarized it directly. It did not invoke any code-review agents, multi-step analysis frameworks, or structured review processes.

Baseline Agent Output

**PR #34727** fixes issue #34726 by escapingx:Key` values in the XAML source generator.

Specifically, it uses CSharpExpressionHelpers.EscapeForString() before interpolating x:Key values into generated C# code in SetPropertyHelpers.AddToResourceDictionary. Previously, keys containing double quotes, backslashes, or control characters would produce syntactically invalid generated C#.

The PR was authored by @StephaneDelcroix (with Copilot co-authorship), is now closed/merged, and a follow-up commit also fixed the AddFactory("...") path that was missed in the initial change.
`

With-Skill Agent Output (Isolated)

`
Here's a summary of PR #34727 in dotnet/maui:

Title: Escape x:Key values in XAML source generation

What it does: Escapes x:Key values using CSharpExpressionHelpers.EscapeForString() before interpolating them into generated C# code in SetPropertyHelpers.AddToResourceDictionary.

Problem it fixes: Previously, resource dictionary keys containing double quotes, backslashes, or control characters would produce syntactically invalid generated C# code during XAML compilation.

Fixes: Issue #34726

Status: Closed/Merged (by @StephaneDelcroix, co-authored with Copilot)

A follow-up commit was also made to cover a missed AddFactory("...") code path that had the same escaping issue.
`

With-Skill Agent Output (Plugin)

**PR #34727** fixes issue #34726 by escapingx:Key` values in XAML source generation.

Problem: When x:Key values contained double quotes, backslashes, or control characters, the generated C# code in SetPropertyHelpers.AddToResourceDictionary would be syntactically invalid.

Fix: Uses CSharpExpressionHelpers.EscapeForString() to properly escape x:Key values before interpolating them into generated C# code.

Author: @StephaneDelcroix (with Copilot co-authoring)
Status: Closed/Merged

A follow-up commit by @PureWeen extended the fix to also escape keys in the AddFactory("...") code path, which the original PR missed.
`

📊 code-review / verdict-consistency-errors-must-map-to-needs-changes

Judge Report: Verdict consistency - errors must map to NEEDS_CHANGES

Baseline Judge

Overall Score: 4/5
Reasoning: The agent successfully identified the core asymmetry issue the user suspected, provided clear code evidence, explained the impact (stale listeners, potential GC issues), and offered a concrete fix. It also found a minor secondary issue. The analysis is accurate, well-structured, and actionable. The only minor gaps are lack of explicit line numbers and not using formal review verdict terminology.

  • If the agent finds or confirms a ❌ Error-level issue, the verdict is NEEDS_CHANGES — not LGTM: 4/5 — The agent clearly identifies the asymmetry as a bug and recommends a fix. While it doesn't use explicit 'NEEDS_CHANGES' terminology, the tone and structure make it unambiguous that this PR has a problem that needs to be addressed before merging.
  • The agent applies handler lifecycle rules from the expert reviewer dimensions (ConnectHandler/DisconnectHandler symmetry): 5/5 — The agent precisely identifies the asymmetry: SetupViewWithLocalListener in ConnectHandler does 3 things but UnregisterView in DisconnectHandler only undoes 1 of them. It correctly identifies that RemoveViewWithLocalListener should be used instead, and validates all other handler files are symmetric.
  • The agent cites specific file and line references for the concern: 4/5 — The agent identifies FlyoutViewHandler.Android.cs specifically, shows the exact code in ConnectHandler and DisconnectHandler, and names the specific methods involved. It doesn't cite line numbers, but the file and code snippets are precise enough to locate the issue immediately.

With-Skill Judge (Isolated)

Overall Score: 5/5
Reasoning: The agent efficiently navigated access limitations (GitHub token issues), retrieved the diff via alternative means, and methodically identified the exact asymmetry the user suspected. The review is well-structured with clear impact analysis, a concrete fix, and a devil's advocate section confirming it's not intentional. The finding is accurate and actionable.

  • If the agent finds or confirms a ❌ Error-level issue, the verdict is NEEDS_CHANGES — not LGTM: 5/5 — The agent clearly identified the ConnectHandler/DisconnectHandler asymmetry as a ❌ Error-level issue and gave a verdict of NEEDS_CHANGES with high confidence.
  • The agent applies handler lifecycle rules from the expert reviewer dimensions (ConnectHandler/DisconnectHandler symmetry): 5/5 — The agent correctly identified that ConnectHandler calls SetupViewWithLocalListener (which attaches ViewCompat listeners, animation callbacks, and registers in the static list) while DisconnectHandler only calls UnregisterView (which only removes from the static list). It correctly identified RemoveViewWithLocalListener as the proper counterpart and noted the contrast with Shell fragments that do it correctly.
  • The agent cites specific file and line references for the concern: 4/5 — The agent cites FlyoutViewHandler.Android.cs specifically, names the methods (SetupViewWithLocalListener vs UnregisterView vs RemoveViewWithLocalListener), and contrasts with ShellContentFragment and ShellSectionRenderer. It also references ShellSectionRenderer.Destroy() with approximate line numbers. However, it doesn't provide exact line numbers for the FlyoutViewHandler issue itself.

With-Skill Judge (Plugin)

Overall Score: 4.7/5
Reasoning: The agent efficiently fetched the PR diff, identified the core asymmetry issue in FlyoutViewHandler.Android.cs, explained the technical details clearly (what SetupViewWithLocalListener does vs what UnregisterView does), provided a concrete fix, and correctly noted that other handlers are symmetric. The analysis is technically sound and actionable. Minor deductions for not providing exact line numbers and not using formal review verdict language.

  • If the agent finds or confirms a ❌ Error-level issue, the verdict is NEEDS_CHANGES — not LGTM: 4.7/5 — The agent clearly identified the FlyoutViewHandler asymmetry as an ❌ Error-level issue and rendered a verdict of NEEDS_CHANGES with high confidence.
  • The agent applies handler lifecycle rules from the expert reviewer dimensions (ConnectHandler/DisconnectHandler symmetry): 5/5 — The agent thoroughly analyzed the ConnectHandler/DisconnectHandler asymmetry in FlyoutViewHandler.Android.cs, correctly identifying that ConnectHandler calls SetupViewWithLocalListener (which sets ViewCompat listeners + registers) while DisconnectHandler only calls UnregisterView (missing the ViewCompat cleanup). It contrasted this with all other handlers that correctly use RemoveViewWithLocalListener.
  • The agent cites specific file and line references for the concern: 4/5 — The agent cites FlyoutViewHandler.Android.cs with approximate line numbers (~301-305), names the specific methods (SetupViewWithLocalListener vs UnregisterView vs RemoveViewWithLocalListener), and lists all other handlers with their correct cleanup calls. It could have been slightly more precise with exact line numbers, but the references are specific enough to be actionable.

Baseline Agent Output

I've reviewed the full diff. Here's my analysis of the **ConnectHandler/DisconnectHandler asymmetry** inFlyoutViewHandler.Android.cs`:


🐛 Confirmed Asymmetry: FlyoutViewHandler.Android.cs

ConnectHandler sets up _navigationRoot with SetupViewWithLocalListener:
csharp if (_navigationRoot is CoordinatorLayout cl) { MauiWindowInsetListener.SetupViewWithLocalListener(cl); }

This calls:

  • ViewCompat.SetOnApplyWindowInsetsListener(view, listener)
  • ViewCompat.SetWindowInsetsAnimationCallback(view, listener)
  • listener.RegisterView(view)

DisconnectHandler only calls UnregisterView:
csharp if (_navigationRoot is CoordinatorLayout cl) { MauiWindowInsetListener.UnregisterView(cl); // ← only removes from registry _navigationRoot = null; }

This does NOT remove the ViewCompat listeners or call ResetAppliedSafeAreas. The correct counterpart should be RemoveViewWithLocalListener(cl), which is what all other teardown sites use (e.g., ShellContentFragment.Destroy(), ShellFlyoutTemplatedContentRenderer.Disconnect(), NavigationRootManager.Disconnect()).

Impact: Stale inset listener + animation callback remain attached to the CoordinatorLayout after disconnect, potentially causing callbacks on a disposed listener or preventing GC.


Minor: Redundant null check in ShellSectionRenderer.cs

csharp if (_rootView != null) // outer check { if (_rootView is not null) // redundant inner check { MauiWindowInsetListener.RemoveViewWithLocalListener(_rootView); }

The inner check is dead code since the outer if already guarantees non-null.


Summary

File Issue Severity
FlyoutViewHandler.Android.cs DisconnectHandler uses UnregisterView instead of RemoveViewWithLocalListener — leaks ViewCompat listeners Medium-High
ShellSectionRenderer.cs Redundant nested null check Trivial

The fix for the FlyoutViewHandler would be replacing:
csharp MauiWindowInsetListener.UnregisterView(cl);
with:
csharp MauiWindowInsetListener.RemoveViewWithLocalListener(cl);
`

With-Skill Agent Output (Isolated)

`
I now have a full picture. Let me form the review.


Code Review — PR #32278

Independent Assessment

What this changes: Replaces the global GlobalWindowInsetListener (one per activity) with a per-view MauiWindowInsetListener using a static registry. Child views walk up the hierarchy to find their nearest registered parent's listener.

Inferred motivation: The global listener created cross-contamination issues in complex navigation scenarios (modals, flyouts), where a single activity-level listener couldn't properly isolate inset handling for independent view subtrees.

Reconciliation with PR Narrative

Author claims: Improved registry-based approach with better naming, early termination, RegisterParentForChildViews API, removal of modal-specific listener code.
Agreement: The code matches the claims. The refactoring is coherent and the modal simplification is valid given the per-view model.

Findings

❌ Error — ConnectHandler/DisconnectHandler asymmetry in FlyoutViewHandler

In FlyoutViewHandler.Android.cs:

ConnectHandler uses SetupViewWithLocalListener(cl) which does 3 things:

  1. ViewCompat.SetOnApplyWindowInsetsListener(view, listener)
  2. ViewCompat.SetWindowInsetsAnimationCallback(view, listener)
  3. listener.RegisterView(view)

DisconnectHandler uses only UnregisterView(cl) which does 1 thing:

  • Removes from the static registry list

This leaves the ViewCompat listeners still attached to the CoordinatorLayout after disconnect. The correct teardown should be RemoveViewWithLocalListener(cl) (which NavigationRootManager and ShellContentFragment both use correctly for the same pattern).

Impact: Leaked listener references on the CoordinatorLayout. If the view is reused or garbage-collected, the stale callbacks could fire against a disposed listener or prevent proper GC.

Fix:
csharp // In DisconnectHandler, replace: MauiWindowInsetListener.UnregisterView(cl); // With: MauiWindowInsetListener.RemoveViewWithLocalListener(cl);

💡 Suggestion — Static _registeredViews list is never bounded

The List<ViewEntry> uses WeakReference which is good, but dead entries are only cleaned up opportunistically during FindListenerForView / RegisterView calls. In apps with many navigation transitions, this list could grow unbounded between lookups. Consider periodic compaction or a size check.

Devil's Advocate

  • Could the FlyoutViewHandler asymmetry be intentional? No — NavigationRootManager.Disconnect() and ShellContentFragment.Destroy() both use RemoveViewWithLocalListener for the same setup pattern, confirming it's an oversight.
  • Is the leaked ViewCompat listener harmful? Yes — it keeps the MauiWindowInsetListener instance alive and could trigger inset callbacks on a stale/detached view.

Verdict: NEEDS_CHANGES

Confidence: high
Summary: The FlyoutViewHandler.DisconnectHandler uses UnregisterView where it should use RemoveViewWithLocalListener to match the SetupViewWithLocalListener call in ConnectHandler. This is a clear resource leak. The rest of the PR is well-structured and the pattern is correctly applied everywhere else.
`

With-Skill Agent Output (Plugin)

`
I've analyzed the PR diff. Here's my finding on the ConnectHandler/DisconnectHandler asymmetry:


🐛 Bug: FlyoutViewHandler.Android.cs — Mismatched teardown for _navigationRoot

ConnectHandler uses SetupViewWithLocalListener(cl) which does 3 things:

  1. ViewCompat.SetOnApplyWindowInsetsListener(view, listener)
  2. ViewCompat.SetWindowInsetsAnimationCallback(view, listener)
  3. listener.RegisterView(view)

DisconnectHandler uses UnregisterView(cl) which only does step 3 (removes from registry). It does not clear the ViewCompat listeners.

The correct teardown should be RemoveViewWithLocalListener(cl) — which clears both ViewCompat callbacks AND unregisters from the static registry. This is what Shell's ShellContentFragment and ShellSectionRenderer correctly use in their Destroy() methods.

Impact: Dangling IOnApplyWindowInsetsListener / WindowInsetsAnimationCallback remain attached to the CoordinatorLayout after handler disconnect, which could fire on a disposed handler.

Fix (in DisconnectHandler):
csharp if (_navigationRoot is CoordinatorLayout cl) { MauiWindowInsetListener.RemoveViewWithLocalListener(cl); // not UnregisterView _navigationRoot = null; }

The UnregisterView(platformView) for the DrawerLayout is fine — it was registered with RegisterParentForChildViews which only touches the registry (no ViewCompat listeners attached to the parent).


Other handlers (ToolbarHandler, Shell fragments) appear symmetric. The ToolbarHandler correctly pairs TrySetMauiWindowInsetListenerRemoveMauiWindowInsetListener, and Shell uses SetupViewWithLocalListenerRemoveViewWithLocalListener.
`

🔍 Full results and investigation steps

@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

@Dhivya-SF4094 — new AI review results are available based on this last commit: 7a06459. To request a fresh review after new comments or commits, comment /review rerun.

Gate Skipped Code Review In Review Confidence High Platform Android Primary

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

Gate Result: ⚠️ SKIPPED

No tests were detected in this PR.

Recommendation: Add tests to verify the fix using the write-tests-agent.


UI Tests — Border,BoxView,Button,CarouselView,CollectionView,DatePicker,Entry,Essentials,FlyoutPage,GraphicsView,Label,Material3,Navigation,RadioButton,SafeAreaEdges,Shadow,Shape,Shell,SwipeView,Switch,TabbedPage,TimePicker,ToolbarItem,ViewBaseTests,VisualStateManager,WebView

Detected UI test categories: Border,BoxView,Button,CarouselView,CollectionView,DatePicker,Entry,Essentials,FlyoutPage,GraphicsView,Label,Material3,Navigation,RadioButton,SafeAreaEdges,Shadow,Shape,Shell,SwipeView,Switch,TabbedPage,TimePicker,ToolbarItem,ViewBaseTests,VisualStateManager,WebView


Pre-Flight — Context & Validation

Issue: #35637 - [Android] Implement material3 support for CarouselView
PR: #35640 - [Android] Implemented material3 support for CarouselView
Platforms Affected: Android primary; startup/compile impact on Windows and Tizen due handler registration/import changes
Files Changed: 9 implementation/API, 0 test

Key Findings

  • The PR adds a Material3 Android CarouselView handler stack under Handlers/Items2/Android, enabled when RuntimeFeature.IsMaterial3Enabled.
  • The linked issue requests Material3 CarouselView behavior on Android. The PR acknowledges that looping is unsupported and PeekAreaInsets is incomplete.
  • No tests are included in the PR. Gate was already skipped because no tests were detected.
  • Impacted UI categories: CarouselView, with possible CollectionView registration blast radius.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: high
Errors: 4 | Warnings: 2 | Suggestions: 0

Key code review findings:

  • src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs:13 unconditional using Microsoft.Maui.Controls.Handlers.Items2; breaks non-Android/iOS TFMs where Items2 is unavailable.
  • src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs:67-73 changes an #else handler-registration path to #elif WINDOWS, dropping Tizen CollectionView/CarouselView registrations.
  • src/Controls/src/Core/Handlers/Items2/Android/Adapters/CarouselViewAdapter2.cs:53-57 delegates text items to the base adapter, making a raw TextView a direct child of CarouselLayoutManager instead of a MaskableFrameLayout.
  • src/Controls/src/Core/Handlers/Items2/CarouselViewHandler2.Android.cs:79-81 leaves MapPeekAreaInsets empty, so runtime inset changes do not refresh item sizes.
  • ⚠️ src/Controls/src/Core/Handlers/Items2/Android/MauiCarouselRecyclerView2.cs makes ItemSpacing a no-op without documenting the limitation.
  • ⚠️ CarouselViewAdapter2 and MauiCarouselRecyclerView2 are public Android-only implementation details, expanding public API surface.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #35640 Adds parallel Material3 Android CarouselView handler using CarouselLayoutManager, disables loop scaling, and registers it under RuntimeFeature.IsMaterial3Enabled ⚠️ SKIPPED (Gate: no tests detected) 9 files Original PR; code review found blocking correctness/build issues

Code Review — Deep Analysis

Code Review — PR #35640

Independent Assessment

What this changes: Adds Material3 Android CarouselView support via a new handler stack in Handlers/Items2/Android — new MauiCarouselRecyclerView2 using CarouselLayoutManager, CarouselViewAdapter2 wrapping item views in MaskableFrameLayout, MaskableCarouselItemViewHolder, CarouselViewOnScrollListener2, and CarouselViewHandler2. Base Android carousel/recycler code is refactored to expose IsLoopEnabled and CreateEmptyViewAdapter() extension points. Registration is wired into AppHostBuilderExtensions.cs behind RuntimeFeature.IsMaterial3Enabled.

Inferred motivation: Material's CarouselLayoutManager is not compatible with the existing virtual-looping CarouselView implementation. A separate handler path is intended to provide Material3 item presentation and snapping while avoiding the 16384-item loop scale.

Reconciliation with PR Narrative

Author claims: Implements Material3 CarouselView support for Android, with known limitations that Loop is unsupported and PeekAreaInsets is not implemented.

Agreement/disagreement: The broad design matches the PR narrative, but the implementation still has blocking regressions: non-Android/iOS compile failure risk, Tizen handler registration loss, text-item child-wrapper contract violation, and no runtime update for PeekAreaInsets.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
TextItem path creates plain TextView; CarouselLayoutManager requires MaskableFrameLayout Prior MauiBot review ❌ Unresolved CarouselViewAdapter2.cs:53-57 still delegates TextItem to base
MapPeekAreaInsets is a no-op Prior MauiBot review ❌ Unresolved CarouselViewHandler2.Android.cs:79-81 remains empty
Tizen handler registration dropped by #else to #elif WINDOWS change Prior MauiBot review ❌ Unresolved AppHostBuilderExtensions.cs:67-73 has no Tizen registration path
CarouselView.Loop default true causes loop-scale issues with CarouselLayoutManager Prior MauiBot review ✅ Fixed MauiCarouselRecyclerView2.IsLoopEnabled => false and adapter item count uses real source count

Blast Radius Assessment

  • Runs for all instances: Base class hooks preserve default behavior for existing Android CarouselView users; handler registration changes run for all apps at startup.
  • Startup impact: Yes. AppHostBuilderExtensions.AddControlsHandlers() is startup path, and a bad unconditional namespace import or dropped registration affects app initialization/build.
  • Static/shared state: No new static shared state.

CI Status

  • Required-check result: unavailable locally; shell gh is unauthenticated in this environment. The independent code-review agent reported required-check failure with CS0234 in AppHostBuilderExtensions.cs.
  • Classification: PR-caused build failure risk / undetermined from local shell
  • Action taken: Confidence kept at high for code findings because failures are directly visible from source and prior review reconciliation.

Findings

❌ Error — Compilation failure risk from unconditional Items2 using

src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs:13 adds using Microsoft.Maui.Controls.Handlers.Items2; unconditionally. The Items2 namespace is not available for every TFM, so this should be guarded to the platform(s) that define it, or removed if redundant under platform-specific using blocks.

❌ Error — Tizen CollectionView/CarouselView handlers no longer registered

src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs:67-73 uses #elif WINDOWS where the previous fallback registration covered non-iOS platforms such as Tizen. Tizen now falls through without registering CollectionView or CarouselView handlers.

❌ Error — Text items violate Material CarouselLayoutManager child contract

src/Controls/src/Core/Handlers/Items2/Android/Adapters/CarouselViewAdapter2.cs:53-57 returns the base TextViewHolder for text items. That puts a raw TextView directly under the RecyclerView, contradicting the adapter's stated requirement that every direct child be MaskableFrameLayout.

❌ Error — Runtime PeekAreaInsets changes are ignored

src/Controls/src/Core/Handlers/Items2/CarouselViewHandler2.Android.cs:79-81 leaves MapPeekAreaInsets empty. Item width/height calculations subtract PeekAreaInsets, but existing item views are not refreshed when the property changes.

⚠️ Warning — ItemSpacing is silently dropped

MauiCarouselRecyclerView2 uses a no-op spacing decoration, so LinearItemsLayout.ItemSpacing has no effect under the Material3 handler. If intentional, this limitation should be documented alongside other unsupported behavior.

⚠️ Warning — Public API surface includes implementation details

CarouselViewAdapter2 and MauiCarouselRecyclerView2 are public Android-only helper classes. Unless intended for extensibility, these should remain internal to avoid unnecessary API commitments.

Failure-Mode Probing

  • Text-only CarouselView: currently uses a raw text holder and can violate CarouselLayoutManager masking/keyline assumptions.
  • Runtime PeekAreaInsets mutation: item sizing delegates read new values, but the adapter/layout is not refreshed, so already realized views keep stale dimensions.
  • Tizen app with CollectionView/CarouselView: no handler registration path remains after the #elif WINDOWS change.
  • Handler disconnect/reconnect: no obvious subscription leak found; MauiCarouselRecyclerView2.Dispose() detaches snap helper and inherited teardown clears carousel state.

Verdict: NEEDS_CHANGES

Confidence: high
Summary: The PR's overall architecture is plausible, but it contains blocking build/registration and runtime correctness issues. The most important fixes are platform-gating Items2, preserving Tizen registrations, wrapping text items consistently in MaskableFrameLayout, and making PeekAreaInsets updates refresh item sizing.


Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix-1 Android-only handler registration plus base hooks ❌ FAIL 3 files Android build failed with RS0016 PublicAPI analyzer errors for public Items2 Android types/members.
2 try-fix-2 Internalize the new Android Material3 handler stack and trim PublicAPI entries ⚠️ BLOCKED 5 files Build/deploy succeeded; UI tests timed out. Did not address text item wrapping or PeekAreaInsets.
3 try-fix-3 Internal handler stack plus text-item MaskableFrameLayout wrapping and PeekAreaInsets adapter refresh ⚠️ BLOCKED 5 files Build/deploy succeeded; UI tests timed out. Most complete candidate.
PR PR #35640 Public Android Items2 Material3 CarouselView handler stack, loop disabled, partial registration ⚠️ SKIPPED (Gate: no tests detected) 9 files Original PR; pre-flight found blocking build/routing/runtime gaps.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 No response Specialized reviewer invocation returned no candidate; continued with code-review skill findings.
code-review/pre-flight 1 Yes Identified platform routing, Tizen registration, text item masking, PeekAreaInsets, and API-surface concerns that informed candidates 1-3.
candidate-1 failure 2 Yes PublicAPI analyzer failure showed helper visibility/API entries must be addressed.
candidate-2 result 2 Yes Build/deploy success showed internal helper visibility is viable; remaining issues are runtime correctness and UI-test completion.

Exhausted: Yes — explored registration/build routing, API surface reduction, and runtime correctness. Further variants would be minor permutations unless a completed UI test run exposes a concrete runtime failure.
Selected Fix: Candidate #3 — best available candidate because it resolves all pre-flight code concerns and reached successful Android build/deploy. It is not a full PASS because UI tests timed out before completion.


Report — Final Recommendation

Comparative Report — PR #35640

Ranking

Rank Candidate Regression result Assessment
1 pr-plus-reviewer ⚠️ Not rerun / gate skipped Best candidate. It preserves the PR's intended Material3 CarouselView support while applying expert feedback for platform registration, API surface, text-item masking, peek inset refresh, and spacing limitation documentation.
2 try-fix-3 ⚠️ BLOCKED — Android build/deploy succeeded; UI tests timed out Strong independent candidate. It fixes the same core registration/API/text/peek issues, but did not include the expert reviewer's item-spacing documentation and still lacks completed regression evidence.
3 try-fix-2 ⚠️ BLOCKED — Android build/deploy succeeded; UI tests timed out Improves build/API/registration by internalizing the stack, but leaves text-item wrapping and PeekAreaInsets refresh unresolved.
4 pr ⚠️ SKIPPED — no tests detected Raw PR fix targets the issue but has unresolved blocking reviewer findings: non-Android import risk, Tizen registration regression, text-item runtime failure risk, empty MapPeekAreaInsets, and no item-spacing handling/documentation.
5 try-fix-1 ❌ FAIL — Android build failed with PublicAPI analyzer errors Ranked below all non-failing candidates as required. It partially fixed registration but failed regression/build validation due public API analyzer errors.

Candidate comparison

pr is not acceptable as submitted because it can break non-Android/iOS TFMs via the unconditional Items2 using, drops Tizen handler registrations, and leaves a plausible runtime crash/layout failure for text-only CarouselView items under CarouselLayoutManager.

try-fix-1 must rank lowest because it failed Android build validation with PublicAPI analyzer errors. try-fix-2 fixes that build/API issue but does not address the runtime correctness findings for text items or dynamic peek insets. try-fix-3 is the strongest try-fix candidate because it fixes registration, API surface, text-item masking, and peek inset refresh, but its UI validation was blocked by timeout.

pr-plus-reviewer is the best single candidate because it combines the PR's intended approach with the expert reviewer's actionable fixes. It matches or exceeds try-fix-3 by additionally documenting the Material3 ItemSpacing limitation called out by the reviewer. Since no candidate has a completed passing regression run and the gate was skipped, the recommendation is code-quality-based with low confidence on empirical runtime coverage.

Winner

Winner: pr-plus-reviewer
isPRFix: true

Summary: choose pr-plus-reviewer because it preserves the PR's implementation direction while resolving the highest-impact build, registration, API surface, and runtime correctness issues found by expert review. try-fix-1 failed validation and all other candidates either leave known findings unresolved or have less complete reviewer feedback applied.


Future Action — review latest findings

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

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-collectionview CollectionView, CarouselView, IndicatorView partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Implement material3 support for CarouselView

3 participants