Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion plugins/dotnet-maui/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dotnet-maui",
"version": "0.1.0",
"description": "Skills for .NET MAUI development: environment setup, diagnostics, and troubleshooting.",
"description": "Skills for .NET MAUI development: environment setup, diagnostics, coding guardrails, and API currency.",
"skills": ["./skills/"]
}
101 changes: 101 additions & 0 deletions plugins/dotnet-maui/skills/maui-coding-guardrails/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
name: maui-coding-guardrails
description: >-
Guardrails for .NET MAUI layouts, controls, and handlers. USE FOR: any MAUI
code generation/review. NOT FOR: API deprecations (maui-current-apis) or
environment setup (dotnet-maui-doctor).
---

# .NET MAUI Coding Guardrails

Apply to all MAUI code generation and editing. For API replacements, use `maui-current-apis`.

## Layout Rules

**Don't put ScrollView/CollectionView inside StackLayout.**
StackLayout gives children infinite height — ScrollView won't scroll, CollectionView loses virtualization. Use Grid.

```xml
<!-- ❌ StackLayout gives infinite height -->
<StackLayout>
<ScrollView>...</ScrollView>
</StackLayout>

<!-- ✅ Grid constrains height -->
<Grid RowDefinitions="Auto,*,Auto">
<Label Text="Header" />
<ScrollView Grid.Row="1">...</ScrollView>
<Button Grid.Row="2" Text="Submit" />
</Grid>
```

**Prefer `VerticalStackLayout`/`HorizontalStackLayout` over `StackLayout`** — avoids legacy Orientation check each measure pass.

**Don't use `AndExpand` options.** No-ops in MAUI; use Grid row/column sizing.

**Flatten nested layouts.** Each nesting level adds measure/arrange cost; prefer flat Grids.

## Control Rules

### ⚠️ DO NOT USE `Frame` — Use `Border` Instead

`Frame` is Xamarin.Forms legacy. `Border` supports `StrokeShape`, custom strokes. Keep `Frame` only for `HasShadow`.

```xml
<Border StrokeShape="RoundRectangle 10" Stroke="Gray" StrokeThickness="1" Padding="12">
<Label Text="Content" />
</Border>
```

**Use `CollectionView` over `ListView`.** ListView and all cell types deprecated in .NET 10. For ≤20 items, use `BindableLayout`.

**Use `Background` over `BackgroundColor`.** Accepts Color and Brush (gradients, images).

**Reference images as `.png`, not `.svg`.** SVGs compile to PNG; `.svg` fails at runtime.

## Navigation Rules

**Don't mix Shell with NavigationPage/TabbedPage/FlyoutPage.** Shell has its own stack; wrapping in NavigationPage creates competing stacks, corruption, double headers. Pick one paradigm.

**Set `App.MainPage` once.** Use Shell routing or `NavigationPage.PushAsync` after. Changing MainPage leaks pages/handlers.

## Handler Architecture

Use **handlers** and Mapper methods, not renderers. Renderers are Xamarin.Forms-only.

```csharp
EntryHandler.Mapper.AppendToMapping("NoBorder", (handler, view) =>
{
#if ANDROID
handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent);
#elif IOS
handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None;
#endif
});
```

`AppendToMapping` runs after defaults, `PrependToMapping` before, `ModifyMapping` wraps one property.

## Compiled Bindings

Declare `x:DataType` on pages and DataTemplates — without it, bindings use slow reflection (8–20×) and typos fail silently.

```xml
<ContentPage x:DataType="viewmodels:MainViewModel">
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ItemViewModel">
<Label Text="{Binding Name}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
```

## Common Pitfalls

| Pitfall | Fix |
|---------|-----|
| Gesture on parent+child — parent intercepts | `InputTransparent="True"` on overlay or restructure ownership |
| Unsubscribed events — pages leak | Unsubscribe in `OnDisappearing` or use `WeakReferenceMessenger` |
Comment on lines +97 to +100

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

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

This table uses || at the start of each row, which adds an unintended empty first column in Markdown. Use a single leading | instead (and update the other tables in this file that follow the same pattern).

Copilot uses AI. Check for mistakes.
| Only testing on emulators | Test on physical devices — emulators hide perf/gesture issues |
109 changes: 109 additions & 0 deletions plugins/dotnet-maui/skills/maui-current-apis/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
name: maui-current-apis
description: >-
Tracks deprecated/removed APIs in .NET MAUI 8/9/10, MauiReactor, Blazor
Hybrid. USE FOR: generating/reviewing MAUI code, fixing deprecation warnings.
NOT FOR: layout patterns (maui-coding-guardrails) or setup (dotnet-maui-doctor).
---
Comment thread
jfversluis marked this conversation as resolved.

# .NET MAUI Current APIs

Prevents generating code with deprecated or removed APIs.

**Before generating:** Read `.csproj` TFM and package versions. API availability varies by .NET version; don't suggest .NET 10 APIs for `net8.0`.

## Key Rules

1. **Read `.csproj` first**; don't assume target version.
2. **Prefer newer APIs** available for the detected version.
3. **`Xamarin.*` don't exist in MAUI** — won't compile.
4. **Avoid `Compatibility.*`** — Xamarin.Forms layout logic, migration aid only.
5. **`Device` class deprecated** — split into services (see table).
6. **Use `*Async` in .NET 10+** — animation/dialog methods renamed.
7. **Check package versions** — CommunityToolkit/MauiReactor break between majors.

---

## Deprecated APIs — .NET MAUI 10

### Controls

Comment thread
jfversluis marked this conversation as resolved.
| ❌ Deprecated | ✅ Replacement | Why |
|---------------|----------------|-----|
| `ListView` | `CollectionView` | Deprecated in .NET 10 with all cell types |
Comment on lines +31 to +33

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

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

These Markdown tables start with || (double pipe), which creates an extra empty first column and renders incorrectly. Replace the leading || with a single | (and apply consistently to all tables in this file).

Copilot uses AI. Check for mistakes.
| `TableView` | `CollectionView` or custom layout | Deprecated in .NET 10 |
| `Frame` | `Border` | Legacy; Border supports StrokeShape |
| `Compatibility.RelativeLayout` | `Grid` | Migration-only |
| `Compatibility.StackLayout` | `VerticalStackLayout`/`HorizontalStackLayout` | Xamarin layout logic |

### Gestures

| ❌ Deprecated | ✅ Replacement |
|---------------|----------------|
| `ClickGestureRecognizer` | `TapGestureRecognizer` |
| `Accelerator` | `KeyboardAccelerator` |

### Navigation

| ❌ Deprecated | ✅ Replacement | Why |
|---------------|----------------|-----|
| `Page.IsBusy` | `ActivityIndicator` | Obsolete .NET 10 |
| `DisplayAlert()` | `DisplayAlertAsync()` | Async rename |
| `DisplayActionSheet()` | `DisplayActionSheetAsync()` | Same |
| `MessagingCenter` | `WeakReferenceMessenger` (CommunityToolkit.Mvvm) | Internal .NET 10; leaked subscriptions |

### Animation (*Async renames in .NET 10)

| ❌ Old | ✅ New |
|--------|--------|
| `FadeTo()`, `RotateTo()`, `ScaleTo()`, `TranslateTo()` | `FadeToAsync()`, `RotateToAsync()`, `ScaleToAsync()`, `TranslateToAsync()` |
| `RelRotateTo()`, `RelScaleTo()`, `LayoutTo()` | `RelRotateToAsync()`, `RelScaleToAsync()`, `LayoutToAsync()` |

### Device APIs (class split into focused services)

| ❌ Deprecated | ✅ Replacement |
|---------------|----------------|
| `Device.RuntimePlatform` | `DeviceInfo.Platform` |
| `Device.BeginInvokeOnMainThread()` | `MainThread.BeginInvokeOnMainThread()` |
| `Device.OpenUri()` | `Launcher.OpenAsync()` |
| `Device.StartTimer()` | `Dispatcher.StartTimer()` or `PeriodicTimer` |
| `DependencyService` | Constructor injection via `builder.Services` |

### Other

| ❌ Deprecated | ✅ Replacement |
|---------------|----------------|
| `Color.FromHex()` | `Color.FromArgb()` |
| `Page.UseSafeArea` / `Layout.IgnoreSafeArea` | `SafeAreaEdges` property (.NET 10) |
| `AutomationProperties.Name`/`.HelpText` | `SemanticProperties.Description`/`.Hint` |

### NuGet Packages

| ❌ Old | ✅ New |
|--------|--------|
| `Xamarin.Forms` | `Microsoft.Maui.Controls` |
| `Xamarin.Essentials` | Built-in MAUI APIs |
| `Xamarin.CommunityToolkit` | `CommunityToolkit.Maui` |
| `Microsoft.Toolkit.Mvvm` | `CommunityToolkit.Mvvm` |

---

## MauiReactor v3+ (.NET MAUI 9/10)

- **Hot reload**: feature switch in `.csproj`, not v2 `EnableMauiReactorHotReload()`.
- **State**: `State<T>`/`Props<T>`, not `RxComponent`.
- **Navigation**: use MauiReactor nav; avoid mixing Shell `GoToAsync`.

## Blazor Hybrid

- Prefer `BlazorWebView`, not `WebView`.
- JS interop: `IJSRuntime.InvokeAsync<T>()` — sync deadlocks on mobile.
- Safe areas: CSS `env(safe-area-inset-*)`; don't combine with XAML `SafeAreaEdges`.

## Version Detection

| TFM | .NET | MAUI | CommunityToolkit.Maui |
|-----|------|------|-----------------------|
| `net10.0-*` | 10 | 10 | v11+ |
| `net9.0-*` | 9 | 9 | v9-10 |
| `net8.0-*` | 8 (LTS) | 8 | v5-7 |
108 changes: 108 additions & 0 deletions tests/dotnet-maui/maui-coding-guardrails/eval.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
scenarios:
- name: "Prevent ListView usage in new MAUI app"
prompt: |
I'm building a new .NET MAUI app and I need to display a list of products.
Each product has a name, price, and image. I want to show them in a scrollable
list with a search bar at the top. Can you give me the XAML for this?
assertions:
- type: "output_contains"
value: "CollectionView"
- type: "output_not_contains"
value: "<ListView"
Comment thread
jfversluis marked this conversation as resolved.
- type: "output_matches"
pattern: "x:DataType"
- type: "output_not_contains"
value: "<Frame"
- type: "output_not_contains"
value: "<TableView"
rubric:
- "Uses CollectionView instead of ListView for the product list"
- "Declares x:DataType for compiled bindings on DataTemplate or page"
- "Does not use any deprecated controls (ListView, TableView, Frame)"
- "Uses Grid or VerticalStackLayout as the parent container, not StackLayout wrapping a ScrollView"
timeout: 120

- name: "Fix AndExpand layout that does nothing"
prompt: |
My .NET MAUI button isn't expanding to fill the remaining space at the
bottom of the screen. I tried this but it doesn't work:

```xml
<VerticalStackLayout>
<Label Text="Title" FontSize="24" />
<Entry Placeholder="Enter text" />
<Button Text="Submit" VerticalOptions="FillAndExpand" />
</VerticalStackLayout>
```

Why doesn't the button fill the rest of the screen?
assertions:
- type: "output_contains"
value: "Grid"
- type: "output_matches"
pattern: "(?i)no.op|does nothing|ignored|no effect|doesn.t work|not supported|undefined"
rubric:
- "Explains that AndExpand/FillAndExpand is a no-op in MAUI (silently does nothing)"
- "Recommends Grid with star-sized rows as the correct approach"
- "Shows a working Grid example with RowDefinitions"
- "Does not suggest VerticalOptions alone will solve the problem"
timeout: 120

- name: "Replace Frame with Border in card layout"
prompt: |
I need a card-style UI component for my .NET MAUI app with rounded corners,
a border, and some padding. Show me the XAML.
assertions:
- type: "output_contains"
value: "Border"
- type: "output_matches"
pattern: "(?i)StrokeShape|RoundRectangle"
rubric:
- "Uses Border instead of Frame for the card component"
- "Uses StrokeShape with RoundRectangle for rounded corners"
- "If Frame is mentioned at all, it is explicitly called out as legacy"
- "Uses Background property instead of BackgroundColor"
timeout: 120

- name: "Handler customization instead of renderer"
prompt: |
I need to customize the Picker control in my .NET MAUI app to change the
dropdown arrow color on Android and remove the underline on iOS. My colleague
said I need a custom renderer with [assembly: ExportRenderer]. Show me how.
assertions:
- type: "output_matches"
pattern: "(?i)handler|mapper"
- type: "output_not_contains"
value: "ExportRenderer"
- type: "output_matches"
pattern: "AppendToMapping|PrependToMapping|ModifyMapping"
rubric:
- "Redirects from custom renderer to handler/mapper approach"
- "Explains that ExportRenderer/renderers don't exist in MAUI"
- "Shows platform-specific code using #if ANDROID / #elif IOS preprocessor directives"
- "Uses AppendToMapping, PrependToMapping, or ModifyMapping method"
timeout: 120

- name: "Image reference and Background property guidance"
prompt: |
I'm adding a logo to my .NET MAUI app. I put logo.svg in Resources/Images/.
Also, I want the containing layout to have a light blue background:

```xml
<VerticalStackLayout BackgroundColor="LightBlue">
<Image Source="logo.svg" />
</VerticalStackLayout>
```

Is this correct?
assertions:
- type: "output_contains"
value: ".png"
- type: "output_matches"
pattern: "(?i)Background(?!Color)"
rubric:
- "Instructs to reference the image as .png, not .svg"
- "Explains that SVGs are converted to PNGs at build time"
- "Recommends Background property over BackgroundColor"
- "Explains Background accepts both Color and Brush (gradients, images)"
timeout: 120
Loading
Loading