Skip to content
Back to skills

Convert Dialog

ASecurity

Drive the conversion of one WinForms dialog to Avalonia through analysis, developer alignment, integration-test planning, exemplar mapping, design, and scaffold/implement. Use when the user invokes /convert-dialog with a dialog class name, or asks to start converting a specific WinForms dialog.

  • 111 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 3, 2026
designgoexpressawsgit

Security analysis

A100/100

Scanned September 20, 2026

npx -y skills add sillsdev/FieldWorks --skill convert-dialog --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Convert Dialog?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Convert Dialog
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sillsdev-convert-dialog/badge)](https://www.skillsdirectory.com/skills/sillsdev-convert-dialog)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: convert-dialog
description: Drive the conversion of one WinForms dialog to Avalonia through analysis, developer alignment, integration-test planning, exemplar mapping, design, and scaffold/implement. Use when the user invokes /convert-dialog with a dialog class name, or asks to start converting a specific WinForms dialog.
---

# Convert Dialog

Converts one WinForms dialog to Avalonia with the developer in the loop at
every gate. The developer decides and confirms; this skill analyzes, drafts,
builds, and never advances past a gate without their explicit go-ahead.

## Two rules that override everything else

**1. The developer's FieldWorks is untouchable.** They are exploring the
dialog while you work. NEVER close, kill, restart, or drive their running
FieldWorks, and never change the UI-mode registry setting under them. The
live-app capture route (fieldworks-winapp) owns the app lifecycle
(relaunch-per-tool, `CloseMainWindow`) -- it is FORBIDDEN while their
instance is open. Do not run `build.ps1`/`test.ps1` without asking either: a
build fails on binaries their running app holds locked, and killing their
process to unblock a build is never acceptable. If a build or test fails on
locked files, STOP and ask.

**2. Every gate is a real stop.** A gate is an interactive question to the
developer (use the question tool, which returns only when they answer) --
never a sentence in a message you then continue past. At a gate: end your
turn on the question and take no further action. Do NOT summarize or restate
a report's contents in chat -- the document is the artifact, and a chat
summary invites skipping the review the gate exists for. Point at the file
and stop.

## How to talk about where you are

The stages below have names. USE THEM with the developer, in plain language
about the work: "I've finished understanding the dialog", "we've agreed on
how it works, so next is deciding what to prove", "the replacement plan is
ready for your review". NEVER say "Phase 2", "phase 4 gate passed", or any
numbered-stage shorthand -- it means nothing to someone who has not read
this file. The stages, in order: **Understanding the dialog** -> **Agreeing
on how it works** -> **Deciding what to prove** -> **Planning the
replacement** -> **Building it** -> **Proving it works**.

Input: the WinForms dialog class name (e.g. `MergeEntryDlg`).
Scope: PORT with low-cost improvements. If the developer's verdict is
"redesign, not port", stop after Understanding the dialog -- redesign is out of
scope; the dialog waits.

## Working documents

All working artifacts live in `Docs/migration/working/<DialogClass>/`
(gitignored; create on first use):

- `<DialogClass>-analysis.md` -- from Understanding the dialog
- `<DialogClass>-integration-test-plan.md` -- from Deciding what to prove
- `<DialogClass>-design.md` -- from Planning the replacement
- `<DialogClass>-state.md` -- one-page state: current phase, plus any OPEN
  GATE and what it waits on (e.g. `waiting-on: MSAGroupBox (control,
  Docs/migration/working/MSAGroupBox/)`); kept current at every gate and
  phase transition

**Resume rule:** on invocation, read `<DialogClass>-state.md` first
(falling back to artifact-presence inference when it is absent: analysis
present -> resume at Agreeing on how it works; test plan -> Planning the replacement; design ->
scaffold/implement choice). Then RE-EVALUATE every open gate against
reality, not the note: if the exemplar map now carries the awaited
control's row, or the awaited child dialog now has an Avalonia route, the
gate self-clears and the dialog resumes past it. If a gate is still
blocked, drop into the blocker's own sub-cycle where IT left off (its
working dir carries its sub-phase by the same rules -- resumable
recursively). State what was found and CONFIRM with the developer before
proceeding. Never silently redo a completed phase.

The working documents remain in place after completion; what to keep,
delete, or attach anywhere is the developer's call.

### Citing other documents and sections

Every reference -- to another document OR to a section of the document you
are writing -- is a markdown link whose VISIBLE TEXT is the target's name.
Never write a bare section number: a number from another document collides
with the local numbering and resolves to nothing.

- Cross-document citations also carry the target's stable id when it has
  one, e.g. `[per-control F1 help (GAP-F1-HELP)](../../../../.claude/skills/fieldworks-winforms-to-avalonia-migration/references/control-exemplar-map.md#gap-f1-help)`.
- Verify the anchor exists by READING the target's heading before writing
  the link -- do not guess. GitHub's slug rule: lowercase, spaces to
  hyphens, punctuation dropped.
- Compute the relative path from the document being written. Working
  documents sit at `Docs/migration/working/<DialogClass>/`, four levels
  below the repo root.
- Keep the headings you generate short and ASCII so their auto-anchors stay
  predictable.
- Numbers may stay in headings for structure; they never appear as a
  citation.

## Understanding the dialog (read-only; safe to run while they explore)

The steps below are pure reading -- source, git history, Jira, layout XML. No
build, no test run, no app automation. This is what makes the phase safe to
run concurrently with the developer's own exploration of the live dialog.
The "before" evidence (the last step here) is NOT part of that concurrent work.

Before source analysis, read `Docs/lessons/avalonia-migration/README.md` and
any card matching the dialog's capabilities. Carry applicable constraints and
rejected assumptions into the analysis only after verifying them against the
current tree and legacy behavior; a card never authorizes an old design.

1. Read the dialog source and every related file (designer, resx, helpers).
2. Pull the file history and investigate related Jira issues (read-only
   Atlassian tooling); document them chronologically as framing context for
   why the dialog's features exist.
3. Find and document ALL calling sites and the conditions under which each
   opens the dialog. Check every launch-site kind the conversion documents
   call out: launcher edges (`Lcm*DialogLauncher`), xCore command handlers
   and dialog listeners (the `EntryDlgListener`/`RecordDlgListener`
   pattern), menu/toolbar command XML, DetailControls slice launchers (the
   `ReferenceLauncher` "..." family), popup-tree manager items ("More..."/
   "Create..." -- `MSAPopupTreeManager` and kin), other dialogs that chain
   into this one, context menus, and direct static `RunDialog`-style helpers
   (e.g. `ValidCharactersDlg.RunDialog`).
4. Analyze the dialog's logic and its interaction with data, internal and
   external: static calls, LCM objects, and any LCM Units of Work --
   including the undo/redo task labels it uses.
5. Build the interaction picture: every supported user flow through cancel /
   OK / apply (and next / back / finish for wizards); whether the dialog is
   modal or MODELESS; the help topic; every CHILD dialog or message box the
   dialog itself opens (each is a conversion dependency -- catalog them
   explicitly).
6. Catalog the enabled/disabled state of every control and everything that
   affects it.
6b. Inventory the dialog's localizable strings (resx usage) and note the
   project-data prerequisites needed to exercise each flow (these feed the
   test plan and the manual checkpoint).
7. "Before" evidence -- NEVER while their FieldWorks is open. Ask which the
   developer prefers, and wait for the answer:
   - **They capture it** (default while they are exploring): they are
     already looking at the dialog, so ask them to grab the screenshots and
     say where they put them. Zero risk to their session.
   - **The harness captures it** (unattended, repeatable): add a
     `Cap`/`CapLoop` case to `ScreenshotHarnessTests` and run
     `.\test.ps1 -SkipNative -TestProject LexTextControlsTests -TestFilter
     "FullyQualifiedName~ScreenshotHarness"` -- but ONLY once they confirm
     FieldWorks is closed (it is an in-process `DrawToBitmap` render that
     never touches the desktop, yet the build it needs fails on binaries
     their running app locks).
   The live-app automation route is a last resort for behavior the harness
   cannot fake, and only with their explicit go-ahead that the app is yours
   to drive.

Write `<DialogClass>-analysis.md` with exactly these sections:

1. **Dialog purpose**
2. **Data interaction**
   - A. All model data displayed and modified, including WHEN modification
     occurs (on OK/apply, on focus change, etc.)
   - B. Any properties or settings displayed or modified, including when
3. **Control interactions** -- interactions between controls, specific about
   enabled/disabled state and what drives it
4. **Layout strategy** -- current control layout, control sizes and minimum
   sizes, resizing behavior, and any persisted bounds/splitter state

## Agreeing on how it works (gate)

When the analysis document is written, STOP. Ask -- via the question tool,
then end your turn -- "I've finished my analysis. Are you ready to align our
understanding?" Do not describe what you found. Do not summarize the
document. Wait.

When they say yes, point them at the file (path only) and ask whether they
see errors, gaps, or have questions -- then stop again and wait. When they
paste a section back with a correction, fix the document and ask again.
Re-ask with varied phrasings ("anything else that looks off?", "any
interaction I've missed?") until you get a clear negative. Each round is its
own stop: one question, end of turn.

The gate is the developer declaring the document an accurate description of
the current dialog.

## Deciding what to prove (gate)

Ask the developer for guidance on integration-test creation -- one question,
end of turn, wait -- then design the test set for the dialog's capabilities
WITH them using the `grill-with-docs` skill (one question at a time, each its own
stop, recommendations offered) until the set is defined.
Every test item must be expressible as: drive the scenario, assert the
behavioral outcome, capture a labeled snapshot.

Save the result as `<DialogClass>-integration-test-plan.md`. The
create-integration-test skill consumes this file verbatim.

## Planning the replacement (gate)

0. FIRST check whether an existing shared dialog already covers it: if the
   dialog belongs to a family one serves (the EntryGo/BaseGoDlg family, the
   ChooserDialog family), the conversion is a new consumer + launcher,
   not a new dialog -- present that finding and shrink the remaining phases
   accordingly.
0b. DEPENDENCY GATE -- children convert first: every child dialog the
   target opens must already have an Avalonia route (converted, covered by
   a shared dialog, or a plain message box via `FwMessageBox`). If any child lacks
   one, present the dependency tree with a recommended conversion order,
   record the open gate and its waiting-on list in
   `<DialogClass>-state.md`, and ASK the developer whether to start
   converting the deepest unconverted child NOW, in this session. Yes:
   launch convert-dialog for that child class (from context) and carry it
   forward -- the parent resumes when the child completes. No: pause this
   conversion; resume re-evaluates the gate.
1. Analyze every WinForms control and design pattern in the dialog.
2. Map each against `.claude/skills/fieldworks-winforms-to-avalonia-migration/references/control-exemplar-map.md` (the exemplar map
   in the migration skill).
3. From the analysis document's layout section, propose an Avalonia layout
   that AT MINIMUM replicates the WinForms capability; exceed it where the
   effort is low.
4. For items with no exemplar: catalog the item's purpose and capabilities,
   then look for an adequate replacement in Avalonia packages -- both those
   already imported and others not yet used (an un-imported package is a
   finding to surface, not a decision to make alone).

**Custom-control sub-cycle.** A CUSTOM WinForms control with no exemplar
gets a capability-first treatment -- what it does, never how it is painted:

1. From the dialog analysis and the control's own source, list every
   capability the control provides.
2. Produce a capability-comparison table: capability -> the stock Avalonia /
   composed / package answer -> classified **covered**,
   **possible-but-costly**, or **not-possible**.
3. The DEVELOPER makes the convert-vs-replace verdict on that table.
   Default bias is stock/composed (unnecessary owned controls are the
   snowflakes the idiomatic audit removed). Not-possible earns an owned
   conversion; possible-but-costly is the developer's judgment.
4. If the verdict is CONVERT: the control becomes its own conversion, and
   **the control converts first**. Inform the developer and ASK whether to
   start converting it NOW, in this session. Yes: open
   `Docs/migration/working/<ControlClass>/` with its own analysis / design
   / test-plan / state documents, record the gate in
   `<DialogClass>-state.md`, and run the control's cycle (align -> grilled
   test plan -> design -> implement -> create-integration-test ->
   exemplar-map row), then resume the dialog with the control as an
   available exemplar. No: record the gate the same way and pause this
   conversion; resume re-evaluates the gate.
5. Placement rule for a new owned control (the two-assembly layering IS the
   legacy FwCoreDlgs/FwCoreDlgControls separation -- FwAvalonia is the
   controls layer, FwAvaloniaDialogs the dialogs layer):
   - used by dialogs only -> `Src/Common/FwAvaloniaDialogs/Controls/` (the
     dialog-composite subfolder; dialogs themselves stay at the project
     root);
   - shared with the detail view or other non-dialog views ->
     `Src/Common/FwAvalonia/` (in `Detail/` when detail-specific, the root
     when general);
   - tests mirror the SUT's folder either way.

Produce the design report with these sections:

1. Each WinForms control type -> its Avalonia exemplar -> the proposed
   replacement
2. Each WinForms code pattern identified -> the proposed Avalonia
   replacement
3. Each control/pattern with NO exemplar -> a proposed replacement, or an
   explicit "none found"
4. The proposed Avalonia layout, with every deliberate difference from the
   WinForms version called out

Every reference the report makes -- to the exemplar map, to a conversion
document, or to one of its own sections -- follows
[Citing other documents and sections](#citing-other-documents-and-sections).

Point the developer at the report and ask them to review it for errors,
gaps, and suggestions -- then stop and wait, without summarizing its
contents. Capture the agreed result as `<DialogClass>-design.md`. If a missing conversion or
capability surfaced, run `grill-with-docs` with the developer to produce the fill
plan (the exemplar-promotion path: the first implementation is built on the
existing idiom rules and, on the developer's approval, a
control-exemplar-map row lands in the same PR naming it THE exemplar --
draft the row and show it before committing).

## Building it (developer's choice)

Offer the choice explicitly -- as a question, then stop and wait:
**generate scaffold** or **implement design**.

**Merge policy: scaffold is branch-state only.** The scaffold/implement
choice is about how much the AI builds first, not what ships -- nothing
merges until Proving it works passes (implemented and verified), so preview users
never meet an empty dialog.

**Scaffold** means: create the Avalonia files following
`.claude/skills/fieldworks-avalonia-ui/references/dialog-conversion.md`; the result
compiles and launches (empty); and EVERY launch site from the analysis
document's calling-sites section is adapted to open the Avalonia or WinForms
dialog based on the UIMode setting (fail-closed: Legacy stays the default).

**Implement** means: scaffold first, then build out the dialog from
`<DialogClass>-design.md` and `<DialogClass>-integration-test-plan.md`.

Build conventions the result must satisfy (confirm each in the diff):

- Naming: legacy stem + role suffix (`FooDlgInput` / `FooDlgView.axaml` /
  `FooDlgViewModel` / `FooDlgPayload`) in `Src/Common/FwAvaloniaDialogs/`;
  shared dialogs keep general names.
- Boundaries: the Input carries everything LCModel-free the dialog needs;
  the ViewModel never touches LCModel; the launcher owns all LCModel work inside
  one undo task with the legacy undo text.
- Presentation, modal: `AvaloniaDialogHost.ShowModal` with the WinForms
  owner -- never an Avalonia-owned window during coexistence; owner icon or
  none; focus returns to the invoking control.
- Presentation, modeless: no exemplar exists yet -- the FIRST modeless
  conversion designs the hosting pattern through the exemplar-gap path
  (`grill-with-docs`), with the constraint that WinForms owns the window during
  coexistence (an Avalonia control hosted in a modeless WinForms Form via
  the host control), and promotes it as the modeless exemplar.
- Strings in `.resx` (accessors appended in order); no L10NSharp; no
  hardcoded UI text. Concrete brushes only. Validation through
  `DialogViewModelBase.GetValidationErrors` with the inline-error exemplar;
  help through `HelpRequested` -> launcher.
- Watch-outs: `Flyout` over free `Popup`; arrow/Enter keys claimed at the
  host; WS-sensitive inputs are never plain TextBoxes (Go-family exemplar);
  match the legacy commit semantics (OK-gated vs commit-on-select) and say
  which in the ViewModel summary.
- Accessibility and keyboard parity: stable automation ids per the owned
  control convention (pinned by `OwnedControlAutomationConventionTests`);
  tab order and mnemonics match the legacy dialog.
- The repository comment standard applies to everything written: see
  `.claude/skills/fieldworks-code-commenting/SKILL.md`.

## Proving it works

1. Run create-integration-test against the plan (TDD before implementation,
   or verification after; if the developer hand-implemented inside the
   scaffold, first evaluate their implementation against
   `<DialogClass>-design.md` and report deviations).
2. The Avalonia visual test emits the paired `<name>-after.png` (same data
   flavor as the `-before`).
3. Ask the developer to manually test -- then stop and wait for their
   findings: walk the analysis document's the Data interaction and Control interactions sections line by line against
   the live dialog in New UI mode, and compare against the `-before`
   captures. They own the app for this; do not drive it or change its UI
   mode for them.
4. Legacy-mode smoke: with the toggle OFF, every launch site from the
   analysis document still opens the legacy dialog unchanged.
5. Work each manual finding through
   [The manual-finding loop](#the-manual-finding-loop).
6. Add any new exemplars created during this conversion to the exemplar map
   (the promotion row from Planning the replacement, if not already landed).
7. Land: comment audit against the repository standard, preflight the
   branch, and PR per the repo's conventions.

### The manual-finding loop

A green suite that missed a defect the developer found by looking is evidence
the SUITE has a hole, not just the code. Close both.

1. **Triage the layer before writing anything.** ViewModel, launcher edge, or
   rendered view? Existing green tests narrow it in one step: if the ViewModel
   tests pass and the dialog looks wrong, the state is right and the defect is
   below it.
2. **Write the missing test at the layer the defect lives in**, before the
   fix. A ViewModel-only suite cannot see a control that draws nothing; a
   render-layer defect needs a render-layer test.
3. **Verify the new test actually catches the defect: revert the fix (or
   write the test first) and watch it FAIL.** This is not ceremony. A test
   asserting a control "renders" can pass against a control drawing nothing --
   an untemplated Avalonia control still measures to its padding and still has
   visual children. A test that has never failed proves nothing, and a
   false-green test is worse than no test because it silences the next person.
4. **Fix, then re-run the owning suite AND every suite the change reaches.**
   A shared control, style, or seam means the blast radius is not the dialog.
5. **Record it in the working documents**: the cause, the fix, and -- when the
   first test attempt was a false green -- which assertion was too weak and
   which one actually bites. That lesson is the reusable part.

If the finding turns out to be a scope decision rather than a defect (a
control nobody planned, a behavior the plan never covered), do not absorb it
silently: put it to the developer as a decision, and if they take it in, add
plan items for it rather than implementing untested.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…