FieldWorks Avalonia UI
SkillWeb & browsingBuild, review, or fix Avalonia UI code in FieldWorks: XAML, MVVM, view models, owned controls, headless tests, preview host, accessibility identity, and product-vs-preview wiring. Use for any change under Src/Common/FwAvalonia/, Src/Common/FwAvaloniaPreviewHost/, or Src/**/*.Avalonia/, and for net48/net8 Avalonia test changes — even if the request only mentions a control, a binding, a style, or a flaky UI test. For whole-surface migration planning use fieldworks-winforms-to-avalonia-migration first.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the FieldWorks Avalonia UI skill
What this skill tells your AI
The instructions your AI receives, as published by sillsdev/fieldworks in .claude/skills/fieldworks-avalonia-ui/SKILL.md and read by ahel’s review.
Use This For
- Avalonia XAML, view models, commands, lifetimes, dispatching, and resource/style changes.
- New or changed projects under
Src/**/**/*.Avalonia/,Src/Common/FwAvalonia/, andSrc/Common/FwAvaloniaPreviewHost/. - Preview Host module registration, sample data providers, and UI
diagnostics (see
.github/instructions/avalonia.instructions.mdfor build/preview commands and project layout rules). - UI host wiring that selects between Avalonia and legacy UI — apply
fieldworks-ui-wiring-reviewalongside this skill.
Start From the Established Patterns
Do not design controls or seams from scratch. The migration hub skill
(fieldworks-winforms-to-avalonia-migration) documents the decided
architecture; its
../fieldworks-winforms-to-avalonia-migration/references/architecture-patterns.md
covers owned controls, writing-system text fields, dialogs/flyouts,
validation, and lifetime. Canonical code to imitate:
- Owned field controls:
Src/Common/FwAvalonia/Detail/FwFieldControls.cs,FwOptionChooser.cs,DetailMenuFlyout.cs,HoverReveal.cs - Detail view + focus memory:
DataTree.cs,DetailFocusMemory.cs - Seams (scheduler, lifetime, clipboard, edit sessions):
Src/Common/FwAvalonia/Seams/ - Headless test setup:
Src/Common/FwAvalonia/FwAvaloniaTests/TestAppBuilder.cs; examples inDetailEditingTests.cs,VisualParityAndDensityTests.cs - Density constants:
Src/Common/FwAvalonia/FwAvaloniaDensity.cs - Dialog stack (XAML + CommunityToolkit.Mvvm + compiled bindings):
Src/Common/FwAvaloniaDialogs/—LexOptionsDlgView.axaml/.axaml.cs+LexOptionsDlgViewModel.cs; headless tests inFwAvaloniaDialogsTests/. This is the verified template for hand-authored dialogs — see "Converting a WinForms dialog (MVVM dialog stack)" below.
Re-implementing a Phase-1 deferred screen (JIRA tickets). The full recipe (per-screen
Docs/migration/<screen>.md on the never-merged phase1-docs branch, stub recovery from git
history, which canonical screen to copy, UIMode=New re-wiring) is canonical in the migration
hub skill — .claude/skills/fieldworks-winforms-to-avalonia-migration/SKILL.md; start there.
Converting a WinForms dialog (MVVM dialog stack)
Hand-authored dialogs/wizards use XAML + CommunityToolkit.Mvvm + compiled
bindings — NOT the region/IR pattern (that is only for XML-view-definition
views). Full step-by-step, the working template, and the decision
history/rationale pointer: references/dialog-conversion.md. The shape, per
dialog:
- View
XyzDialogView.axaml(+.axaml.cs): aUserControl(not aWindow— see modality below),x:DataTypeset to the view-model, compiled{Binding}s, and a stableAutomationProperties.AutomationIdon every interactive control. Reuse owned controls (FwMultiWsTextField,FwOptionChooser) for writing-system fields and list pickers. - View-model
XyzDialogViewModel.cs:ObservableObjectwith[ObservableProperty]state and[RelayCommand]actions; expose the result (e.g.Accepted). Keep it LCModel-free for the view; bind real settings/domain through the app-settings/edit-session seams. - Tests
XyzDialogTests.cs(headless[AvaloniaTest]): assert the compiled bindings propagate both directions and the commands fire, plus the per-stage PNG captures and subjective checks under "Dialog spacing" below — together, the per-dialog definition of done.
Style system (density + borders, per view)
The font/density tokens and the field-border rule are a GLOBAL system, calibrated to WinForms density —
not the roomy Fluent defaults — and applied per-control-tree (the only mechanism that renders in BOTH the
runtime host and the headless tests). Full detail, the calibrated numbers, and the per-view intent:
references/style-system.md. Headlines: dialog inputs are BOXED (Border.fwFieldHost),
detail/region values are FLAT with subtle separators, browse keeps its grid lines — just denser
font everywhere; one source of truth per family (DialogTheme.axaml for dialogs, FwSurfaceStyles for
region/browse); anything that must render headlessly uses a CONCRETE value, never a Fluent
DynamicResource.
Dialog spacing
All dialog spacing/borders come from the shared tokens in
Src/Common/FwAvaloniaDialogs/DialogTheme.axaml (applied to each dialog body by
DialogThemeBootstrap.Apply(this), called from every dialog view ctor). See style-system.md's "Dialog
spacing tokens" table for the current calibrated values — that table is the single source of truth; don't
copy the numbers here too. Headlines (full rules + rationale:
references/dialog-conversion.md §2a-bis):
- Every dialog root carries
Classes="fwDialogRoot"(window padding); no rootMarginliteral. - No text-bearing or
PART_*Hostcontrol with 0 padding — host borders carryClasses="fwFieldHost". - OK/Cancel use the standard button-strip gap tokens.
- Never hardcode a margin/spacing literal — use a token; add new tokens to
DialogTheme.axaml. - The headless
DialogLayoutAssert.AssertNoCrowding(view)tripwire gates this in every dialog's realized-view test. - Capture a PNG at EACH interaction stage via
DialogSnapshot.Capture(view, "<Prefix>-<NN>-<stage>")(→ flat gitignored folderOutput/Snapshots/<Prefix>-<NN>-<stage>.png), then Read each PNG and answer the six subjective-quality questions — a hard rule and part of the per-dialog definition of done, for detail/browse views too. The canonical checklist, the six questions, and the capture → run → Read → judge → fix → re-capture loop:references/visual-snapshot-testing.md.
Rules specific to dialogs:
- It lives in
Src/Common/FwAvaloniaDialogs/(the dedicated XAML project), never in the pure-C#FwAvaloniafoundation. Avalonia projects — including the XAML-compiled ones — are ordinary members of theFieldWorks.projtraversal (theSrcglob); a new dialog project just needs adding toFieldWorks.sln(restore + VS). Exclude any nested test folder from the library's compile glob (<Compile Remove="XxxTests/**/*.cs"/>). - Modality during coexistence: no Avalonia
Window.ShowDialog— show the dialogUserControlviaAvaloniaDialogHost.ShowModal; the view-model implementsIDialogViewModeland raisesCloseRequested(bool)from OK/Cancel. Mechanics + code:references/dialog-conversion.md§2. - Coexistence sync with the WinForms twin: while both implementations
ship, they are edited together — the apply-order mirroring, divergence
register, and paired-edit rules live in the
dialog-updateskill. - Scope: simple/confirmation/settings dialogs are good junior+AI work;
Views-engine-coupled dialogs (Find/Replace, Styles host
IVwRootSite) belong with the document engine (Stage 9), NOT this stack.
Required Checks
- Use current Avalonia docs for uncertain APIs; do not guess dispatcher, headless, automation, or binding behavior.
- Keep field labels on the StringTable strategy. Product-facing
FieldWorks-owned strings go in the project
.resxand are consumed via the string accessor (FwAvaloniaStrings/FwAvaloniaDialogsStrings), never hardcoded; the neutral resx is the English source of truth. Prototype hardcoded strings must be called out as gaps. - Stamp stable, nonlocalized
AutomationProperties.AutomationId(derived from IRStableIdwhere applicable) and localizedAutomationProperties.Nameon user-facing controls. - UI logic stays in bindings/view models where practical; avoid logic-heavy code-behind.
- For any Avalonia "select from a list" control, prefer the shared
FwOptionChooserpattern inSrc/Common/FwAvalonia/Detail/FwOptionChooser.cs(AutoCompleteBox-based, keyboard-safe, search-capable, compact density) over ad hocListBoxpopups or one-off editable selectors. Reach for a rawComboBoxonly when the UX explicitly needs an always-visible inline combo rather than the shared flyout selector. - Do not fix Avalonia keyboard, focus, filtering, selection, popup, or
rendering bugs by patching
System.Windows.Formshosts, WinForms interop message handling, or other legacy host-only routes unless the task explicitly targets interop behavior. Default to fixing the issue inside the Avalonia control tree or Avalonia-owned seams. - Marshal to the UI thread through
IUiScheduler(or Avalonia dispatcher in non-region code); no hiddenTask.Run, no sync-over-async. - Keep preview data lightweight unless the change explicitly opts into LCModel/project data; product-facing paths use real edit-session/domain contracts — detached DTO-only models remain preview-only.
- Headless tests: simulate input on
Window, flush withDispatcher.UIThread.RunJobs(), and capture visual regression frames with Skia (UseHeadlessDrawing=false+CaptureRenderedFrame()). - Resx satellite assemblies need no runtime bootstrap; only tests that exercise genuine Chorus-supplied UI need an L10NSharp LocalizationManager.
- Evidence runs through
./build.ps1and./test.ps1via the normal repo graph, not branch-only build paths.
Review Red Flags
- A Common project directly references a feature module without an explicit architecture decision.
- Preview-only code launched from product UI without a feature gate.
- Tests manually call
OnPropertyChanged(...),ShowRecord(), or similar instead of proving the real broadcast/wiring path. - The active Avalonia path drives hidden legacy rendering/menu infrastructure (see the hub skill's hard rules).
- Sleep-based or timing-sensitive UI tests.
- Claims of accessibility, localization, IME, or keyboard parity without
executable evidence (see the hub skill's
../fieldworks-winforms-to-avalonia-migration/references/parity-evidence.md§"Evidence language").
Evidence for a PR or a ticket
Avalonia is the surface where capture is automated, so a visible change ships with a picture. Do not leave it to the reviewer to imagine the before and after.
- Capture both states from a permanent headless test, not a throwaway
fixture, so the evidence regenerates.
references/visual-snapshot-testing.mdhas the harness. - Assert the behaviour deterministically in that same test. The test is the evidence; the screenshot is the courtesy. Keep PNGs as subjective evidence rather than pixel-golden tests.
- Trim, caption and label before publishing, then upload by the routes in
.claude/references/evidence.md.
Label every capture control-level headless, never "screenshot of FLEx", unless the product actually drives the code path. When an operation exists but nothing writes it at runtime yet, say so beside the image; a reader who assumes otherwise believes a feature has shipped.
Captures belong in Output/ManualEvidence/<TICKET>/, which is gitignored.
Do not commit files from Output.
Handoff
Report Avalonia docs consulted, tests run, remaining prototype gaps, whether the change is product-facing or preview-only, and how the live wiring path was validated for each affected host. For parity work, say whether visual evidence is control-level headless capture or live desktop capture, and which automation identities were assigned.
Keep This Skill Current
When a control pattern, headless-test technique, or Avalonia API gotcha
proves out (or a pointer above goes stale), update this skill in the same
PR — and route durable architecture lessons through the protocol in
../fieldworks-winforms-to-avalonia-migration/references/lessons-learned.md.
Signals
- GitHub stars
- 111
- Forks
- 42
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
fieldworks-avalonia-ui- Source
- github.com/sillsdev/fieldworks