Skip to content

Contributing

The conventions every change follows, to keep the codebase consistent, tested, and portable across the desktop, browser, and mobile heads.

Coding conventions

  • No static methods or properties — except Avalonia AvaloniaProperty.Register and framework metadata. Prefer instance members so everything stays injectable and testable.
  • Localization is resx-only. All translatable strings live in Strings.en/de.resx (or a domain-specific resx pair) and are referenced through LocalizationService. Both languages must carry every key. See Localization.
  • No empty catch blocks. Log through the app logger and surface user-facing failures through the dialog service.
  • A new FieldDefinition subtype changes nothing outside its own files — dispatch is virtual and registration is by type name, so there are no type-switches to update. See Adding a field type.
  • Missing a field type? Ship a simple version plus an on-screen note rather than silently skipping the use case.
  • No trademarked words in source files.
  • Keep dependencies conservative — official Microsoft or well-regarded community packages only, and prefer the built-in BCL over niche third-party libraries.
  • Credentials are handled carefully. Passwords use PBKDF2-HMAC-SHA512 with a per-user random salt, and the iteration count and algorithm are stored alongside the hash. Nothing is ever kept in plaintext or in any reversible form. See Accounts.

Tests are required

Every behaviour change — features and bug fixes alike — is developed test-first, and ships with all three test layers: unit, integration, and headless. Line coverage and the mutation score must not drop. The Testing page walks through the workflow in detail.

Avalonia gotchas worth knowing

  • Dynamic MenuItem submenus must be built in code-behind; XAML ItemsSource binding doesn't render submenus in this Avalonia version.
  • IsVisible on a null sub-path evaluates true when the object is null — add FallbackValue=False.
  • Never replace an ObservableCollection instance — mutate it in place (Clear() + Add()).
  • Compiled bindings are on by default — every DataTemplate needs x:DataType.

Pull requests and release notes

Every change ships as a pull request — never a direct commit to master. Two conventions make the release notes write themselves:

  • The PR title is the release note. Write it as a single, user-facing sentence describing the change the way it should read in the release notes, not as an internal summary. The release is built from these titles, so "Collapse the search filters into a Filters toggle on narrow windows" is right, while "refactor SearchBarViewModel" is not.
  • Label the PR so it lands in the right section. Release notes are grouped by label via .github/release.yml:
Label Section
feature Features
fix or bug Fixes

A PR with none of these labels does not appear in the release notes at all — that's how deployment, CI, chore, and docs-only changes are kept out. The flip side: if you forget to label a real feature or fix, it is silently dropped from the notes, so add the label when you open the PR.

Before you open a change

  • Make sure all three test layers are green and the coverage and mutation gates still pass.
  • For UI changes, verify manually in the running app with explicit repro steps — see Building.
  • Give the PR a release-note-quality title and the right feature/fix label (or leave it unlabeled if it should stay out of the notes).