LT-22625: Add the WinForms to Avalonia conversion foundation - #964
Open
johnml1135 wants to merge 78 commits into
Open
LT-22625: Add the WinForms to Avalonia conversion foundation#964johnml1135 wants to merge 78 commits into
johnml1135 wants to merge 78 commits into
Conversation
This was referenced Jun 24, 2026
johnml1135
force-pushed
the
phase1-base
branch
2 times, most recently
from
June 25, 2026 10:38
3a905c6 to
b423a19
Compare
This comment has been minimized.
This comment has been minimized.
This was referenced Jul 1, 2026
johnml1135
added a commit
that referenced
this pull request
Jul 1, 2026
…ing docs Per deep review of the Phase-1 base PR, this removes ~10,300 lines of openspec content that does not belong in the base PR: Deleted entirely (self-declared superseded, or already implemented on a sibling branch with no home needed here): - openspec/changes/graphite-transition-support/ (self-labeled superseded throughout; content restated elsewhere) - openspec/changes/fieldworks-avalonia-shell-migration/ (self-labeled folded into/superseded by avalonia-end-game) - openspec/changes/avalonia-interlinear-editor/ (implemented on phase1-followup-interlinear, commit 3c5893e) - openspec/changes/avalonia-rule-formula-editor/ (implemented on phase1-followup-rule, commit 2c142bc) Deleted here for relocation to phase1-docs (speculative future-phase planning with real future value, not needed to review/merge this PR): - openspec/changes/avalonia-migration-roadmap/complete-migration-program.md, epics/**, reviews/** (JIRA-epic drafting for unstarted stages 5-13) - openspec/changes/legacy-screenshot-capture/ (dev tooling supporting a Docs/migration/ effort this PR isn't carrying) - openspec/changes/avalonia-end-game/ (depends on a Phase-1 burn-down this PR hasn't finished) Trimmed/deleted within datatree-model-view-separation (this change's own proposal.md/hybrid-alignment.md already carry a 2026-06-09 supersession note saying DataTreeModel/SliceSpec/IDataTreeView "should not be built"; these were the pieces that never caught up to that note): - Deleted specs/datatree-model/spec.md (asserted the abandoned DataTreeModel/SliceSpec/IDataTreeView requirements as live ADDED reqs); kept specs/datatree-partial-split/spec.md (the partial-class-split slice the proposal says remains valid as optional legacy maintenance) - Deleted three overlapping draft test plans for the abandoned design: testing-approach-2.md, test-plan-forms.md, test-plan-forms-future.md - Deleted stale coverage-gap planning docs: specs/changes-from-test-before-refactor/coverage-wave2-test-matrix.md and ".../tests to fix coverage gaps.md" - Trimmed datatree-mental-model.md to the current-state description only, removing the "target shape after split" section describing the abandoned architecture Fixed two stale artifacts that never caught up to their sibling supersession notes: - avalonia-migration-roadmap/specs/avalonia-migration-roadmap/spec.md: added a supersession note to the "DataTree split is the first migrated region" requirement and added an as-built scenario describing the actual region-model path (ViewDefinitionModel/LexicalEditRegionModel), matching the note already in this change's own design.md - lexical-edit-avalonia-migration/architecture-diagrams.md: removed the IPropertyStateStore port node (never built per task 18.6; state flows through IRecordNavigationContext + host PropertyTable), annotating the Navigation port instead Kept as-is per review: avalonia-migration-roadmap/{proposal,design, tasks}.md + specs/ (the ordered roadmap every later stack PR needs), avalonia-multi-writing-system-text-foundation/ (substantially shipped, not speculative), and the rest of datatree-model-view-separation (design.md/tasks.md/proposal.md already carry accurate snapshot framing). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
johnml1135
force-pushed
the
phase1-base
branch
2 times, most recently
from
July 1, 2026 16:23
10d7181 to
af20c5b
Compare
This comment has been minimized.
This comment has been minimized.
4 tasks
johnml1135
added a commit
that referenced
this pull request
Jul 3, 2026
…ss, openspec Records accumulated phase-1 working-tree changes not part of the Options-dialog work: adoption of the shared AvaloniaDialogTestHarness across the dialog test suites, LexicalBrowse* / ViewDefinition test updates, the FwAvalonia Preview/** compile exclusion (PR #964 review §4 finding F), preview-host project ref, and openspec roadmap edits (incl. removing the superseded datatree-model-view- separation change). Bundled as one checkpoint; the tree builds and all touched test suites pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
johnml1135
added a commit
that referenced
this pull request
Jul 3, 2026
Records accumulated phase-1 working-tree changes not part of the Options-dialog work: adoption of the shared AvaloniaDialogTestHarness across the dialog test suites, LexicalBrowse* / ViewDefinition test updates, the FwAvalonia Preview/** compile exclusion (PR #964 review §4 finding F), preview-host project ref, and openspec roadmap edits (incl. removing the superseded datatree-model-view- separation change). Bundled as one checkpoint; the tree builds and all touched test suites pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
johnml1135
force-pushed
the
phase1-base
branch
2 times, most recently
from
July 3, 2026 20:54
6636e17 to
40ae9d1
Compare
johnml1135
added a commit
that referenced
this pull request
Jul 4, 2026
Stacked on the interlinear follow-up. Restore the rule-formula surface and activate it: - add back the 29 rule-formula files (the 5 plugins, RuleFormulaModel/RegionEditor + RuleCellCommands, PhEnvironment + BasicIPASymbol editors, projector/sinks/options + deriver, and all their tests incl. SupportingEditorComposeTests) - restore the 5 plugin registrations in RegionEditorPlugins - restore the 5 rule class names in the burn-down census - FLIP: register the 6 rule tools (PhonologicalRuleEdit, EnvironmentEdit, compoundRuleAdvancedEdit, naturalClassedit, phonemeEdit, AdhocCoprohibEdit) in LexicalEditFeatureCatalog under a new "Grammar rule editors" group (restoring the canonical pre-split active set + parity notes); Phase1FollowUpSurfaceTools is now empty (both edit follow-ups landed; browse table gated separately) - add the 6 rule TestCase rows to RegisteredRecordEditTools_ResolveToAvalonia Rebased onto the squashed phase1-base (post PR #964 review): tool registration is now catalog-driven, so this flip also adds a new "Grammar rule editors" group (6 rows) to the Tools->Options "Manage Individual Features" dialog under UIMode=New. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
johnml1135
added a commit
that referenced
this pull request
Jul 4, 2026
Restore the interlinear surface on top of phase1-base and activate it: - add back the 11 interlinear files (InterlinearRegionEditor + analysis model + projector/write-back + plugin + their tests, incl. the FwAvalonia model and Visual tests) - restore the InterlinearSlicePlugin registration in RegionEditorPlugins - restore the interlinear class name + resolve assertion in the burn-down census - FLIP: register "Analyses" in LexicalEditFeatureCatalog (with new display-name/description strings), so it flows into the catalog- sourced DefaultSupportedTools and the Words Analyses detail editor resolves to Avalonia under UIMode=New; removed from Phase1FollowUpSurfaceTools - add the "Analyses" TestCase back to RegisteredRecordEditTools_ResolveToAvalonia The browse "Analyses" list pane stays inert (table follow-up territory). Rebased onto the squashed phase1-base (post PR #964 review): the base PR had since refactored tool registration to be catalog-driven, so this flip now also adds a "Words Analyses" row to the Tools->Options "Manage Individual Features" dialog under UIMode=New. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
johnml1135
added a commit
that referenced
this pull request
Jul 4, 2026
Stacked on the interlinear follow-up. Restore the rule-formula surface and activate it: - add back the 29 rule-formula files (the 5 plugins, RuleFormulaModel/RegionEditor + RuleCellCommands, PhEnvironment + BasicIPASymbol editors, projector/sinks/options + deriver, and all their tests incl. SupportingEditorComposeTests) - restore the 5 plugin registrations in RegionEditorPlugins - restore the 5 rule class names in the burn-down census - FLIP: register the 6 rule tools (PhonologicalRuleEdit, EnvironmentEdit, compoundRuleAdvancedEdit, naturalClassedit, phonemeEdit, AdhocCoprohibEdit) in LexicalEditFeatureCatalog under a new "Grammar rule editors" group (restoring the canonical pre-split active set + parity notes); Phase1FollowUpSurfaceTools is now empty (both edit follow-ups landed; browse table gated separately) - add the 6 rule TestCase rows to RegisteredRecordEditTools_ResolveToAvalonia Rebased onto the squashed phase1-base (post PR #964 review): tool registration is now catalog-driven, so this flip also adds a new "Grammar rule editors" group (6 rows) to the Tools->Options "Manage Individual Features" dialog under UIMode=New. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
johnml1135
added a commit
that referenced
this pull request
Jul 4, 2026
Restore the interlinear surface on top of phase1-base and activate it: - add back the 11 interlinear files (InterlinearRegionEditor + analysis model + projector/write-back + plugin + their tests, incl. the FwAvalonia model and Visual tests) - restore the InterlinearSlicePlugin registration in RegionEditorPlugins - restore the interlinear class name + resolve assertion in the burn-down census - FLIP: register "Analyses" in LexicalEditFeatureCatalog (with new display-name/description strings), so it flows into the catalog- sourced DefaultSupportedTools and the Words Analyses detail editor resolves to Avalonia under UIMode=New; removed from Phase1FollowUpSurfaceTools - add the "Analyses" TestCase back to RegisteredRecordEditTools_ResolveToAvalonia The browse "Analyses" list pane stays inert (table follow-up territory). Rebased onto the squashed phase1-base (post PR #964 review): the base PR had since refactored tool registration to be catalog-driven, so this flip now also adds a "Words Analyses" row to the Tools->Options "Manage Individual Features" dialog under UIMode=New. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
johnml1135
added a commit
that referenced
this pull request
Jul 4, 2026
Stacked on the interlinear follow-up. Restore the rule-formula surface and activate it: - add back the 29 rule-formula files (the 5 plugins, RuleFormulaModel/RegionEditor + RuleCellCommands, PhEnvironment + BasicIPASymbol editors, projector/sinks/options + deriver, and all their tests incl. SupportingEditorComposeTests) - restore the 5 plugin registrations in RegionEditorPlugins - restore the 5 rule class names in the burn-down census - FLIP: register the 6 rule tools (PhonologicalRuleEdit, EnvironmentEdit, compoundRuleAdvancedEdit, naturalClassedit, phonemeEdit, AdhocCoprohibEdit) in LexicalEditFeatureCatalog under a new "Grammar rule editors" group (restoring the canonical pre-split active set + parity notes); Phase1FollowUpSurfaceTools is now empty (both edit follow-ups landed; browse table gated separately) - add the 6 rule TestCase rows to RegisteredRecordEditTools_ResolveToAvalonia Rebased onto the squashed phase1-base (post PR #964 review): tool registration is now catalog-driven, so this flip also adds a new "Grammar rule editors" group (6 rows) to the Tools->Options "Manage Individual Features" dialog under UIMode=New. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
johnml1135
added a commit
that referenced
this pull request
Jul 6, 2026
- VersionInfoProvider: copyright year no longer freezes at whatever year the constant was last edited, ApplicationVersion resolves from the correct assembly instead of always falling back to the entry assembly, and MajorVersion/ParseInformationalVersion index defensively instead of assuming a fixed part count. Covered by new VersionInfoProviderTests.cs. - RegFree.targets: removes a dangling ManagedVwWindow.dll entry; the project was already retired in #904/#906, so the entry pointed at nothing. - opsx-*.prompt.md: replace inlined instructions with delegation to the existing .claude/skills/openspec-*/SKILL.md files, per this repo's skills-over-inline-prompts convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
johnml1135
added a commit
that referenced
this pull request
Jul 7, 2026
Six EXE projects (FieldWorks, LCMBrowser, UnicodeCharEditor, GenerateHCConfig, ComManifestTestHost, NativeBuild) each import RegFree.targets and generate manifests for the same shared managed assemblies (FwUtils.dll, SimpleRootSite.dll, ManagedVwWindow.dll) into the same $(OutDir). Under a parallel MSBuild build, their CreateComponentManifests targets can run in different MSBuild worker processes at the same time and race to read/write the exact same manifest file, throwing an IOException that fails the whole build (observed intermittently in CI, e.g. FieldWorks PR #964). Wrap RegFree.Execute()'s read-modify-write of the manifest file in a cross-process named Mutex keyed by the resolved output path, so concurrent invocations targeting the same file serialize instead of racing; invocations for different manifest files are unaffected and still run fully in parallel. string.GetHashCode() is deliberately not used for the mutex name since .NET randomizes it per process, which would defeat cross-process synchronization - MD5 is used instead as a deterministic fingerprint. Added a regression test that runs 12 concurrent RegFree.Execute() calls against the same manifest path and asserts they all succeed and produce valid, uncorrupted XML. Verified it actually catches the regression: temporarily reverted the mutex fix, confirmed the test fails reliably (3/3 runs), then restored the fix and confirmed it passes reliably (5/5 runs). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
The ViewModel wording sweep re-encoded six skill files and turned every em-dash, arrow, and section sign into a literal '?'. Restore each affected line's original characters; the sweep's wording itself is untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
johnml1135
added a commit
that referenced
this pull request
Jul 31, 2026
Remove the openspec change folders for the phase-1 migration research (avalonia-migration-roadmap, avalonia-multi-writing-system-text-foundation, lexical-edit-avalonia-migration, shared-editable-virtualized-table). Their durable contracts were synced into openspec/specs/ (12 new spec folders); the rest of the record lives in git history and in the provenance comment on PR #964. Align the migration skills, docs, and agent instructions to the code as it exists on this branch: fix stale type and path references, drop claims the alignment sweep proved false, and repoint provenance citations at the synced specs or git history instead of the removed change folders. Add the pr-pitch skill and wire pr-preflight to it as the single entrypoint for composing PR bodies and provenance comments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pitch had no length budget, so it grew into the audit it was meant to replace: PR #964's body ran 8,700 characters and buried its own argument. Give Phase 3 a hard 200-400 word budget scaled by risk rather than diff size, split the reviewer's gap into known and unknown unknowns, and add the overflow rule -- a section that will not fit was provenance-comment material, so it moves and leaves a link. The evidence behind each "where to look" bullet now has a home: the first collapsed section of the provenance comment, written for a reviewer who wants to check rather than trust. Make that comment sticky. Phase 4 said to use the markers but never said how, so a re-run could stack duplicates and rot the body's link. Phase 5 now gives the marker-match lookup and edits the comment in place, with the reason not to use --edit-last. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A second comment split the record for no gain. The description is the one place a reader always looks, it is inherently sticky -- editing it is in place and the URL never changes -- and it is the only part of a PR that survives being read a year later without scrolling a thread. The pitch keeps its 200-400 word budget as the top zone; the accordions move below it, as long as the reasoning deserves, since a closed details costs a reader nothing. Phase 5 loses the marker-match comment machinery and gains the fold-and- delete path for PRs that already have a provenance comment, including the grep for tree references that would dangle after the delete. Refresh the CONTEXT.md Avalonia vocabulary, which predated the rename sweep: Region became Detail, and LexicalEditRegionModel, LexicalEditSurfaceResolver, LcmRegionEditSession, RegionEditContextHolder, IRegionEditContext and IBrowseColumnSource no longer resolve anywhere in Src. Add the terms the vocabulary was missing -- view definition, cross-namespace twin, slice plugin, Unsupported row. Repoint the roadmap spec's superseded-requirement banner at the PR description now that the comment it cited is gone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A comment that cites a design document is a dangling reference waiting to happen: the document gets archived or deleted and the comment is left pointing at nothing. Several already had. The comment standard bans them outright, with LT-##### as the one sanctioned durable pointer. Removed across 39 files: - Markdown filenames: winforms-free-lexeme-editor.md, dialog-ownership.md, style-system.md, xml-retirement-blockers, and a cpp-build-modernization.md under a specs/ directory that no longer exists. - Finding and task labels defined only in those documents: D1-D4, B7, B8, GAP 1, A11Y-01 through A11Y-04, NETFX-01, section 19b/19d, task N.N, and numbered Stage/Phase coordinates. - Tree paths used as citations: openspec/, Docs/migration/, and named skills under .claude/skills. - PR #964 review findings cited as the place the reasoning lives. Each comment keeps its WHY, restated so it stands alone. Where the pointer was the whole comment, the pointer goes and the sentence stays. Two fixes fell out of touching these lines: a stale IRegionEditorPlugin reference became ISlicePlugin, and two comments narrating what code "now" does were rewritten to state what it does. String literals were left alone per the comment standard -- assertion messages, an ArgumentException message, and one Ignore reason still carry D1/D2/D3 and similar labels. Those are a separate pass. Comment-only: no code construct changed, and all 21 edited XML build files still parse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
johnml1135
added a commit
that referenced
this pull request
Jul 31, 2026
The pitch had no length budget, so it grew into the audit it was meant to replace: PR #964's body ran 8,700 characters and buried its own argument. Give Phase 3 a hard 200-400 word budget scaled by risk rather than diff size, split the reviewer's gap into known and unknown unknowns, and add the overflow rule -- a section that will not fit was provenance-comment material, so it moves and leaves a link. The evidence behind each "where to look" bullet now has a home: the first collapsed section of the provenance comment, written for a reviewer who wants to check rather than trust. Make that comment sticky. Phase 4 said to use the markers but never said how, so a re-run could stack duplicates and rot the body's link. Phase 5 now gives the marker-match lookup and edits the comment in place, with the reason not to use --edit-last. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
johnml1135
added a commit
that referenced
this pull request
Jul 31, 2026
A comment that cites a design document is a dangling reference waiting to happen: the document gets archived or deleted and the comment is left pointing at nothing. Several already had. The comment standard bans them outright, with LT-##### as the one sanctioned durable pointer. Removed across 39 files: - Markdown filenames: winforms-free-lexeme-editor.md, dialog-ownership.md, style-system.md, xml-retirement-blockers, and a cpp-build-modernization.md under a specs/ directory that no longer exists. - Finding and task labels defined only in those documents: D1-D4, B7, B8, GAP 1, A11Y-01 through A11Y-04, NETFX-01, section 19b/19d, task N.N, and numbered Stage/Phase coordinates. - Tree paths used as citations: openspec/, Docs/migration/, and named skills under .claude/skills. - PR #964 review findings cited as the place the reasoning lives. Each comment keeps its WHY, restated so it stands alone. Where the pointer was the whole comment, the pointer goes and the sentence stays. Two fixes fell out of touching these lines: a stale IRegionEditorPlugin reference became ISlicePlugin, and two comments narrating what code "now" does were rewritten to state what it does. String literals were left alone per the comment standard -- assertion messages, an ArgumentException message, and one Ignore reason still carry D1/D2/D3 and similar labels. Those are a separate pass. Comment-only: no code construct changed, and all 21 edited XML build files still parse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Jul 31, 2026
Picture editing was cut from the Avalonia detail view earlier in this branch, which left the Avalonia picture-properties dialog with no consumer on the new path: nothing outside its own test constructed the view or view-model. Delete the dialog, its test, and the localization rows that existed only to label it. The two LCModel-free exchange types the dialog used (DetailPictureMetadata / DetailPictureDialogResult) had no other reference in production or test code once the dialog was gone, and no edit-context member carried them, so they go too. Accessors and .resx rows are removed together because AvaloniaLocalizationTests reflects over every accessor and asserts it resolves from the neutral resx; splitting the pair across commits would break that test. Legacy FwCoreDlgs/PicturePropertiesDialog and DetailControls/PictureSlice are untouched -- the WinForms picture path still ships. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The "Manage Individual Features" dialog is a feature-catalog management utility reached from Tools > Options. It is unrelated to the Lexicon Edit detail view and to the InsertEntry dialog family, so it comes out with its Options entry point rather than shipping as a stray New-mode-only utility. LexiconFeatureCatalog and LexiconFeatureDescriptor STAY. The catalog is shared: EditSurfaceRegistry.DefaultSupportedTools is built from LexiconFeatureCatalog.ToolNames, so it remains the single list of tools that ship with a working Avalonia surface. Only the dialog set that rendered the catalog as a checkbox list is deleted, along with the four FeatureManager* strings and the ManageIndividualFeatures button string. The Options wiring is unwound end to end so no dead affordance is left: the button in LexOptionsDlgView, the view-model's ManageFeaturesCommand and ManageFeaturesVisible gate, the LexOptionsDlgState.ManageFeatures callback, and the launcher's ShowManageFeaturesDialog. LexOptionsDlgState.UIModeDisabledTools is kept. That setting is the per-tool opt-out EditSurfaceResolver still honours (seeded into the PropertyTable by FwXWindow and read by RecordEditView); the Options dialog now carries it through untouched instead of editing it. Comments that pointed at the deleted dialog are retargeted at the setting itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Picture editing was cut from the Avalonia detail view earlier in this branch and the picture-properties dialog came out with it. Three picture-only remnants survived with no consumer left on the new path. FwAvaloniaStrings.PictureInsert and PictureDelete labelled the insert/delete affordances on a picture row. Nothing constructs those affordances now that the detail view no longer edits pictures, and grep finds no reference to either accessor outside its own declaration. Accessor and .resx row are removed together because AvaloniaLocalizationTests reflects over every accessor and asserts it resolves from the neutral resx; splitting the pair would break it. Surviving rows keep their order. AvaloniaHostControlBase.HostedContent existed so the host could hand the Avalonia IStorageProvider to the picture media seam's file picker. Its only caller was the LcmRegionMediaServices construction in RecordEditView.Avalonia.cs, which went away with the media seam, so the accessor is now unreferenced. CurrentContent stays -- DetailHostControl still captures focus through it. Kept: the composer routing that renders a picture slice as a labelled Unsupported worklist row (EditorKindMap picture/image -> DetailEditorCategory.Picture -> WalkUnsupported) is current behaviour, as is the DetailOrcKind.Picture classification that makes an embedded picture render and delete inside rich text. Legacy WinForms PicturePropertiesDialog and PictureSlice are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The six FwAvalonia* projects sat inside Src/Common solution folders while the other ~138 projects in FieldWorks.sln are flat at the top level, so Solution Explorer showed them nested for no reason a reader could infer. dotnet sln add creates solution folders mirroring the directory hierarchy unless --in-root is passed, which is how the nesting arrived. Removes the Common solution folder and its seven NestedProjects entries only. Solution folders and NestedProjects are Solution Explorer presentation: the FieldWorks.proj traversal globs Src/** and never reads the sln layout, and the configuration sections key off project GUIDs, not folder GUIDs. Every project declaration is untouched. The historical Src/FXT folder is left as it is.
A generated design document cited "GAP section 3.5". That number belonged to the gap register in control-exemplar-map.md, but the citing document had its own numbered sections, so the reference read as a pointer into the citing document, resolved to nothing, and could not be clicked. Numbered citations also rot the moment either document is renumbered. Give each gap a stable slug id (GAP-PROGRESS, GAP-WIZARD, GAP-WS-SELECTOR, GAP-TRISTATE, GAP-F1-HELP, GAP-BROWSE-GRID, GAP-ROOTSITE-VIEWS). An explicit <a id="..."> anchor sits above each heading so links survive a heading rename, and the id leads the heading text so a reader can quote it. The five control-map rows that cited a gap by number now link to it by name and id. Record the rule in convert-dialog and convert-slice so generated documents follow it: references are links named for their target, never bare section numbers; cross-document links carry the stable id; the anchor is verified against the target's heading before it is written; relative paths are computed from the working document's own directory. Convert the three bare section references in the migration overview to verified anchored links. Scoped to the documents that describe the conversion workflow and to the gap register they cite. The wider sweep of lessons-learned, migration-checklist, dialog-conversion, and the migration SKILL is deliberately not included here. All ten anchored links were verified by resolving each fragment against the target file's actual headings and explicit anchors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A developer taking an AI-generated dialog, slice, or control had no documented way to adjust its visual result. The gap is not only tooling: the appearance of a converted dialog comes from four places -- launcher C# for window size, the axaml for structure, DialogTheme tokens for spacing, and the view-model for what appears at all -- and editing the wrong one wastes the most time. Add Docs/migration/adjust-the-layout.md, a third playbook beside the dialog and slice ones and linked from both. It states up front that Avalonia has no WYSIWYG designer, so nobody hunts for one: you edit markup and watch it render. It maps each visual property to the file that owns it, gives three loops in preference order, explains why a converted surface is previewable at all (the kit's LCModel-free view-model and UserControl view are what make it possible, so difficulty here is a design signal), and records the gotchas that cost real time. Make the middle loop real rather than described. FwAvaloniaPreviewHost gains a DialogPreviewWindow base and two registered surfaces, so a developer can open one converted dialog at its launcher's client size with the runtime's compact density and no FLEx launch. Registration is one assembly attribute: the host's module catalog reflects over every assembly in its own output folder, which is its own bin rather than the shared Output, so the host now references the dialog kit to put that assembly where the catalog can load it. That reference also makes this Avalonia-capable exe a candidate host for the Visual Studio previewer, which needs a BuildAvaloniaApp entry point that FieldWorks.exe does not have -- the cause of "Unable to create AppBuilder from type SIL.FieldWorks.FieldWorks". Whether the previewer process itself works against this net48 target is recorded as unverified rather than claimed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dotnet sln add manufactures solution folders mirroring the directory hierarchy
unless --in-root is passed; its help states the default outright ("Place
project in root of the solution, rather than creating a solution folder.
[default: False]"). The instruction in section 3 omitted the flag, so following
it nests the new project under Src/Common while the solution's other 142
projects sit flat at the top level.
Add the flag to the command and say why: consistency with what is already
there, not a claim that solution folders are wrong, so nobody "helpfully"
re-nests later. Note that the drift is invisible in review, since a .sln diff
of that size is unreadable -- which is why the command itself has to be right.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three commits on this branch removed the Avalonia picture-properties dialog, its host remnants, and the lexicon feature-manager dialog. Each explained itself in its commit message, but a message about a deleted file is reachable only by archaeology, and AGENTS.md directs a planner to Docs/lessons before working in a covered area. The knowledge was not findable where it is meant to be looked for. Add two cards on the established template. Picture editing covers both picture removals, which share a cause: the capability was cut from the detail view and the dialog and host affordances were remnants. The feature manager gets its own card because its lesson is different -- a New-mode-only utility with no WinForms counterpart is a standing parity divergence, and its catalog and setting survived because neither depended on the editor. The picture card's first lesson is the one this branch paid for twice: a view plus view-model with no launcher is dormant code that reads as finished, because it compiles and has tests. A later reader took that surface for a deliberate design and had to be corrected. Every survivor and removal claim was checked against the tree: LexiconFeatureCatalog, LexiconFeatureDescriptor, UIModeDisabledTools and DefaultSupportedTools are present, DefaultSupportedTools is built from the catalog, the six removed symbols resolve to nothing, and the legacy WinForms picture path is intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"kit" appeared 134 times across skills, docs, code comments and XML doc
blocks, and carried at least five different meanings: a specific reusable
dialog, the shared implementation as opposed to the consumer, the
FwAvaloniaDialogs assembly, a style authority, and a copy-this exemplar
pointer. It was never defined in the terminology section, yet it decided
naming conventions ("kits keep general names"). A phrase-window census
found 65 distinct collocations across 80 windowed uses -- the term almost
never appeared in a fixed phrase, which is what let the senses drift.
Two further reasons not to keep it. GoF documents "Kit" as the alternate
name for Abstract Factory, which this is not: there is no interface
creating families of related products, only one presentation model and
several adapters. And TECkit, an unrelated SIL encoding-conversion
library, is referenced in this same repository, so a bare "kit" is
ambiguous on a grep.
No replacement collective noun is introduced, because the parts already
have standard names: the view-model is Fowler's Presentation Model (which
MVVM renamed), the Input/Payload pair are Data Transfer Objects, each
launcher is an Adapter over LCModel, and AvaloniaDialogLauncher is a
Template Method. Per sense: redundant uses lose the word ("the reusable
entry-search dialog"); where a noun was load-bearing the types are named
instead; shared-versus-consumer becomes "shared"; the assembly becomes
FwAvaloniaDialogs or "the dialog stack"; normative uses become "dialog
rule"/"dialog convention"; exemplar pointers become "converted dialog
view".
134 insertions and 134 deletions: a line-for-line swap with no
restructuring. build.ps1 -SkipNative is clean, which matters because
about 40 of the edits sit inside XML doc summary and see-cref blocks the
compiler parses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The NOT-migrated notes restated what the gated code already shows: a legacy dialog stays on the WinForms path. They go ahead of finishing the migration, since the gate is the fact and the comment only duplicated it. The four notes reviewed individually are resolved in place. The PARITY label comes off where the note describes achieved behavior, and the UI-language note on AvaloniaOptionsDialogLauncher is replaced with what is actually undecided rather than asserting an equivalence that was never established. Repoint control-exemplar-map.md's GAP-WS-SELECTOR evidence at EntryGoLauncherShared.BuildVernacularSearchFieldSpec so it cites code instead of a comment that no longer exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auditing the remaining PARITY notes against legacy source found the MSAGroupBox/FwSandboxMsa claims to be wrong. Legacy InsertEntryDlg has no inflection-class picker: the property is written only by SetMsa and read back by SetEntryMsa, with no control in the form or its resx. Legacy MSAGroupBox has no inflection-feature editor at all; its four widgets are affix-type, main POS, slots and secondary POS. Both Avalonia panels are additions rather than parity mappings, which MSAGroupBox.cs already stated correctly in one place while contradicting itself in four others. Three gaps that were buried in prose become TODOs naming what blocks them: the missing "Add features to <POS>" link, whose two legacy outcomes are LT-5913 and LT-7167 and whose jump plumbing already exists, plus the two round-trips LcmMsaCreatorDialogLauncher does not perform. Comments that only restated the code beneath them are gone, and so is the DetailComposer claim that the visited set caps recursion at one pass per object and layout: the set is added to and removed from around each descent, so it guards re-entry, not repetition. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A comment claimed the nested Members rows do not compose. They do. With one Morpheme Rule member present, every part of its EditAdHocGroup layout composes (Key Morpheme, Cannot Occur, Other Morpheme(s), Active), bound to the member and indented below the group's own rows. The claim went unchecked because the existing test builds an empty group, where there is nothing to descend into and the sequence node composes as a lone text row. Assert the structure instead: each layout part composes, the rows bind to the member by ObjectHvo, they nest deeper than the group's own rows, and they arrive as Unsupported worklist rows rather than editors. Implementing the custom-slice editors will fail that last assertion and get a deliberate update, which is the accurate version of what the deleted comment was reaching for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A census found five senses sharing the word: which framework renders a tool,
a UI region of the app, product-vs-preview wiring, the drawn background of a
control, and the plain verb. The first three each get their own name; the
styling sense and the verb keep the word, as do the linguistic term "surface
form" and ordinary idioms.
The framework axis had three vocabularies for one binary - the UIMode strings
Legacy/New, EditSurface { WinForms, Avalonia }, and EditSurfaceKind
{ Legacy, Avalonia }. Both enums merge into UIFramework { Legacy, Avalonia },
resolved by UIFrameworkResolver over UIFrameworkRegistry and decided per host
by UIFrameworkSelectionService; only the persisted UIMode string still says
"New", normalized at the boundary as before. Members that held realized
controls are named for what they hold: EnsureDataTreeInitialized,
TearDownAvaloniaEntryForm, EditControlFactory. SurfaceRouting becomes
HostRouting; its persisted member names and the JSON "routing" key are
unchanged, and the unused XML attribute the importer accepted as "surface"
now matches that key. Prose throughout code, skills, docs, and openspec specs
names the concrete referent instead - view, detail view, entry form, dialog,
tool, host, framework, layout, or prefix.
CONTEXT.md retires the Surface glossary entry in place, recording why: its
own defining documents disagreed on whether dialogs were included. Stale
Region-era vocabulary found along the way is swept to Detail terms, snapshot
docs now show the Detail- prefixes the tests actually emit, and the one
user-facing resx string using the word (the dormant feature-catalog
description) now says "view".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace "gate on" and its inflections with plain verbs -- "branch on", "assert on", "limit activation to", "must pass before". Gate stays as a noun where it names a real thing: the confirmation gates in a playbook, the opt-in gate, the detail view's UIMode gate. Drop the "worklist row" compound in favor of "Unsupported row" and delete the glossary entry that defined it, which also removes the only forward reference in that list. Lower-case CHILD and CUSTOM in the dialog playbook, where the sentence already carries the emphasis.
InitializeUIModeControls runs after InitializeComponent, so AutoScaleMode.Font never scales the group it injects. Its children inherited the scaled font while keeping unscaled coordinates, which put the Mode label on top of the combo at any scaling above 100 percent. Derive every offset from the inherited font height or a measured edge. The beta warning now autosizes within the combo's width rather than a fixed 30px box, and the group takes its height from that content instead of a literal 100.
Structural and DRY fixes from a review of the conversion support code: - Rename the Avalonia factory entry points to Create*, matching the LCM factory convention already used across the repository. - Extract ComposedDetailEditContext and FieldEditHandler from DetailComposer. - Move the chooser's empty-item label into FwAvaloniaStrings.resx so the Avalonia code owns its own strings instead of reading a legacy resx. - Wire EditorKindMap's KnownEditors set to the constants beside it. - Log the writing-system keyboard failure and the composer lookup fallbacks rather than swallowing them. Density and project configuration: - Match the slice and dialog body font at 11pt, and route DialogTheme's six FontSize setters through its DialogFontSize resource, which had no consumers. The C# and XAML copies must stay in step: Avalonia's compiled XAML rejects x:Static as a resource declaration and drops the file without failing the build. - Remove properties the new project files restated from Directory.Build.props. LangVersion stays, with a comment: the view models use init-only setters. The preview host keeps its own output folder because it must not ship. Also revert the unused FieldWorks.cs usings, rename DataTreeDisposalCharacterizationTests to DataTreeObservableBehaviorTests for what it asserts, and bring the touched comments in line with the commenting skill.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The WinForms to Avalonia conversion foundation
LT-22625
This is the foundational change for converting FieldWorks WinForms dialogs and
views to Avalonia. It delivers three things: a documented, repeatable process
for performing a conversion, the support code that process targets, and
example conversions that prove both are usable.
Default behavior is unchanged.
UIModedefaults toLegacyand both gatepredicates fail closed, so with no action FieldWorks does exactly what
maindoes. Open Tools -> Options -> Interface and set New UI (preview) to
New, and the converted views render in Avalonia, live, without a restart.
Support code
The reusable machinery. Nothing in this section is specific to a converted
view or dialog, and a new conversion should add nothing here.
View definitions to Avalonia controls
Src/Common/FwAvalonia/(77 files),Src/xWorks/Avalonia/(22 files)ViewDefinition/ViewDefinitionCompilercompiles the existingConfiguration/**/*.xmlParts and Layout files into an immutable,fingerprinted snapshot cached by (class, layout, type, fingerprint). Migrating
a view edits no layout XML.
Composer/DetailComposerwalks that snapshot against LCModel and produces aDetailModelplus anIDetailEditContext, applying per-projectViewDefinitionOverridefiles. Each node carries aHostRoutingvalue(product / preview / unsupported), so real LCModel wiring and preview-host
sample wiring can never be silently confused.
Detail/DataTreerenders the model as aUserControl, with the ownedcontrols beside it (
FwFieldControls,FwOptionChooser,FwStructuredTextField,DetailMenuFlyout,DetailFocusMemory).Plugins/SlicePluginsholdsISlicePlugin, keyed by the legacy layout'sclass=string.SlicePluginRegistry.Registerthrows on a double claim: oneowner per legacy class, no silent overwrite.
The gate: which framework renders a tool
Src/Common/FwAvalonia/UIFramework.csand friends,Src/Common/FwUtils/UIModeGates.csUIFramework { Legacy, Avalonia }is the valueUIFrameworkResolver.Resolvereturns,
UIFrameworkSelectionServicewraps in its per-host decision, andRecordEditViewstores to choose which of its two children to build and show.LexiconFeatureCatalogis the single list of tools that have a workingAvalonia implementation, with the display name, description, and grouping a
settings UI shows.
UIFrameworkRegistryis built from it, so the gate andthe user-visible list cannot drift apart.
UIFrameworkResolver.Resolveanswers, for one tool, whether Legacy WinFormsor Avalonia renders it. Precedence: an unregistered tool is always Legacy;
otherwise an explicit override wins (tests use this); otherwise the persisted
UIModedecides. Only a case-insensitive "New" selects Avalonia, so null,blank, or an unrecognized value falls back to Legacy. The type has no
Avalonia dependency, so the decision is unit-testable without a UI runtime.
UIFrameworkSelectionService.Decideclassifies each host's behavior underthe global switch (supported / explicit legacy fallback), and per-tool
opt-outs (
UIModeDisabledTools) are honored by the hosting layer, so Newmode is not all-or-nothing.
UIModeis a PropertyTable string defaulting toLegacy. Call sites branchthrough a
[MethodImpl(MethodImplOptions.NoInlining)]helper so the Avaloniaassemblies are never loaded on the legacy path.
Dialog hosting
Src/Common/FwAvaloniaDialogs/(57 files),Src/Common/FwAvalonia/AvaloniaDialogHost.csAvaloniaDialogHost.ShowModalhosts an AvaloniaUserControlinside aWinForms-owned modal
Form, which is what makes coexistence possible: aconverted dialog can be launched from unconverted WinForms code.
AvaloniaDialogLauncher<TState, TViewModel, TPayload>is the template methodevery conversion fills in (build state, create view model, create view,
apply). It collapses a non-accepted result to cancelled in one place.
DialogViewModelBasesuppliesRequestClose,Accepted,CanOk,ApplyChanges, and the generated OK/Cancel commands.DialogThemeand the shared sizing tokens keep converted dialogs visuallyconsistent without per-dialog styling.
FwAvaloniaDialogsStrings.resx. Localization stays in resx.Preview host
Src/Common/FwAvaloniaPreviewHost/(7 files)Renders a converted dialog or view standalone, so layout work does not
require launching FieldWorks and opening a project.
Example conversions
Proof that the support code and the process work. These are the deliverable's
evidence, not its purpose.
Detail views (4 tools)
Enrolled in
LexiconFeatureCatalog:lexiconEdit,lexiconEditPopup,notebookEdit,posEdit. Each renders through the compiler and composerabove, from the unmodified layout XML.
Dialog bodies (9)
AddNewSenseDlg,ChooserDialog,CreateFeatureDialog,EntryGoDialog,FeatureChooserDialog,InsertEntryDlg,LexOptionsDlg,MessageBox,MsaCreatorDlg.Product wiring (14 launchers)
Src/LexText/**/Avalonia/(39 files). One launcher per legacy dialog entrypoint, each selected at its call site by the resolved framework.
The reuse pattern matters more than the count:
EntryGoDialogis one dialog bodyre-skinned by six launchers (
LcmGoToEntryDialogLauncher,LcmLinkEntryOrSenseDialogLauncher,LcmLinkAllomorphDialogLauncher,LcmLinkMsaDialogLauncher,LcmMergeEntryDialogLauncher,LcmAddAllomorphDialogLauncher). Only the title, prompts, search filter, andon-OK behavior differ. Three members of that legacy family are not yet
converted (
ReversalEntryGoDlg,RecordGoDlg,WordformGoDlg); each needs alauncher, not a view.
The process
This is what makes the change foundational rather than four screens and some
dialogs.
Agent procedure (15 skills added, 3 updated)
.claude/skills/(37 files).convert-dialogandconvert-slicedrive oneconversion through analysis, developer alignment, test planning, exemplar
mapping, design, and implementation. The middle stages are gates: an interactive
question that ends the turn, not a paragraph to read past. Analysis is read-only,
so it is safe to run while a developer has the live application open.
Supporting skills cover the areas a conversion routinely gets wrong:
dialog-update(keeping a converted pair in sync),create-integration-test,fieldworks-code-commenting(the C# comment standard, referenced fromAGENTS.md),fieldworks-localization-review,fieldworks-semantic-render-parity,fieldworks-ui-wiring-review,fieldworks-uia2-parity-testing,fieldworks-managed-netfx-review,fieldworks-migration-scope-review,fieldworks-test-coverage.Developer guides
Docs/migration/(4 files):avalonia-migration-overview.md,migrate-a-dialog.md,migrate-a-slice-type.md, andadjust-the-layout.md--the hand-tuning path for a developer who wants to adjust a converted layout in
Visual Studio rather than accept what the conversion produced.
Shared language
CONTEXT.mdcarries the project glossary the conversion vocabulary isgrounded in. Terms that accumulated conflicting senses during the work were
renamed rather than documented around: the framework choice is
UIFramework,product-vs-preview wiring is
HostRouting, and the glossary records theretirement of the words they replaced.
Durable contracts and lessons
openspec/specs/(41 files) holds the contracts a future change must not break.Docs/lessons/avalonia-migration/(7 files) is an indexed set of lesson cardsrecording reusable observations and decision boundaries. The cards constrain
future research; they do not authorize restoring retired code.
Unfinished work is visible, not absent
Two mechanisms, with different scopes. Inside a converted detail view, a slice
the Avalonia path cannot yet render composes as a labeled
Unsupportedrowwith a stable automation id, never a silent omission -- grep those rows and you
have the remaining slice work. This is why the enrolled tools can ship
before every field kind exists. Everything else that is unconverted -- dialogs
without a launcher, tools not in the registry -- shows nothing new at all: it
stays entirely on the WinForms path via the gate, so there is no half-rendered
state anywhere.
Tests
139 new test files, 1,590 test methods, +34,895 lines. Headless Avalonia tests
for views and view models, composer tests against a real LCModel cache, gate and
fallback tests, automation-id convention tests, and semantic snapshot comparisons
against the WinForms views.
Scope
476 files, +82,359 / -470, against
main. Tests are counted separately from thesource rows below, not within them.
Src/Common/FwAvalonia(core)Src/Common/FwAvaloniaDialogsSrc/xWorks(hosting, composer)Src/LexText(launchers, gates).claude/skills(process)openspec/specsDocs/migrationSrc(gate call sites elsewhere)Docs/lessonsSrc/Common/FwAvaloniaPreviewHostDocs(other)The 470 deletions are gate call sites and comment cleanup. No legacy WinForms
view or dialog is removed by this change.
Verification
CI on this head is the source of truth. Locally: whole-solution build clean, and
the Avalonia and composer test projects green.
To exercise it by hand, set New UI (preview) to New and confirm the four
tools render and the converted dialogs open; then set it back to Legacy and
confirm the WinForms path is untouched.
What is deleted when the conversion is complete
Avalonia-in-WinForms hosting. Exists only to put Avalonia content inside a
WinForms parent.
AvaloniaDialogHost.cs,AvaloniaHostControlBase.cs,DetailHostControl.cs,InputKeyClaimingAvaloniaHost.cs,FinalizerSafeSynchronizationContext.cs,FwAvaloniaRuntime.cs,Seams/IXCoreCommandBridge.cs,Src/xWorks/Avalonia/Hosting/RecordEditView.Avalonia.csFramework selection. Chooses Legacy or Avalonia per tool; one framework
leaves nothing to choose.
UIFramework.cs,UIFrameworkResolver.cs,UIFrameworkRegistry.cs,UIFrameworkSelectionService.cs,LexiconFeatureCatalog.cs,EditControlFactory.cs,Src/Common/FwUtils/UIModeGates.cs, theUIModesetting and its Options-dialog editor, and every
NoInliningcall siteLegacy managed UI. Every WinForms dialog and control, plus the record-edit
stack behind them, replaced by the Avalonia views and dialog bodies.
163
Formsubclasses, 84UserControlsubclasses, 236.Designer.csfiles andtheir
.resxcompanions, plusSrc/Common/Controls/DetailControls(88 files,DataTreeand theSlicehierarchy),Src/Common/Controls/XMLViews(66),Src/Common/RootSite(44),Src/Common/SimpleRootSite(42), andSrc/XCore(96, the mediator and
XWindow)Native Views library. The C++ COM engine that draws every legacy field;
Avalonia renders and shapes its own text, which
lexical-edit-font-decommissioningalready requires.Src/viewsentire -- 136 C++ files (VwRootBox,VwEnv,VwGraphics, thelazy-box layout,
lib/GraphiteEngine,lib/UniscribeEngine), theViews.idhCOM interfaces, and the STA constraint the render path imposes on the process
FieldWorks.Mainchanges rather than goes. ItsApplication.EnableVisualStyles()andApplication.Run()becomeBuildAvaloniaApp().StartWithClassicDesktopLifetime(args)-- the call the previewhost already makes -- so Avalonia owns the message loop. The
Application.ThreadExceptionhook becomes a dispatcher handler andApplication.DoEventsmoves toDispatcher.UIThread.Runtime XML layout parsing retires separately, view by view as parity gates pass,
leaving the typed view definitions as the runtime source
(
lexical-edit-view-definition).This change is