Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8405072
Fix dotnet-maui skill regressions and sharpen decisiveness (#895)
kubaflo Jul 26, 2026
b07628c
maui-collectionview: fix eval-identified loss causes
kubaflo Jul 26, 2026
8a200fa
Fix markdownlint MD012 (duplicate blank line)
kubaflo Jul 26, 2026
ad057a0
Apply multi-model review feedback (4-model consensus round 1)
kubaflo Jul 26, 2026
746679a
Apply multi-model review feedback (round 2) + Copilot comment
kubaflo Jul 26, 2026
0b86a41
Apply multi-model review feedback (round 3)
kubaflo Jul 26, 2026
df0806a
Resolve Copilot review comment: conflicting XC0022 guidance
kubaflo Jul 26, 2026
9c02b41
Fix the 3 skills that failed the official evaluation
kubaflo Jul 27, 2026
5812d5a
Address review feedback: guard the regression, pin versions, fix cros…
kubaflo Jul 28, 2026
1db8796
maui-theming: recommend a single-source palette instead of inline lit…
kubaflo Jul 28, 2026
4d61dd9
Merge remote-tracking branch 'upstream/main' into kubaflo/dotnet-maui…
kubaflo Jul 28, 2026
714b597
maui-theming: never Clear() MergedDictionaries when swapping a theme
kubaflo Jul 28, 2026
6d1201c
Eliminate the last two tying scenarios in shell-navigation and DI
kubaflo Jul 28, 2026
cf62633
Add regression-guard scenarios to theming, shell-navigation and DI evals
kubaflo Jul 29, 2026
fcb7e56
Fix SetAppTheme example: wrong BindableProperty owner (Copilot review)
kubaflo Jul 29, 2026
a1eec66
Merge branch 'main' into kubaflo/dotnet-maui-skills-eval-fixes
kubaflo Jul 29, 2026
b02c5a2
Revert accidental repo-wide runs: 1 -> 3 change in the experiment config
kubaflo Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions plugins/dotnet-maui/skills/maui-app-lifecycle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,18 @@ protected override void OnDestroying()

### iOS / Mac Catalyst

| Window Event | UIKit Callback |
|---|---|
| Created | `WillFinishLaunching` / `SceneWillConnect` |
| Activated | `DidBecomeActive` |
| Deactivated | `WillResignActive` |
| Stopped | `DidEnterBackground` |
| Resumed | `WillEnterForeground` |
| Destroying | `WillTerminate` |
| Window Event | UIKit Callback | `AddiOS` builder method |
|---|---|---|
| Created | `WillFinishLaunching` / `SceneWillConnect` | `.WillFinishLaunching()` / `.SceneWillConnect()` |
| Activated | `DidBecomeActive` | `.OnActivated()` |
| Deactivated | `WillResignActive` | `.OnResignActivation()` |
| Stopped | `DidEnterBackground` | `.DidEnterBackground()` |
| Resumed | `WillEnterForeground` | `.WillEnterForeground()` |
| Destroying | `WillTerminate` | `.WillTerminate()` |

> ⚠️ The UIKit selector names and the `AddiOS` builder method names differ for
> activation. There is **no** `.DidBecomeActive()` or `.WillResignActive()` builder
> method — use `.OnActivated()` and `.OnResignActivation()` or the code will not compile.

### Windows (WinUI)

Expand Down Expand Up @@ -192,8 +196,8 @@ builder.ConfigureLifecycleEvents(events =>
.OnDestroy(activity => Debug.WriteLine("Android OnDestroy")));
#elif IOS || MACCATALYST
events.AddiOS(ios => ios
.DidBecomeActive(app => Debug.WriteLine("iOS DidBecomeActive"))
.WillResignActive(app => Debug.WriteLine("iOS WillResignActive"))
.OnActivated(app => Debug.WriteLine("iOS OnActivated"))
.OnResignActivation(app => Debug.WriteLine("iOS OnResignActivation"))
.DidEnterBackground(app => Debug.WriteLine("iOS DidEnterBackground"))
.WillEnterForeground(app => Debug.WriteLine("iOS WillEnterForeground")));
#elif WINDOWS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,18 @@ protected override Window CreateWindow(IActivationState? activationState)

### iOS / Mac Catalyst

| Window event | UIKit callback |
|---|---|
| Created | `WillFinishLaunching` / `SceneWillConnect` |
| Activated | `DidBecomeActive` |
| Deactivated | `WillResignActive` |
| Stopped | `DidEnterBackground` |
| Resumed | `WillEnterForeground` |
| Destroying | `WillTerminate` |
| Window event | UIKit callback | `AddiOS` builder method |
|---|---|---|
| Created | `WillFinishLaunching` / `SceneWillConnect` | `.WillFinishLaunching()` / `.SceneWillConnect()` |
| Activated | `DidBecomeActive` | `.OnActivated()` |
| Deactivated | `WillResignActive` | `.OnResignActivation()` |
| Stopped | `DidEnterBackground` | `.DidEnterBackground()` |
| Resumed | `WillEnterForeground` | `.WillEnterForeground()` |
| Destroying | `WillTerminate` | `.WillTerminate()` |

> ⚠️ The activation builder methods are **not** named after the UIKit selectors.
> `.DidBecomeActive()` and `.WillResignActive()` do not exist on `IiOSLifecycleBuilder` —
> use `.OnActivated()` and `.OnResignActivation()`.

### Windows (WinUI)

Expand Down Expand Up @@ -129,8 +133,8 @@ builder.ConfigureLifecycleEvents(events =>
events.AddiOS(ios => ios
.WillFinishLaunching((app, options) => { Log("iOS WillFinishLaunching"); return true; })
.SceneWillConnect((scene, session, options) => Log("iOS SceneWillConnect"))
.DidBecomeActive(app => Log("iOS DidBecomeActive"))
.WillResignActive(app => Log("iOS WillResignActive"))
.OnActivated(app => Log("iOS OnActivated"))
.OnResignActivation(app => Log("iOS OnResignActivation"))
.DidEnterBackground(app => Log("iOS DidEnterBackground"))
.WillTerminate(app => Log("iOS WillTerminate")));
#elif WINDOWS
Expand Down
110 changes: 97 additions & 13 deletions plugins/dotnet-maui/skills/maui-collectionview/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ description: >
displaying scrollable data, replacing ListView.
DO NOT USE FOR: simple static layouts without scrollable data (use Grid or
StackLayout), map pin lists (use Microsoft.Maui.Controls.Maps), table-based
data entry forms, or non-MAUI list controls.
data entry forms, non-MAUI list controls, CarouselView or BindableLayout
questions, platform-specific handler or renderer customization, diagnosing
CollectionView bugs in the MAUI framework itself, or general MVVM/binding
questions that merely happen to mention a list (use maui-data-binding).
license: MIT
---

Expand All @@ -34,6 +37,37 @@ license: MIT
- Table-based data entry forms — use standard form controls
- Simple text-only lists with no interaction — consider `BindableLayout` on a `StackLayout`

## Scope Control — Answer Only What Was Asked

This skill is a **reference you consult**, not a checklist you apply. Most requests
need one or two sections from it. Pulling in the rest makes the answer worse.

**Stop conditions — do NOT act when:**

- **The user asked a narrow question.** Answer that question only. Do not append
grouping, swipe actions, empty views, snap points, or performance tips that
were not asked about.
- **The user's existing code already works.** Do not rewrite working markup to
match the examples here. Point out a concrete defect or say nothing.
- **The change is stylistic.** Renaming, reordering attributes, or restructuring
a template that already behaves correctly is churn, not a fix.
- **The control isn't `CollectionView`.** `CarouselView`, `BindableLayout`, and
`ListView`-in-maintenance code have different rules. Do not migrate a working
`ListView` to `CollectionView` unless the user asked to migrate.
- **The problem is really a binding, DI, or navigation problem** that happens to
involve a list — defer to `maui-data-binding`, `maui-dependency-injection`, or
`maui-shell-navigation`.

**Everything below is a suggestion the agent may decline.** Only three rules are
non-negotiable, because violating them produces code that does not work:

1. Never use `ViewCell` as a `DataTemplate` root in `CollectionView`.
2. Use `ObservableCollection<T>` when the list mutates after first render.
3. Mutate the bound collection on the UI thread.

Everything else — sizing strategy, snap points, header/footer, empty views — is
optional and should be offered only when it addresses the user's actual problem.

## Inputs

- A data source (typically `ObservableCollection<T>`) bound to `ItemsSource`
Expand All @@ -42,19 +76,31 @@ license: MIT

## Basic Setup

A complete, copy-pasteable page. Note the `xmlns:models` declaration — every
`x:DataType="models:…"` in this skill assumes it:

```xml
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<HorizontalStackLayout Padding="8" Spacing="8">
<Image Source="{Binding Icon}" WidthRequest="40" HeightRequest="40" />
<Label Text="{Binding Name}" VerticalOptions="Center" />
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:MyApp.Models"
x:Class="MyApp.ItemsPage">
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<HorizontalStackLayout Padding="8" Spacing="8">
<Image Source="{Binding Icon}" WidthRequest="40" HeightRequest="40" />
<Label Text="{Binding Name}" VerticalOptions="Center" />
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
```

Later snippets show only the `CollectionView` element. When you hand a snippet to a
user, include the matching `xmlns:` declaration for any prefix it uses, or the XAML
will not compile.

**Key rules:**

- Bind `ItemsSource` to an `ObservableCollection<T>` so the UI updates on add/remove.
Expand Down Expand Up @@ -309,12 +355,36 @@ collectionView.ScrollTo(item: myItem, position: ScrollToPosition.MakeVisible, an
- `SnapPointsType`: `None`, `Mandatory`, `MandatorySingle`
- `SnapPointsAlignment`: `Start`, `Center`, `End`

## Migrating from ListView

`ListView` still exists and is still supported — it is not removed or obsolete. But
`CollectionView` is the recommended control for new work: better performance, no
`ViewCell` requirement, and flexible layouts. Migrate when the user asks, or when
they hit a `ListView` limitation; don't churn working `ListView` code otherwise.

| `ListView` | `CollectionView` equivalent |
|---|---|
| `ViewCell` template root | Any `View`/`Layout` root — **`ViewCell` is not supported** |
| `ItemSelected` / `ItemTapped` events | `SelectionChanged` event, or `SelectionChangedCommand` |
| `IsPullToRefreshEnabled` + `Refreshing` | Wrap the `CollectionView` in a `RefreshView` |
| `IsGroupingEnabled` | `IsGrouped` |
| `HasUnevenRows="True"` | Default `ItemSizingStrategy="MeasureAllItems"` |
| `HasUnevenRows="False"` + `RowHeight` | `ItemSizingStrategy="MeasureFirstItem"` (uniform items) |
| `SeparatorVisibility` / `SeparatorColor` | **No equivalent** — draw a `BoxView`/`Border` in the item template |

The missing separator API is the most common migration surprise: `CollectionView`
has no built-in separators, so add one to the template yourself.

## Performance Tips

- **Use `MeasureFirstItem`** for uniform item sizes — significantly faster than `MeasureAllItems`:
Apply these only when the user reports a performance problem or explicitly asks
about performance — they are not a default checklist.

- **Use `MeasureFirstItem`** for uniform item sizes — significantly faster than `MeasureAllItems`. Set it on the `CollectionView` itself (it is declared on `StructuredItemsView`), **not** on `LinearItemsLayout` / `GridItemsLayout`:
```xml
<LinearItemsLayout Orientation="Vertical" ItemSizingStrategy="MeasureFirstItem" />
<CollectionView ItemsSource="{Binding Items}" ItemSizingStrategy="MeasureFirstItem" />
```
Only use it when every item really is the same height — with variable-height items it clips or stretches content.
- **Always use `ObservableCollection<T>`**, not `List<T>`. Swapping a `List` forces a full re-render.
Comment thread
AbhitejJohn marked this conversation as resolved.
Outdated
- **Update collections on the UI thread** — `MainThread.BeginInvokeOnMainThread(() => Items.Add(item))`.

Expand All @@ -328,10 +398,24 @@ collectionView.ScrollTo(item: myItem, position: ScrollToPosition.MakeVisible, an
| Incremental loading fires endlessly | Don't use `StackLayout` as layout; use `LinearItemsLayout` or `GridItemsLayout`. |
| EmptyView doesn't render correctly | Wrap custom empty views in `ContentView`. |
| Poor scroll performance | Use `MeasureFirstItem` sizing strategy for uniform item sizes. |
| `ItemSizingStrategy` doesn't compile | It is declared on `StructuredItemsView` — set it on `<CollectionView>`, not on `<LinearItemsLayout>` / `<GridItemsLayout>`. |
| Items clipped or stretched | `MeasureFirstItem` assumes uniform item size. Use the default `MeasureAllItems` for variable-height items. |
| Selected state not visible | Add `VisualState Name="Selected"` to the item template root element. |
| Binding errors in SwipeView commands | Use `RelativeSource AncestorType` to reach the ViewModel from inside the item template. |
| Using ListView instead of CollectionView | `CollectionView` replaces `ListView` — it has better performance, no `ViewCell`, and flexible layouts. |

## Validation

Before returning a `CollectionView` answer, confirm:

- [ ] The `DataTemplate` root is a `View`/`Layout` — **not** `ViewCell`.
- [ ] `DataTemplate` declares `x:DataType` for compiled bindings.
- [ ] `ItemsSource` is bound to `ObservableCollection<T>` if the list mutates.
- [ ] `ItemSizingStrategy` (if used) is on `<CollectionView>`, not on the layout.
- [ ] `Multiple` selection binds `SelectedItems`; `Single` binds `SelectedItem` (`TwoWay`).
- [ ] `RefreshView.IsRefreshing` is set back to `false` when the refresh completes.
- [ ] The answer covers **only** what the user asked — no unrequested sections.

## References

- [CollectionView overview](https://learn.microsoft.com/dotnet/maui/user-interface/controls/collectionview/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,22 @@ Set `ItemsLayout` to control arrangement. Default is `VerticalList`.

## ItemSizingStrategy

Controls how items are measured. Set on `ItemsLayout`.
Controls how items are measured. Set on the **`CollectionView` element itself** — it is declared on `StructuredItemsView`, **not** on `ItemsLayout`.

| Value | Behavior |
|---|---|
| `MeasureAllItems` | Measures every item individually (default). Accurate but slower for heterogeneous sizes. |
| `MeasureFirstItem` | Measures only the first item and applies that size to all. Much faster for uniform items. |

```xml
<!-- ✅ Correct — ItemSizingStrategy is a CollectionView property -->
<CollectionView ItemsSource="{Binding Items}"
ItemSizingStrategy="MeasureFirstItem" />
```

```xml
<!-- ❌ Wrong — LinearItemsLayout/GridItemsLayout have no ItemSizingStrategy property.
This does not compile. -->
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSizingStrategy="MeasureFirstItem" />
Expand Down
25 changes: 21 additions & 4 deletions plugins/dotnet-maui/skills/maui-data-binding/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,23 @@ and treat binding warnings as build errors.
- XAML pages or C# code-behind where bindings are declared
- A ViewModel class (or plan to create one)

## Rules That Change the Answer

Apply these to every binding answer — they are the differences between "it compiles"
and "it actually updates the UI".

| Situation | Do this | Not this |
|---|---|---|
| Page has a `BindingContext` | `x:DataType` on the **root element only** | Scattering `x:DataType` on children |
| A binding won't compile | Fix the path or the `x:DataType` | `x:DataType="x:Object"` to silence it — this disables compile-time checking |
| Every `DataTemplate` | Give it its **own** `x:DataType` | Relying on the outer scope's `x:DataType` (XC0024) |
| ViewModel change notification | `ObservableObject` + `[ObservableProperty]`, or implement `INotifyPropertyChanged` | A plain POCO base class — bindings will never update |
| Bindings show blank | Check `BindingContext` is actually set | Assuming the binding path is wrong |
| Enforcing compiled bindings | `<WarningsAsErrors>XC0022;XC0025</WarningsAsErrors>` | Leaving them as warnings and ignoring them |

**Do not** convert a working reflection-based binding to a compiled binding, add a
converter, or restructure a ViewModel unless the user asked or it fixes a real defect.

---

## Compiled Bindings — x:DataType Placement
Expand Down Expand Up @@ -99,10 +116,10 @@ anti-pattern — it disables compile-time checking and reintroduces reflection.

| Warning | Meaning |
|---------|---------|
| **XC0022** | Binding path not found on the declared `x:DataType` |
| **XC0023** | Property is not bindable |
| **XC0024** | `x:DataType` type not found |
| **XC0025** | Binding used without `x:DataType` (non-compiled fallback) |
| **XC0022** | Binding used **without `x:DataType` in scope** — not compiled, falls back to reflection |
| **XC0023** | Binding not compiled because `x:DataType` is **explicitly `null`** |
| **XC0024** | `x:DataType` came from an **outer scope** — annotate the `DataTemplate` with its own `x:DataType` |
| **XC0025** | Binding not compiled because it has an explicit **`Source`** — enable `<MauiEnableXamlCBindingWithSourceCompilation>` |

Add to the `.csproj`:

Expand Down
14 changes: 14 additions & 0 deletions plugins/dotnet-maui/skills/maui-dependency-injection/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ license: MIT
- Knowledge of which services, ViewModels, and Pages need registration
- Target platforms (Android, iOS, Mac Catalyst, Windows) for conditional registrations

## Rules That Change the Answer

| Situation | Do this | Why |
|---|---|---|
| Registering a Page or ViewModel | `AddTransient` | Each navigation must get a fresh instance; a Singleton page keeps stale state and can't be re-added to the visual tree |
| Registering shared/expensive state | `AddSingleton` | One instance app-wide (settings, DB connection, `HttpClient` handler) |
| Tempted to use `AddScoped` | Use `AddTransient` (or `AddSingleton` if sharing is intended) | MAUI has **no** built-in request scope. Without a manually created `IServiceScope`, a Scoped registration resolves from the root scope and behaves like a Singleton |
| Navigating to a DI-registered page | Register the page **and** its ViewModel, then `Routing.RegisterRoute` | `Shell.Current.GoToAsync` resolves the page through DI and injects its constructor dependencies |
| Platform-specific implementation | `#if` per platform **with every platform covered** | A missing platform branch leaves the service unregistered and throws at resolution time |

**Do not** introduce DI into a project that isn't using it, swap a working service
lifetime, or add an interface purely for symmetry — only when the user asked or it
fixes a real defect.

## Workflow

1. Identify all services, ViewModels, and Pages that need to participate in dependency injection.
Expand Down
6 changes: 3 additions & 3 deletions plugins/dotnet-maui/skills/maui-safe-area/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ In .NET 9, Android `ContentPage` behaved like `Container`. In .NET 10, the defau
<ContentPage SafeAreaEdges="Container">
```

### WindowSoftInputModeAdjust.Resize removed
### WindowSoftInputModeAdjust.Resize superseded

If you used `WindowSoftInputModeAdjust.Resize` in .NET 9, replace it with `SafeAreaEdges="All"` on the ContentPage for equivalent keyboard avoidance.
`WindowSoftInputModeAdjust.Resize` still exists and still compiles (it is not removed and not obsolete), but it is Android-only. For cross-platform keyboard avoidance prefer `SafeAreaEdges="All"` (or the `SoftInput` region) on the ContentPage.

## Usage Patterns

Expand Down Expand Up @@ -271,7 +271,7 @@ Available CSS environment variables: `env(safe-area-inset-top)`, `env(safe-area-
| `Layout.IgnoreSafeArea="True"` | `SafeAreaEdges="None"` |
| `WindowSoftInputModeAdjust.Resize` | `SafeAreaEdges="All"` on ContentPage |

The legacy properties still compile but are marked obsolete. `IgnoreSafeArea="True"` maps internally to `SafeAreaRegions.None`.
The legacy `ios:Page.UseSafeArea` and `Layout.IgnoreSafeArea` properties still compile but are marked obsolete. `IgnoreSafeArea="True"` maps internally to `SafeAreaRegions.None`. `WindowSoftInputModeAdjust.Resize` is **not** obsolete — it remains supported, but is Android-only.

```xaml
<!-- .NET 9 (legacy, iOS-only) -->
Expand Down
18 changes: 18 additions & 0 deletions plugins/dotnet-maui/skills/maui-shell-navigation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ Implement page navigation in .NET MAUI apps using Shell. Shell provides URI-base
- Pages (`ContentPage`) to navigate between
- Route names for detail pages not in the visual hierarchy

## Rules That Change the Answer

These are the Shell-specific decisions that are easy to get wrong. Apply them
whenever they are relevant to what the user asked.

| Situation | Do this | Not this |
|---|---|---|
| Declaring pages in `AppShell.xaml` | `<ShellContent ContentTemplate="{DataTemplate pages:MyPage}" />` — lazy | `<ShellContent Content="..."/>`, which constructs **every** page at startup |
Comment thread
AbhitejJohn marked this conversation as resolved.
Outdated
| Navigating to a page not in the visual hierarchy | `Routing.RegisterRoute("details", typeof(DetailsPage))` first | Calling `GoToAsync("details")` unregistered — it throws at runtime |
| Receiving navigation parameters | Implement `IQueryAttributable` on the **ViewModel** | Implementing it on the Page, which splits state from the BindingContext |
| Passing a whole object | `ShellNavigationQueryParameters` | Serialising the object into the query string |
| Any `GoToAsync` call | `await` it | Fire-and-forget — exceptions are swallowed and navigation races |
| Confirming before back navigation | `ShellNavigatingEventArgs.GetDeferral()` … `deferral.Complete()` | Blocking synchronously on the dialog task |
| Detecting back navigation | Check `e.Source == ShellNavigationSource.Pop` | Assuming every navigation is a back action |

**Do not** propose `NavigationPage` / `PushAsync` solutions for a Shell app, and do
not restructure a working `AppShell` hierarchy unless the user asked.

## Shell Visual Hierarchy

Shell uses a four-level hierarchy. Each level wraps the one below it:
Expand Down
Loading