Files
Clario/.claude/skills/avalonia/SKILL.md
Nouredeen06 d8dea1913a
All checks were successful
Build Linux / build (push) Successful in 1m8s
Added multi-currency support, account/budget management, and settings
- Primary account determines app-wide reference currency; all totals, charts, and summaries convert to it automatically using live rates

- Transactions show both converted and original amounts for cross-currency accounts; IsMultiCurrency recalculates on primary currency change

- Exchange rates fetched live on account save and broadcast via RatesRefreshed so all views update without a restart

- Account create/edit/delete with currency, icon, color, and primary toggle

- Budget create/edit/delete; savings goal dialog

- Settings view: display name, avatar upload, theme, language

- Removed currency selector from Settings (follows primary account)

- Fixed account list sort: primary first, then oldest CreatedAt, per group

- Fixed total balance overlap in dashboard accounts card
2026-04-03 02:39:51 +03:00

9.0 KiB

name, description
name description
avalonia Use when working on any Avalonia UI code — AXAML, control styling, bindings, control templates, animations, custom controls, platform differences, or LiveCharts2/Svg.Skia integration. Triggers on questions about Avalonia controls, properties, ControlThemes, styles, pseudo-classes, DataTemplates, ViewLocator, or any "how do I do X in Avalonia" question.

Avalonia UI Skill

You are working in an Avalonia UI project. This skill gives you accurate, verified knowledge about Avalonia and prevents hallucinating WPF-style patterns that do not work in Avalonia.


Step 1 — Check before you answer

NEVER answer from memory alone for:

  • Specific control properties or template part names
  • Pseudo-class selectors (:pointerover, :pressed, :focus, etc.)
  • Animation API (Animation, KeyFrame, Cue, Easing classes)
  • ControlTheme vs Style syntax differences
  • Platform-specific behaviors (mobile vs desktop)
  • LiveCharts2 or Svg.Skia properties

Always verify using one of these sources in order:

  1. Official docs: https://docs.avaloniaui.net/docs/reference/controls/{control-name}
  2. GitHub source (most reliable for exact property names): https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Controls/{ControlName}.cs
  3. Avalonia samples: https://github.com/AvaloniaUI/Avalonia.Samples

For styling/theming questions also check:

  • https://github.com/AvaloniaUI/Avalonia/tree/master/src/Avalonia.Themes.Fluent/Controls

Step 2 — Core Avalonia vs WPF differences

These are frequent sources of errors. Apply automatically:

Styling

<!-- Avalonia: CSS-like selectors -->
<Style Selector="Button.primary:pointerover /template/ ContentPresenter">
    <Setter Property="Background" Value="Blue"/>
</Style>

<!-- NOT WPF DataTriggers — those do not exist in Avalonia -->
<!-- NOT WPF Triggers — use pseudo-classes instead -->

ControlTheme (Avalonia 11+)

<!-- For re-theming built-in controls use ControlTheme, not Style -->
<ControlTheme x:Key="{x:Type Button}" TargetType="Button">
    <Setter Property="Template">
        <ControlTemplate>...</ControlTemplate>
    </Setter>
</ControlTheme>

Bindings

<!-- x:CompileBindings="True" (default) requires x:DataType -->
<!-- Use x:CompileBindings="False" on shell/dynamic views -->
<!-- DynamicResource NOT StaticResource for theme colors -->
<!-- No ElementName binding across UserControl boundaries — use RelativeSource or pass via property -->

No DataTriggers

Avalonia has no DataTriggers. Use instead:

  • Classes.myClass="{Binding SomeBool}" + style on .myClass
  • IsVisible="{Binding SomeBool}"
  • MultiBinding with converter

x:Name in code-behind

x:Name does NOT create direct fields in Avalonia. Access named controls via:

var btn = this.Get<Button>("PART_Button");       // throws if not found
var btn = this.FindControl<Button>("PART_Button"); // returns null if not found
// TranslateTransform cannot have x:Name — access via RenderTransform:
var tf = (TranslateTransform)someControl.RenderTransform!;

Animations in code-behind

var animation = new Animation
{
    Duration = TimeSpan.FromMilliseconds(320),
    Easing   = new CubicEaseOut(),
    FillMode = FillMode.Forward,
    Children =
    {
        new KeyFrame { Cue = new Cue(0d), Setters = { new Setter(TranslateTransform.YProperty, 0d) } },
        new KeyFrame { Cue = new Cue(1d), Setters = { new Setter(TranslateTransform.YProperty, 300d) } }
    }
};
await animation.RunAsync(targetControl);

Platform detection

bool isMobile = ApplicationLifetime is ISingleViewApplicationLifetime;
// App.IsMobile is the project's cached version

Step 3 — Known Avalonia gotchas from this project

ViewLocator (no DataTemplates in AXAML)

// ViewLocator auto-resolves: {Name}ViewModel → {Name}View (desktop) or {Name}ViewMobile (mobile)
// Do NOT register DataTemplates in AXAML
// Register FuncDataTemplate in App.axaml.cs code-behind if needed

Observable property initialization order

Object initializers set properties one by one — partial void On{Property}Changed fires immediately, before other properties are set. Never trigger initialization logic from property changed handlers when the VM needs multiple properties. Always use an explicit Initialize() method called after the object initializer.

// WRONG
partial void OnTransactionsChanged(List<Transaction> value) => ProcessData(); // Categories may be null

// RIGHT
var vm = new MyViewModel { Transactions = t, Categories = c, Accounts = a };
vm.Initialize(); // all props guaranteed set

ObservableCollection mutations

Mutating a List<T> never triggers binding updates. Replace the entire collection:

MyList = new List<T>(newItems); // triggers OnPropertyChanged
// NOT: MyList.Add(item); // binding won't update for List<T>

For ObservableCollection<T>, .Add() and .Remove() do trigger updates but .Clear() + re-add causes a full re-render. Prefer replacing the collection for large updates.

ScrollViewer + LiveCharts2

LiveCharts2 CartesianChart intercepts scroll events. Forward them manually:

protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
    base.OnAttachedToVisualTree(e);
    var charts = this.GetVisualDescendants().OfType<CartesianChart>();
    foreach (var chart in charts)
        chart.AddHandler(PointerWheelChangedEvent, OnChartScroll, RoutingStrategies.Tunnel);
}
private void OnChartScroll(object? sender, PointerWheelEventArgs e)
{
    var sv = this.GetVisualAncestors().OfType<ScrollViewer>().FirstOrDefault();
    if (sv is null) return;
    sv.Offset = new Vector(sv.Offset.X, sv.Offset.Y - e.Delta.Y * sv.SmallChange.Height * 3);
    e.Handled = true;
}

Half-donut chart

<Border Height="150" ClipToBounds="True">
    <lvc:PieChart Series="{Binding ...}" Height="300" Margin="0,0,0,-150"
                  InitialRotation="-180" MaxAngle="180" LegendPosition="Hidden"
                  ZoomMode="None"/>
</Border>

Svg.Skia CSS

<!-- stroke-based (Lucide icons) -->
<Svg Path="../Assets/Icons/name.svg" Css="{DynamicResource SvgBlue}"/>

<!-- SvgBlue resource = "path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #7B9CFF; }" -->
<!-- Fill-based icons use SvgFillBlue etc. -->

Mobile-specific AXAML rules

  • No BoxShadow — GPU expensive, causes jitter
  • No MinWidth/MinHeight on UserControl root
  • Add Classes="mobile" to root element for mobile-specific style overrides
  • Use VirtualizingStackPanel in ItemsControl for long lists
  • Page size 10 on mobile vs 25 on desktop

CalendarDayButton / Calendar

Avalonia's Calendar uses CalendarDayButton not CalendarDayItem. Template parts: PART_MonthView, PART_YearView, PART_HeaderButton, PART_PreviousButton, PART_NextButton.

FlyoutPresenter

<!-- Custom transparent flyout presenter must be a ControlTheme in Resources, not Styles -->
<ControlTheme x:Key="TransparentFlyoutPresenter" TargetType="FlyoutPresenter">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="Padding" Value="0"/>
</ControlTheme>

TextBox ghost class

<!-- Transparent textbox that works in all states -->
<Style Selector="TextBox.ghost">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="Padding" Value="0"/>
    <Setter Property="FocusAdorner" Value="{x:Null}"/>
</Style>
<Style Selector="TextBox.ghost:pointerover /template/ Border#PART_BorderElement">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="0"/>
</Style>
<!-- Also add :focus and :disabled variants -->

Step 4 — How to look up unfamiliar Avalonia APIs

For a control's properties:

Fetch: https://docs.avaloniaui.net/docs/reference/controls/{control-name-lowercase}

For template part names (e.g. what's inside a ComboBox):

Search GitHub: https://github.com/search?q=repo:AvaloniaUI/Avalonia+PART_+{ControlName}&type=code
Or fetch: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/{ControlName}.axaml

For pseudo-class selectors:

Fetch: https://docs.avaloniaui.net/docs/reference/styles/pseudo-classes

For animation classes (Easing, FillMode, etc.):

Fetch: https://docs.avaloniaui.net/docs/guides/graphics-and-animations/animation

For ColorPicker internals:

Fetch: https://raw.githubusercontent.com/AvaloniaUI/Avalonia/refs/heads/master/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

Step 5 — Response format

  1. State what you verified and where
  2. Provide the correct AXAML or C# with no WPF-isms
  3. Flag any Avalonia version caveat if relevant (project uses 11.x)
  4. If something cannot be done via AXAML, explain the code-behind approach
  5. Never guess at property names — fetch source if uncertain