This document defines the architectural and coding rules for the project. It is authoritative for all new code and refactors.
- Single Responsibility: every class has exactly one reason to change.
- Open/Closed: extend behavior via composition and interfaces; avoid modifying stable code paths when adding features.
- Liskov Substitution: derived types must be safely substitutable without altering expected behavior or contract.
- Interface Segregation: prefer small, focused interfaces; avoid "god" interfaces.
- Dependency Inversion: depend on abstractions; wire concrete types in the composition root only.
- Views are passive. No UI logic in code-behind beyond
InitializeComponent(). - All inputs are routed to ViewModels via bindings, commands, and behaviors.
- ViewModels are UI-framework agnostic and unit-testable.
- Models and services contain business logic and data access; ViewModels orchestrate them via DI.
- Prefer composition in ViewModels/services/code over inheritance wherever possible,
except where framework base types are required (e.g.,
ReactiveObjectfor ViewModels).
- UI (Avalonia Views + XAML): visual composition only.
- Presentation (ViewModels): state, commands, reactive composition.
- Domain/Services: business logic, parsing, validation, domain rules.
- Infrastructure: file system, persistence, external integrations.
- UI depends on Presentation; Presentation depends on Domain; Infrastructure is depended on by Domain or Presentation via interfaces.
- No reference from Domain to UI or Avalonia types.
Reference: https://github.com/AvaloniaUI/Avalonia
- Use XAML for layout and visuals; avoid creating controls in code.
- Define styles and resources in dedicated resource dictionaries and merge them
in
App.axamlto keep styling consistent and maintainable. - Prefer
StaticResourcefor immutable resources andDynamicResourcewhen runtime updates are required.
- Use compiled bindings only (no reflection bindings) with explicit
x:DataTypeon all binding scopes (views, DataTemplates, control themes, and resources). - Keep bindings one-way unless user input must update the ViewModel.
- Use
DataTemplatesor aViewLocator(custom, non-reflection) for view lookup.
- Use
StyledPropertyonly for values that must participate in styling. - Prefer
DirectPropertyfor non-styled properties to avoid extra overhead. - For best UI/UX, prefer custom control creation or re-templating using control themes instead of CRUD-style UI.
Reference: https://github.com/reactiveui/ReactiveUI
- All ViewModels inherit from
ReactiveObject. - Use
ReactiveCommandfor commands; never use event handlers in code-behind. - Use
WhenAnyValue,ObservableAsPropertyHelper, andInteraction<TIn,TOut>to model state, derived values, and dialogs. - Use
ReactiveUI.SourceGeneratorsfor INPC/ReactiveObject boilerplate where applicable. https://github.com/reactiveui/ReactiveUI.SourceGenerators
- Use
IScreenwith a singleRoutingStateas the navigation root. - All navigable ViewModels implement
IRoutableViewModel. - Views host navigation via
RoutedViewHost. - Use route segments that are stable, explicit, and testable.
- Use
ReactiveUI.Avalonia(latest) and do not useAvalonia.ReactiveUIdirectly. - If a third-party dependency requires
Avalonia.ReactiveUI(e.g., Dock integration), isolate it to the docking layer and do not reference it from app UI code. https://github.com/reactiveui/ReactiveUI.Avalonia
- Configure services in a single composition root (App startup).
- Use
AddSingleton,AddScoped,AddTransientcorrectly:- Singleton: thread-safe, shared, expensive-to-create services.
- Scoped: per-document or per-operation services created within explicit scopes.
- Transient: stateless lightweight services.
- Never resolve scoped services from singletons without creating a scope.
- Do not dispose services resolved from the container manually.
- Prefer allocation-free APIs:
Span<T>,ReadOnlySpan<T>,Memory<T>,ValueTask,ArrayPool<T>, andSystem.Buffers. - Use SIMD (
System.Numerics.Vector<T>or hardware intrinsics) where it provides measurable wins and keeps code maintainable. - Avoid LINQ in hot paths; use loops and pre-sized collections.
- Minimize boxing, virtual dispatch in tight loops, and avoid unnecessary allocations in render/update loops.
- Profile before and after optimizations; document expected gains.
- Avoid reflection whenever possible.
- Prefer source generators (incremental generators required) before any reflection-based approach.
- If reflection is the only viable option, ask the user explicitly before introducing it.
References:
-
All production code must be covered by unit tests; xUnit is required for unit testing.
-
UI tests must use Avalonia Headless (xUnit integration) and follow the headless testing guidance and helpers for input simulation.
-
Unit-test ViewModels and Domain services.
-
Use integration tests for parsing, IO, and docking layout persistence.
-
UI tests should validate navigation flows, docking, and editor behaviors.
- No code-behind event handlers.
- Avoid static state (except truly immutable constants).
- Prefer explicit types where clarity is improved; avoid
varin public APIs. - All public APIs must be documented and unit-tested.