feat: add Avalonia Zafiro development, layout, and viewmodel skills

This commit is contained in:
SuperJMN
2026-01-23 15:24:41 +01:00
parent 8c8bae5e98
commit c6df6cee4c
18 changed files with 787 additions and 8 deletions

View File

@@ -0,0 +1,59 @@
---
name: avalonia-layout-zafiro
description: Guidelines for modern Avalonia UI layout using Zafiro.Avalonia, emphasizing shared styles, generic components, and avoiding XAML redundancy.
allowed-tools: Read, Write, Edit, Glob, Grep
---
# Avalonia Layout with Zafiro.Avalonia
> Master modern, clean, and maintainable Avalonia UI layouts.
> **Focus on semantic containers, shared styles, and minimal XAML.**
## 🎯 Selective Reading Rule
**Read ONLY files relevant to the layout challenge!**
---
## 📑 Content Map
| File | Description | When to Read |
|------|-------------|--------------|
| `themes.md` | Theme organization and shared styles | Setting up or refining app themes |
| `containers.md` | Semantic containers (`HeaderedContainer`, `EdgePanel`, `Card`) | Structuring views and layouts |
| `icons.md` | Icon usage with `IconExtension` and `IconOptions` | Adding and customizing icons |
| `behaviors.md` | `Xaml.Interaction.Behaviors` and avoiding Converters | Implementing complex interactions |
| `components.md` | Generic components and avoiding nesting | Creating reusable UI elements |
---
## 🔗 Related Project (Exemplary Implementation)
For a real-world example, refer to the **Angor** project:
`/mnt/fast/Repos/angor/src/Angor/Avalonia/Angor.Avalonia.sln`
---
## ✅ Checklist for Clean Layouts
- [ ] **Used semantic containers?** (e.g., `HeaderedContainer` instead of `Border` with manual header)
- [ ] **Avoided redundant properties?** Use shared styles in `axaml` files.
- [ ] **Minimized nesting?** Flatten layouts using `EdgePanel` or generic components.
- [ ] **Icons via extension?** Use `{Icon fa-name}` and `IconOptions` for styling.
- [ ] **Behaviors over code-behind?** Use `Interaction.Behaviors` for UI-logic.
- [ ] **Avoided Converters?** Prefer ViewModel properties or Behaviors unless necessary.
---
## ❌ Anti-Patterns
**DON'T:**
- Use hardcoded colors or sizes (literals) in views.
- Create deep nesting of `Grid` and `StackPanel`.
- Repeat visual properties across multiple elements (use Styles).
- Use `IValueConverter` for simple logic that belongs in the ViewModel.
**DO:**
- Use `DynamicResource` for colors and brushes.
- Extract repeated layouts into generic components.
- Leverage `Zafiro.Avalonia` specific panels like `EdgePanel` for common UI patterns.

View File

@@ -0,0 +1,35 @@
# Interactions and Logic
To keep XAML clean and maintainable, minimize logic in views and avoid excessive use of converters.
## 🎭 Xaml.Interaction.Behaviors
Use `Interaction.Behaviors` to handle UI-related logic that doesn't belong in the ViewModel, such as focus management, animations, or specialized event handling.
```xml
<TextBox Text="{Binding Address}">
<Interaction.Behaviors>
<UntouchedClassBehavior />
</Interaction.Behaviors>
</TextBox>
```
### Why use Behaviors?
- **Encapsulation**: UI logic is contained in a reusable behavior class.
- **Clean XAML**: Avoids code-behind and complex XAML triggers.
- **Testability**: Behaviors can be tested independently of the View.
## 🚫 Avoiding Converters
Converters often lead to "magical" logic hidden in XAML. Whenever possible, prefer:
1. **ViewModel Properties**: Let the ViewModel provide the final data format (e.g., a `string` formatted for display).
2. **MultiBinding**: Use for simple logic combinations (And/Or) directly in XAML.
3. **Behaviors**: For more complex interactions that involve state or events.
### When to use Converters?
Only use them when the conversion is purely visual and highly reusable across different contexts (e.g., `BoolToOpacityConverter`).
## 🧩 Simplified Interactions
If you find yourself needing a complex converter or behavior, consider if the component can be simplified or if the data model can be adjusted to make the view binding more direct.

View File

@@ -0,0 +1,41 @@
# Building Generic Components
Reducing nesting and complexity is achieved by breaking down views into generic, reusable components.
## 🧊 Generic Components
Instead of building large, complex views, extract recurring patterns into small `UserControl`s.
### Example: A generic "Summary Item"
Instead of repeating a `Grid` with labels and values:
```xml
<!-- ❌ BAD: Repeated Grid -->
<Grid ColumnDefinitions="*,Auto">
<TextBlock Text="Total:" />
<TextBlock Grid.Column="1" Text="{Binding Total}" />
</Grid>
```
Create a generic component (or use `EdgePanel` with a Style):
```xml
<!-- ✅ GOOD: Use a specialized control or style -->
<EdgePanel StartContent="Total:" EndContent="{Binding Total}" Classes="SummaryItem" />
```
## 📉 Flattening Layouts
Avoid deep nesting. Deeply nested XAML is hard to read and can impact performance.
- **StackPanel vs Grid**: Use `StackPanel` (with `Spacing`) for simple linear layouts.
- **EdgePanel**: Great for "Label - Value" or "Icon - Text - Action" rows.
- **UniformGrid**: Use for grids where all cells are the same size.
## 🔧 Component Granularity
- **Atomical**: Small controls like custom buttons or icons.
- **Molecular**: Groups of atoms like a `HeaderedContainer` with specific content.
- **Organisms**: Higher-level sections of a page.
Aim for components that are generic enough to be reused but specific enough to simplify the parent view significantly.

View File

@@ -0,0 +1,50 @@
# Semantic Containers
Using the right container for the data type simplifies XAML and improves maintainability. `Zafiro.Avalonia` provides specialized controls for common layout patterns.
## 📦 HeaderedContainer
Prefer `HeaderedContainer` over a `Border` or `Grid` when a section needs a title or header.
```xml
<HeaderedContainer Header="Security Settings" Classes="WizardSection">
<StackPanel>
<!-- Content here -->
</StackPanel>
</HeaderedContainer>
```
### Key Properties:
- `Header`: The content or string for the header.
- `HeaderBackground`: Brush for the header area.
- `ContentPadding`: Padding for the content area.
## ↔️ EdgePanel
Use `EdgePanel` to position elements at the edges of a container without complex `Grid` definitions.
```xml
<EdgePanel StartContent="{Icon fa-wallet}"
Content="Wallet Balance"
EndContent="$1,234.00" />
```
### Slots:
- `StartContent`: Aligned to the left (or beginning).
- `Content`: Fills the remaining space in the middle.
- `EndContent`: Aligned to the right (or end).
## 📇 Card
A simple container for grouping related information, often used inside `HeaderedContainer` or as a standalone element in a list.
```xml
<Card Header="Enter recipient address:">
<TextBox Text="{Binding Address}" />
</Card>
```
## 📐 Best Practices
- Use `Classes` to apply themed variants (e.g., `Classes="Section"`, `Classes="Highlight"`).
- Customize internal parts of the containers using templates in your styles when necessary, rather than nesting more controls.

View File

@@ -0,0 +1,53 @@
# Icon Usage
`Zafiro.Avalonia` simplifies icon management using a specialized markup extension and styling options.
## 🛠️ IconExtension
Use the `{Icon}` markup extension to easily include icons from libraries like FontAwesome.
```xml
<!-- Positional parameter -->
<Button Content="{Icon fa-wallet}" />
<!-- Named parameter -->
<ContentControl Content="{Icon Source=fa-gear}" />
```
## 🎨 IconOptions
`IconOptions` allows you to customize icons without manually wrapping them in other controls. It's often used in styles to provide a consistent look.
```xml
<Style Selector="HeaderedContainer /template/ ContentPresenter#Header EdgePanel /template/ ContentControl#StartContent">
<Setter Property="IconOptions.Size" Value="20" />
<Setter Property="IconOptions.Fill" Value="{DynamicResource Accent}" />
<Setter Property="IconOptions.Padding" Value="10" />
<Setter Property="IconOptions.CornerRadius" Value="10" />
</Style>
```
### Common Properties:
- `IconOptions.Size`: Sets the width and height of the icon.
- `IconOptions.Fill`: The color/brush of the icon.
- `IconOptions.Background`: Background brush for the icon container.
- `IconOptions.Padding`: Padding inside the icon container.
- `IconOptions.CornerRadius`: Corner radius if a background is used.
## 📁 Shared Icon Resources
Define icons as resources for reuse across the application.
```xml
<ResourceDictionary xmlns="https://github.com/avaloniaui">
<Icon x:Key="fa-wallet" Source="fa-wallet" />
</ResourceDictionary>
```
Then use them with `StaticResource` if they are already defined:
```xml
<Button Content="{StaticResource fa-wallet}" />
```
However, the `{Icon ...}` extension is usually preferred for its brevity and ability to create new icon instances on the fly.

View File

@@ -0,0 +1,51 @@
# Theme Organization and Shared Styles
Efficient theme organization is key to avoiding redundant XAML and ensuring visual consistency.
## 🏗️ Structure
Follow the pattern from Angor:
1. **Colors & Brushes**: Define in a dedicated `Colors.axaml`. Use `DynamicResource` to support theme switching.
2. **Styles**: Group styles by category (e.g., `Buttons.axaml`, `Containers.axaml`, `Typography.axaml`).
3. **App-wide Theme**: Aggregate all styles in a main `Theme.axaml`.
## 🎨 Avoiding Redundancy
Instead of setting properties directly on elements:
```xml
<!-- ❌ BAD: Redundant properties -->
<HeaderedContainer CornerRadius="10" BorderThickness="1" BorderBrush="Blue" Background="LightBlue" />
<HeaderedContainer CornerRadius="10" BorderThickness="1" BorderBrush="Blue" Background="LightBlue" />
<!-- ✅ GOOD: Use Classes and Styles -->
<HeaderedContainer Classes="BlueSection" />
<HeaderedContainer Classes="BlueSection" />
```
Define the style in a shared `axaml` file:
```xml
<Style Selector="HeaderedContainer.BlueSection">
<Setter Property="CornerRadius" Value="10" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource Accent}" />
<Setter Property="Background" Value="{DynamicResource SurfaceSubtle}" />
</Style>
```
## 🧩 Shared Icons and Resources
Centralize icon definitions and other shared resources in `Icons.axaml` and include them in the `MergedDictionaries` of your theme or `App.axaml`.
```xml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<MergeResourceInclude Source="UI/Themes/Styles/Containers.axaml" />
<MergeResourceInclude Source="UI/Shared/Resources/Icons.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
```

View File

@@ -0,0 +1,29 @@
---
name: avalonia-viewmodels-zafiro
description: Optimal ViewModel and Wizard creation patterns for Avalonia using Zafiro and ReactiveUI.
---
# Avalonia ViewModels with Zafiro
This skill provides a set of best practices and patterns for creating ViewModels, Wizards, and managing navigation in Avalonia applications, leveraging the power of **ReactiveUI** and the **Zafiro** toolkit.
## Core Principles
1. **Functional-Reactive Approach**: Use ReactiveUI (`ReactiveObject`, `WhenAnyValue`, etc.) to handle state and logic.
2. **Enhanced Commands**: Utilize `IEnhancedCommand` for better command management, including progress reporting and name/text attributes.
3. **Wizard Pattern**: Implement complex flows using `SlimWizard` and `WizardBuilder` for a declarative and maintainable approach.
4. **Automatic Section Discovery**: Use the `[Section]` attribute to register and discover UI sections automatically.
5. **Clean Composition**: map ViewModels to Views using `DataTypeViewLocator` and manage dependencies in the `CompositionRoot`.
## Guides
- [ViewModels & Commands](viewmodels.md): Creating robust ViewModels and handling commands.
- [Wizards & Flows](wizards.md): Building multi-step wizards with `SlimWizard`.
- [Navigation & Sections](navigation_sections.md): Managing navigation and section-based UIs.
- [Composition & Mapping](composition.md): Best practices for View-ViewModel wiring and DI.
## Example Reference
For real-world implementations, refer to the **Angor** project:
- `CreateProjectFlowV2.cs`: Excellent example of complex Wizard building.
- `HomeViewModel.cs`: Simple section ViewModel using functional-reactive commands.

View File

@@ -0,0 +1,75 @@
# Composition & Mapping
Ensuring your ViewModels are correctly instantiated and mapped to their corresponding Views is crucial for a maintainable application.
## ViewModel-to-View Mapping
Zafiro uses the `DataTypeViewLocator` to automatically map ViewModels to Views based on their data type.
### Integration in App.axaml
Register the `DataTypeViewLocator` in your application's data templates:
```xml
<Application.DataTemplates>
<DataTypeViewLocator />
<DataTemplateInclude Source="avares://Zafiro.Avalonia/DataTemplates.axaml" />
</Application.DataTemplates>
```
### Registration
Mappings can be registered globally or locally. Common practice in Zafiro projects is to use naming conventions or explicit registrations made by source generators.
## Composition Root
Use a central `CompositionRoot` to manage dependency injection and service registration.
```csharp
public static class CompositionRoot
{
public static IShellViewModel CreateMainViewModel(Control topLevelView)
{
var services = new ServiceCollection();
services
.AddViewModels()
.AddUIServices(topLevelView);
var serviceProvider = services.BuildServiceProvider();
return serviceProvider.GetRequiredService<IShellViewModel>();
}
}
```
### Registering ViewModels
Register ViewModels with appropriate scopes (Transient, Scoped, or Singleton).
```csharp
public static IServiceCollection AddViewModels(this IServiceCollection services)
{
return services
.AddTransient<IHomeSectionViewModel, HomeSectionSectionViewModel>()
.AddSingleton<IShellViewModel, ShellViewModel>();
}
```
## View Injection
Use the `Connect` helper (if available) or manual instantiation in `OnFrameworkInitializationCompleted`:
```csharp
public override void OnFrameworkInitializationCompleted()
{
this.Connect(
() => new ShellView(),
view => CompositionRoot.CreateMainViewModel(view),
() => new MainWindow());
base.OnFrameworkInitializationCompleted();
}
```
> [!TIP]
> Use `ActivatorUtilities.CreateInstance` when you need to manually instantiate a class while still resolving its dependencies from the `IServiceProvider`.

View File

@@ -0,0 +1,53 @@
# Navigation & Sections
Zafiro provides powerful abstractions for managing application-wide navigation and modular UI sections.
## Navigation with INavigator
The `INavigator` interface is used to switch between different views or viewmodels.
```csharp
public class MyViewModel(INavigator navigator)
{
public async Task GoToDetails()
{
await navigator.Navigate(() => new DetailsViewModel());
}
}
```
## UI Sections
Sections are modular parts of the UI (like tabs or sidebar items) that can be automatically registered.
### The [Section] Attribute
ViewModels intended to be sections should be marked with the `[Section]` attribute.
```csharp
[Section("Wallet", icon: "fa-wallet")]
public class WalletSectionViewModel : IWalletSectionViewModel
{
// ...
}
```
### Automatic Registration
In the `CompositionRoot`, sections can be automatically registered:
```csharp
services.AddAnnotatedSections(logger);
services.AddSectionsFromAttributes(logger);
```
### Switching Sections
You can switch the current active section via the `IShellViewModel`:
```csharp
shellViewModel.SetSection("Browse");
```
> [!IMPORTANT]
> The `icon` parameter in the `[Section]` attribute supports FontAwesome icons (e.g., `fa-home`) when configured with `ProjektankerIconControlProvider`.

View File

@@ -0,0 +1,68 @@
# ViewModels & Commands
In a Zafiro-based application, ViewModels should be functional, reactive, and resilient.
## Reactive ViewModels
Use `ReactiveObject` as the base class. Properties should be defined using the `[Reactive]` attribute (from ReactiveUI.SourceGenerators) for brevity.
```csharp
public partial class MyViewModel : ReactiveObject
{
[Reactive] private string name;
[Reactive] private bool isBusy;
}
```
### Observation and Transformation
Use `WhenAnyValue` to react to property changes:
```csharp
this.WhenAnyValue(x => x.Name)
.Select(name => !string.IsNullOrEmpty(name))
.ToPropertyEx(this, x => x.CanSubmit);
```
## Enhanced Commands
Zafiro uses `IEnhancedCommand`, which extends `ICommand` and `IReactiveCommand` with additional metadata like `Name` and `Text`.
### Creating a Command
Use `ReactiveCommand.Create` or `ReactiveCommand.CreateFromTask` and then `Enhance()` it.
```csharp
public IEnhancedCommand Submit { get; }
public MyViewModel()
{
Submit = ReactiveCommand.CreateFromTask(OnSubmit, canSubmit)
.Enhance(text: "Submit Data", name: "SubmitCommand");
}
```
### Error Handling
Use `HandleErrorsWith` to automatically channel command errors to the `NotificationService`.
```csharp
Submit.HandleErrorsWith(uiServices.NotificationService, "Submission Failed")
.DisposeWith(disposable);
```
## Disposables
Always use a `CompositeDisposable` to manage subscriptions and command lifetimes.
```csharp
public class MyViewModel : ReactiveObject, IDisposable
{
private readonly CompositeDisposable disposables = new();
public void Dispose() => disposables.Dispose();
}
```
> [!TIP]
> Use `.DisposeWith(disposables)` on any observable subscription or command to ensure proper cleanup.

View File

@@ -0,0 +1,47 @@
# Wizards & Flows
Complex multi-step processes are handled using the `SlimWizard` pattern. This provides a declarative way to define steps, navigation logic, and final results.
## Defining a Wizard
Use `WizardBuilder` to define the steps. Each step corresponds to a ViewModel.
```csharp
SlimWizard<string> wizard = WizardBuilder
.StartWith(() => new Step1ViewModel(data))
.NextUnit()
.WhenValid()
.Then(prevResult => new Step2ViewModel(prevResult))
.NextCommand(vm => vm.CustomNextCommand)
.Then(result => new SuccessViewModel("Done!"))
.Next((_, s) => s, "Finish")
.WithCompletionFinalStep();
```
### Navigation Rules
- **NextUnit()**: Advances when a simple signal is emitted.
- **NextCommand()**: Advances when a specific command in the ViewModel execution successfully.
- **WhenValid()**: Wait until the current ViewModel's validation passes before allowing navigation.
- **Always()**: Navigation is always allowed.
## Navigation Integration
The wizard is navigated using an `INavigator`:
```csharp
public async Task CreateSomething()
{
var wizard = BuildWizard();
var result = await wizard.Navigate(navigator);
// Handle result
}
```
## Step Configuration
- **WithCompletionFinalStep()**: Marks the wizard as finished when the last step completes.
- **WithCommitFinalStep()**: Typically used for wizards that perform a final "Save" or "Deploy" action.
> [!NOTE]
> The `SlimWizard` handles the "Back" command automatically, providing a consistent user experience across different flows.

View File

@@ -0,0 +1,29 @@
---
name: avalonia-zafiro-development
description: Mandatory skills, conventions, and behavioral rules for Avalonia UI development using the Zafiro toolkit.
---
# Avalonia Zafiro Development
This skill defines the mandatory conventions and behavioral rules for developing cross-platform applications with Avalonia UI and the Zafiro toolkit. These rules prioritize maintainability, correctness, and a functional-reactive approach.
## Core Pillars
1. **Functional-Reactive MVVM**: Pure MVVM logic using DynamicData and ReactiveUI.
2. **Safety & Predictability**: Explicit error handling with `Result` types and avoidance of exceptions for flow control.
3. **Cross-Platform Excellence**: Strictly Avalonia-independent ViewModels and composition-over-inheritance.
4. **Zafiro First**: Leverage existing Zafiro abstractions and helpers to avoid redundancy.
## Guides
- [Core Technical Skills & Architecture](core-technical-skills.md): Fundamental skills and architectural principles.
- [Naming & Coding Standards](naming-standards.md): Rules for naming, fields, and error handling.
- [Avalonia, Zafiro & Reactive Rules](avalonia-reactive-rules.md): Specific guidelines for UI, Zafiro integration, and DynamicData pipelines.
- [Zafiro Shortcuts](zafiro-shortcuts.md): Concise mappings for common Rx/Zafiro operations.
- [Common Patterns](patterns.md): Advanced patterns like `RefreshableCollection` and Validation.
## Procedure Before Writing Code
1. **Search First**: Search the codebase for similar implementations or existing Zafiro helpers.
2. **Reusable Extensions**: If a helper is missing, propose a new reusable extension method instead of inlining complex logic.
3. **Reactive Pipelines**: Ensure DynamicData operators are used instead of plain Rx where applicable.

View File

@@ -0,0 +1,49 @@
# Avalonia, Zafiro & Reactive Rules
## Avalonia UI Rules
- **Strict Avalonia**: Never use `System.Drawing`; always use Avalonia types.
- **Pure ViewModels**: ViewModels must **never** reference Avalonia types.
- **Bindings Over Code-Behind**: Logic should be driven by bindings.
- **DataTemplates**: Prefer explicit `DataTemplate`s and typed `DataContext`s.
- **VisualStates**: Avoid using `VisualStates` unless absolutely required.
## Zafiro Guidelines
- **Prefer Abstractions**: Always look for existing Zafiro helpers, extension methods, and abstractions before re-implementing logic.
- **Validation**: Use Zafiro's `ValidationRule` and validation extensions instead of ad-hoc reactive logic.
## DynamicData & Reactive Rules
### The Mandatory Approach
- **Operator Preference**: Always prefer **DynamicData** operators (`Connect`, `Filter`, `Transform`, `Sort`, `Bind`, `DisposeMany`) over plain Rx operators when working with collections.
- **Readable Pipelines**: Build and maintain pipelines as a single, readable chain.
- **Lifecycle**: Use `DisposeWith` for lifecycle management.
- **Minimal Subscriptions**: Subscriptions should be minimal, centralized, and strictly for side-effects.
### Forbidden Anti-Patterns
- **Ad-hoc Sources**: Do NOT create new `SourceList` / `SourceCache` on the fly for local problems.
- **Logic in Subscribe**: Do NOT place business logic inside `Subscribe`.
- **Operator Mismatch**: Do NOT use `System.Reactive` operators if a DynamicData equivalent exists.
### Canonical Patterns
**Validation of Dynamic Collections:**
```csharp
this.ValidationRule(
StagesSource
.Connect()
.FilterOnObservable(stage => stage.IsValid)
.IsEmpty(),
b => !b,
_ => "Stages are not valid")
.DisposeWith(Disposables);
```
**Filtering Nulls:**
Use `WhereNotNull()` in reactive pipelines.
```csharp
this.WhenAnyValue(x => x.DurationPreset).WhereNotNull()
```

View File

@@ -0,0 +1,19 @@
# Core Technical Skills & Architecture
## Mandatory Expertise
The developer must possess strong expertise in:
- **C# and modern .NET**: Utilizing the latest features of the language and framework.
- **Avalonia UI**: For cross-platform UI development.
- **MVVM Architecture**: Maintaining strict separation between UI and business logic.
- **Clean Code & Clean Architecture**: Focusing on maintainability and inward dependency flow.
- **Functional Programming in C#**: Embracing immutability and functional patterns.
- **Reactive Programming**: Expertise in DynamicData and System.Reactive.
## Architectural Principles
- **Pure MVVM**: Mandatory for all UI code. Logic must be independent of UI concerns.
- **Composition over Inheritance**: Favor modular building blocks over deep inheritance hierarchies.
- **Inward Dependency Flow**: Abstractions must not depend on implementations.
- **Immutability**: Prefer immutable structures where practical to ensure predictability.
- **Stable Public APIs**: Design APIs carefully to ensure long-term stability and clarity.

View File

@@ -0,0 +1,15 @@
# Naming & Coding Standards
## General Standards
- **Explicit Names**: Favor clarity over cleverness.
- **Async Suffix**: Do **NOT** use the `Async` suffix in method names, even if they return `Task`.
- **Private Fields**: Do **NOT** use the `_` prefix for private fields.
- **Static State**: Avoid static state unless explicitly justified and documented.
- **Method Design**: Keep methods small, expressive, and with low cyclomatic complexity.
## Error Handling
- **Result & Maybe**: Use types from **CSharpFunctionalExtensions** for flow control and error handling.
- **Exceptions**: Reserved strictly for truly exceptional, unrecoverable situations.
- **Boundaries**: Never allow exceptions to leak across architectural boundaries.

View File

@@ -0,0 +1,45 @@
# Common Patterns in Angor/Zafiro
## Refreshable Collections
The `RefreshableCollection` pattern is used to manage lists that can be refreshed via a command, maintaining an internal `SourceCache`/`SourceList` and exposing a `ReadOnlyObservableCollection`.
### Implementation
```csharp
var refresher = RefreshableCollection.Create(
() => GetDataTask(),
model => model.Id)
.DisposeWith(disposable);
LoadData = refresher.Refresh;
Items = refresher.Items;
```
### Benefits
- **Automatic Loading**: Handles the command execution and results.
- **Efficient Updates**: Uses `EditDiff` internally to update items without clearing the list.
- **UI Friendly**: Exposes `Items` as a `ReadOnlyObservableCollection` suitable for binding.
## Mandatory Validation Pattern
When validating dynamic collections, always use the Zafiro validation extension:
```csharp
this.ValidationRule(
StagesSource
.Connect()
.FilterOnObservable(stage => stage.IsValid)
.IsEmpty(),
b => !b,
_ => "Stages are not valid")
.DisposeWith(Disposables);
```
## Error Handling Pipeline
Instead of manual `Subscribe`, use `HandleErrorsWith` to pipe errors directly to the user:
```csharp
LoadProjects.HandleErrorsWith(uiServices.NotificationService, "Could not load projects");
```

View File

@@ -0,0 +1,43 @@
# Zafiro Reactive Shortcuts
Use these Zafiro extension methods to replace standard, more verbose Reactive and DynamicData patterns.
## General Observable Helpers
| Standard Pattern | Zafiro Shortcut |
| :--- | :--- |
| `Replay(1).RefCount()` | `ReplayLastActive()` |
| `Select(_ => Unit.Default)` | `ToSignal()` |
| `Select(b => !b)` | `Not()` |
| `Where(b => b).ToSignal()` | `Trues()` |
| `Where(b => !b).ToSignal()` | `Falses()` |
| `Select(x => x is null)` | `Null()` |
| `Select(x => x is not null)` | `NotNull()` |
| `Select(string.IsNullOrWhiteSpace)` | `NullOrWhitespace()` |
| `Select(s => !string.IsNullOrWhiteSpace(s))` | `NotNullOrEmpty()` |
## Result & Maybe Extensions
| Standard Pattern | Zafiro Shortcut |
| :--- | :--- |
| `Where(r => r.IsSuccess).Select(r => r.Value)` | `Successes()` |
| `Where(r => r.IsFailure).Select(r => r.Error)` | `Failures()` |
| `Where(m => m.HasValue).Select(m => m.Value)` | `Values()` |
| `Where(m => !m.HasValue).ToSignal()` | `Empties()` |
## Lifecycle Management
| Description | Method |
| :--- | :--- |
| Dispose previous item before emitting new one | `DisposePrevious()` |
| Manage lifecycle within a disposable | `DisposeWith(disposables)` |
## Command & Interaction
| Description | Method |
| :--- | :--- |
| Add metadata/text to a ReactiveCommand | `Enhance(text, name)` |
| Automatically show errors in UI | `HandleErrorsWith(notificationService)` |
> [!TIP]
> Always check `Zafiro.Reactive.ObservableMixin` and `Zafiro.CSharpFunctionalExtensions.ObservableExtensions` before writing custom Rx logic.

View File

@@ -39,7 +39,7 @@
"id": "agent-evaluation",
"path": "skills/agent-evaluation",
"name": "agent-evaluation",
"description": "\"Testing and benchmarking LLM agents including behavioral testing, capability assessment, reliability metrics, and production monitoring\u2014where even top agents achieve less than 50% on real-world benchmarks Use when: agent testing, agent evaluation, benchmark agents, agent reliability, test agent.\""
"description": "\"Testing and benchmarking LLM agents including behavioral testing, capability assessment, reliability metrics, and production monitoringwhere even top agents achieve less than 50% on real-world benchmarks Use when: agent testing, agent evaluation, benchmark agents, agent reliability, test agent.\""
},
{
"id": "agent-manager-skill",
@@ -177,7 +177,7 @@
"id": "backend-dev-guidelines",
"path": "skills/backend-dev-guidelines",
"name": "backend-dev-guidelines",
"description": "Comprehensive backend development guide for Node.js/Express/TypeScript microservices. Use when creating routes, controllers, services, repositories, middleware, or working with Express APIs, Prisma database access, Sentry error tracking, Zod validation, unifiedConfig, dependency injection, or async patterns. Covers layered architecture (routes \u2192 controllers \u2192 services \u2192 repositories), BaseController pattern, error handling, performance monitoring, testing strategies, and migration from legacy patterns."
"description": "Comprehensive backend development guide for Node.js/Express/TypeScript microservices. Use when creating routes, controllers, services, repositories, middleware, or working with Express APIs, Prisma database access, Sentry error tracking, Zod validation, unifiedConfig, dependency injection, or async patterns. Covers layered architecture (routes controllers services repositories), BaseController pattern, error handling, performance monitoring, testing strategies, and migration from legacy patterns."
},
{
"id": "cc-skill-backend-patterns",
@@ -369,7 +369,7 @@
"id": "copywriting",
"path": "skills/copywriting",
"name": "copywriting",
"description": "When the user wants to write, rewrite, or improve marketing copy for any page \u2014 including homepage, landing pages, pricing pages, feature pages, about pages, or product pages. Also use when the user says \"write copy for,\" \"improve this copy,\" \"rewrite this page,\" \"marketing copy,\" \"headline help,\" or \"CTA copy.\" For email copy, see email-sequence. For popup copy, see popup-cro."
"description": "When the user wants to write, rewrite, or improve marketing copy for any page including homepage, landing pages, pricing pages, feature pages, about pages, or product pages. Also use when the user says \"write copy for,\" \"improve this copy,\" \"rewrite this page,\" \"marketing copy,\" \"headline help,\" or \"CTA copy.\" For email copy, see email-sequence. For popup copy, see popup-cro."
},
{
"id": "core-components",
@@ -507,13 +507,13 @@
"id": "form-cro",
"path": "skills/form-cro",
"name": "form-cro",
"description": "When the user wants to optimize any form that is NOT signup/registration \u2014 including lead capture forms, contact forms, demo request forms, application forms, survey forms, or checkout forms. Also use when the user mentions \"form optimization,\" \"lead form conversions,\" \"form friction,\" \"form fields,\" \"form completion rate,\" or \"contact form.\" For signup/registration forms, see signup-flow-cro. For popups containing forms, see popup-cro."
"description": "When the user wants to optimize any form that is NOT signup/registration including lead capture forms, contact forms, demo request forms, application forms, survey forms, or checkout forms. Also use when the user mentions \"form optimization,\" \"lead form conversions,\" \"form friction,\" \"form fields,\" \"form completion rate,\" or \"contact form.\" For signup/registration forms, see signup-flow-cro. For popups containing forms, see popup-cro."
},
{
"id": "free-tool-strategy",
"path": "skills/free-tool-strategy",
"name": "free-tool-strategy",
"description": "When the user wants to plan, evaluate, or build a free tool for marketing purposes \u2014 lead generation, SEO value, or brand awareness. Also use when the user mentions \"engineering as marketing,\" \"free tool,\" \"marketing tool,\" \"calculator,\" \"generator,\" \"interactive tool,\" \"lead gen tool,\" \"build a tool for leads,\" or \"free resource.\" This skill bridges engineering and marketing \u2014 useful for founders and technical marketers."
"description": "When the user wants to plan, evaluate, or build a free tool for marketing purposes lead generation, SEO value, or brand awareness. Also use when the user mentions \"engineering as marketing,\" \"free tool,\" \"marketing tool,\" \"calculator,\" \"generator,\" \"interactive tool,\" \"lead gen tool,\" \"build a tool for leads,\" or \"free resource.\" This skill bridges engineering and marketing useful for founders and technical marketers."
},
{
"id": "frontend-design",
@@ -807,7 +807,7 @@
"id": "page-cro",
"path": "skills/page-cro",
"name": "page-cro",
"description": "When the user wants to optimize, improve, or increase conversions on any marketing page \u2014 including homepage, landing pages, pricing pages, feature pages, or blog posts. Also use when the user says \"CRO,\" \"conversion rate optimization,\" \"this page isn't converting,\" \"improve conversions,\" or \"why isn't this page working.\" For signup/registration flows, see signup-flow-cro. For post-signup activation, see onboarding-cro. For forms outside of signup, see form-cro. For popups/modals, see popup-cro."
"description": "When the user wants to optimize, improve, or increase conversions on any marketing page including homepage, landing pages, pricing pages, feature pages, or blog posts. Also use when the user says \"CRO,\" \"conversion rate optimization,\" \"this page isn't converting,\" \"improve conversions,\" or \"why isn't this page working.\" For signup/registration flows, see signup-flow-cro. For post-signup activation, see onboarding-cro. For forms outside of signup, see form-cro. For popups/modals, see popup-cro."
},
{
"id": "paid-ads",
@@ -825,7 +825,7 @@
"id": "paywall-upgrade-cro",
"path": "skills/paywall-upgrade-cro",
"name": "paywall-upgrade-cro",
"description": "When the user wants to create or optimize in-app paywalls, upgrade screens, upsell modals, or feature gates. Also use when the user mentions \"paywall,\" \"upgrade screen,\" \"upgrade modal,\" \"upsell,\" \"feature gate,\" \"convert free to paid,\" \"freemium conversion,\" \"trial expiration screen,\" \"limit reached screen,\" \"plan upgrade prompt,\" or \"in-app pricing.\" Distinct from public pricing pages (see page-cro) \u2014 this skill focuses on in-product upgrade moments where the user has already experienced value."
"description": "When the user wants to create or optimize in-app paywalls, upgrade screens, upsell modals, or feature gates. Also use when the user mentions \"paywall,\" \"upgrade screen,\" \"upgrade modal,\" \"upsell,\" \"feature gate,\" \"convert free to paid,\" \"freemium conversion,\" \"trial expiration screen,\" \"limit reached screen,\" \"plan upgrade prompt,\" or \"in-app pricing.\" Distinct from public pricing pages (see page-cro) this skill focuses on in-product upgrade moments where the user has already experienced value."
},
{
"id": "pc-games",
@@ -1335,7 +1335,7 @@
"id": "voice-agents",
"path": "skills/voice-agents",
"name": "voice-agents",
"description": "\"Voice agents represent the frontier of AI interaction - humans speaking naturally with AI systems. The challenge isn't just speech recognition and synthesis, it's achieving natural conversation flow with sub-800ms latency while handling interruptions, background noise, and emotional nuance. This skill covers two architectures: speech-to-speech (OpenAI Realtime API, lowest latency, most natural) and pipeline (STT\u2192LLM\u2192TTS, more control, easier to debug). Key insight: latency is the constraint. Hu\""
"description": "\"Voice agents represent the frontier of AI interaction - humans speaking naturally with AI systems. The challenge isn't just speech recognition and synthesis, it's achieving natural conversation flow with sub-800ms latency while handling interruptions, background noise, and emotional nuance. This skill covers two architectures: speech-to-speech (OpenAI Realtime API, lowest latency, most natural) and pipeline (STT→LLM→TTS, more control, easier to debug). Key insight: latency is the constraint. Hu\""
},
{
"id": "voice-ai-development",
@@ -1432,5 +1432,23 @@
"path": "skills/zapier-make-patterns",
"name": "zapier-make-patterns",
"description": "\"No-code automation democratizes workflow building. Zapier and Make (formerly Integromat) let non-developers automate business processes without writing code. But no-code doesn't mean no-complexity - these platforms have their own patterns, pitfalls, and breaking points. This skill covers when to use which platform, how to build reliable automations, and when to graduate to code-based solutions. Key insight: Zapier optimizes for simplicity and integrations (7000+ apps), Make optimizes for power \""
},
{
"id": "avalonia-layout-zafiro",
"path": "skills/avalonia-layout-zafiro",
"name": "avalonia-layout-zafiro",
"description": "Guidelines for modern Avalonia UI layout using Zafiro.Avalonia, emphasizing shared styles, generic components, and avoiding XAML redundancy."
},
{
"id": "avalonia-viewmodels-zafiro",
"path": "skills/avalonia-viewmodels-zafiro",
"name": "avalonia-viewmodels-zafiro",
"description": "Optimal ViewModel and Wizard creation patterns for Avalonia using Zafiro and ReactiveUI."
},
{
"id": "avalonia-zafiro-development",
"path": "skills/avalonia-zafiro-development",
"name": "avalonia-zafiro-development",
"description": "Mandatory skills, conventions, and behavioral rules for Avalonia UI development using the Zafiro toolkit."
}
]