LT-22625 Phase-1 base: Avalonia migration spine (UIMode defaults Legacy) - #964
Open
johnml1135 wants to merge 61 commits into
Open
LT-22625 Phase-1 base: Avalonia migration spine (UIMode defaults Legacy)#964johnml1135 wants to merge 61 commits into
johnml1135 wants to merge 61 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.
Move the 17 LexTextControls files that bridge to the Avalonia dialog kit out of the project root into a flat Avalonia/ folder, so the Avalonia launchers/adapters read as one area instead of being interleaved with the legacy WinForms controls. Every moved file references FwAvaloniaDialogs; EntryGoDlg.cs and EntryGoSearchEngine.cs (no Avalonia-kit reference) stay in the root. Avalonia/ AvaloniaOptionsDialogLauncher, EntryGoLauncherShared, FwFeatureStructureAdapter, LcmInflectionFeatureCreateWiring, LcmAddAllomorphDialogLauncher, LcmAddNewSenseDialogLauncher, LcmCreateFeatureLauncher, LcmCreatePartOfSpeechLauncher, LcmGoToEntryDialogLauncher, LcmInflectionFeatureChooserLauncher, LcmInsertEntryDialogLauncher, LcmLinkAllomorphDialogLauncher, LcmLinkEntryOrSenseDialogLauncher, LcmLinkMsaDialogLauncher, LcmMergeEntryDialogLauncher, LcmMsaCreatorDialogLauncher, LcmPhonologicalFeatureChooserLauncher Pure git mv (R100); LexTextControls.csproj is SDK-glob (only Compile Remove is LexTextControlsTests/**), so no compile-item edits are needed. Behavior-preserving folder move. build.ps1 -SkipNative -BuildTests: 0 errors, 0 warnings (LexTextControls + LexTextControlsTests compile from the new layout). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move the region-bridge and Avalonia-launcher test files into Avalonia/ trees
that mirror the source reorg (3A/3B), so each test sits next to the area it
covers. The moved sets are exactly the test files added on this branch relative
to main (35 in xWorksTests, 17 in LexTextControlsTests); the two pre-existing
files touched on the branch (xWorksTests/XWorksAppTestBase, BulkEditBarTests)
stay in their roots.
xWorksTests/Avalonia/ mirrors 3A (best-fit by SUT): Composer/
FieldTypeComposerTests, FullEntryRegionOverrideTests,
FullEntryRegionReferenceChooserTests, FullEntryRegionVoiceWsTests,
LexiconFirstSliceEditContextEdgeCaseTests, RegionEditContextEditingTests,
RegionEditSessionLifecycleTests, StructuredText{Adapter,Integration,
Workflow}Tests, BackRefVectorTests, EntryReferenceVectorTests,
GhostLexRefSliceTests, LexReferenceMultiSliceTests, AdhocCoProhibComposeTests,
CompoundRuleComposeTests, NaturalClassComposeTests, RegionConsolidationTests,
RegionOverrideMigrationTests Plugins/ LexemeEditorBurnDownTests Hosting/
RecordEditViewActiveHostContractTests, RecordEditViewSwitchTests,
RecordClerkNavigationContextTests, FwTsStringClipboardTests,
FwXWindowUIModeSeedingTests, RegionCommandAdapterHardeningTests,
RegionContextMenuCompositionTests, RegionObjectCommandExecutionTests,
RegionEditLinkDispatchTests, RegionEditGuardAndSchedulingTests,
RegionDataTreeMoveReachabilityTests, WinFormsUiaSmokeTests,
AvaloniaHeadlessPlatformTests, AvaloniaHeadlessSetUpFixture,
TestLocalizationManagerBootstrap
LexTextControlsTests/Avalonia/ (flat) mirrors 3B: the 15 launcher/adapter test
files plus LexOptionsDlgTests and ScreenshotHarnessTests (the other two new
Avalonia-dialog test files).
Slotting notes (folder-only, no behavior effect):
- Root-level SUT tests (RegionEditContext*/RegionOverrideMigration) are placed
in the closest of Composer/Hosting since the test mirror uses only the three
subfolders.
- Cross-cutting test infrastructure (AvaloniaHeadlessSetUpFixture,
AvaloniaHeadlessPlatformTests, TestLocalizationManagerBootstrap) is parked in
Hosting. The [SetUpFixture] keeps its code-declared namespace, so its scope is
unchanged by the move.
Pure git mv (R100); both test projects are SDK-glob, so no compile-item edits.
Behavior-preserving. build.ps1 -SkipNative -BuildTests: 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewer chose the bare legacy stem over the qualified RegionSliceFactory, making the FwAvalonia.Region type a deliberate cross-namespace twin of DetailControls.SliceFactory; no CS0104 alias was needed (no site references the bare stem while importing DetailControls), and the engine-isolation audit's source denylist drops SliceFactory since FwAvalonia.Region now owns the twin while the assembly-level guard still enforces isolation; behavior-preserving. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Seams namespace keeps only true substitution seams, their implementations, and boundary data contracts: - FinalizerSafeSynchronizationContext moves to the FwAvalonia root beside FwAvaloniaRuntime, which installs it (a hosting crash guard, not a seam). - MorphTypeSwapLogic (with MorphTypeKind) moves to FwAvalonia.Region beside its RegionComposer consumer; the DetailControls pointer comment now names the new location. - EditSurfaceKind is extracted from ActiveHostContract.cs into its own file at the FwAvalonia root, anchoring the EditSurface* family. - ISeams.cs (a grab-bag named after no type) splits into one file per interface: IEditSession, IRegionRefreshCoordinator, IUiScheduler, IRegionLifetime, IXCoreCommandBridge, IRecordNavigationContext. Behavior preserving; doc comments carried verbatim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three whole-identifier renames, no behavior change: - ISettlePendingEdits -> IPrepareToGoAway (method SettlePendingEdits -> PrepareToGoAway, still void). Named for the legacy DataTree/ BrowseViewer PrepareToGoAway idiom; the summaries now spell out the deliberate contract difference — this seam cannot veto and always settles the open edit. RecordEditView implements it explicitly because the class already carries the legacy vetoing bool PrepareToGoAway() override. - FwOptionPicker -> FwOptionChooser, unifying on the FieldWorks "chooser" vocabulary (FwOptionPickerTests -> FwOptionChooserTests). Diagnostic string literals keep the old name: the "FwOptionPicker" TraceSwitch id (paired with FieldWorks.Diagnostics.dev.config), the [FwOptionPicker] trace prefixes, the FwOptionPicker-NN dialog snapshot names, and three assertion-message mentions. - LexemeEditorBurnDownTests -> LexemeEditorInventoryTests, dropping task-management vocabulary from the census fixture; the xWorksTests.csproj pointer comment follows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Options label for the lexical-edit UI mode chooser said "Lexical Edit UI", which names the internal migration rather than what the user is choosing. Both twins now read "New UI (preview)": - Avalonia: FwAvaloniaDialogsStrings.LexicalEditUiLabel -> NewUiPreviewLabel (resx key FwAvaloniaDialogs.NewUiPreviewLabel, renamed in place to keep the append-only order), value "New UI (preview)"; LexOptionsDlgView.axaml follows the accessor. - Legacy WinForms twin (PR-added entry): LexTextControls.resx UiModeGroupTitle keeps its neutral key, value becomes "New UI (preview):"; the LexOptionsDlg in-code fallback copy of the label follows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stage (a) of the Region->Detail rename: the FwAvalonia core model/view types (DetailModel, DetailField, DetailTextRun, DetailRichTextValue, DetailWsValue, DetailChoiceOption, DetailMenu*, DetailViewing*, and the rest of the mechanical table), IDetailEditContext, the seams (IDetailRefreshCoordinator, IDetailLifetime/DetailLifetime), the host pair (AvaloniaHostControlBase / DetailHostControl), DetailEditorCategory with ClassifyDetailFieldKind, and RegionDataTree as the bare legacy twin DataTree. The namespace moves from FwAvalonia.Region to FwAvalonia.Detail and the folder follows via git mv. RecordEditView's two partials pin bare DataTree to the legacy DetailControls type with a using alias (the anticipated CS0104 twin). The engine-isolation audit's forbidden-symbol list drops DataTree under the same reclaimed-twin exemption it already documents for SliceFactory; assembly-level isolation still enforces the real dependency ban. A stale SetRegionContent cref now points at the real member, SetHostContent. String literals (automation ids, trace text, snapshot capture names) are deliberately unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stage (b) of the Region->Detail rename: DetailComposer, ComposedDetail,
ComposedDetailEditContext, DetailEditContextBase/Holder, DetailValueFactory
(with its DetailRichTextAdapter helper), LcmDetailEditSession,
DetailOverrideMigration, AvaloniaDetailRefreshController, and
ReversalDetailEditContext; the slice-plugin family becomes ISlicePlugin /
SlicePluginRegistry / SlicePluginBuildContext (file SlicePlugins.cs). Members
and methods follow: ShowDetail, SettleDetailEdits, RefreshAvaloniaDetail,
OnAvaloniaDetailEditCompleted, OnDetailMenuRequested, OnDetailLinkRequested,
RaiseDetailEditCompleted, DetailEditCompleted, RefreshedDetailFieldCount,
m_detailEditContext, and the bare region/regionField locals.
The one reflection literal on the renamed method
(GetMethod("RefreshAvaloniaRegion")) chases the new name so the private-invoke
test keeps binding; trace-message literals that embed the old type names stay
unchanged by the literal rule. The two csproj comments naming the plugin
contract now say ISlicePlugin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stage (c) of the Region->Detail rename: the preview support family
(DetailPreviewWindow / DetailPreviewScenario / DetailPreviewDataProvider
in DetailPreviewSupport.cs), the workflow driver DetailEditorDriver, the
test doubles (PreviewDetailEditContext, InMemoryDetailEditContext,
FakeDetailEditContext, Rich/UnsupportedRich/Fake DetailValueProvider,
RealisticDetailModel, DetailDefinition), and the EntryGo dialog's
DescriptionPane family (HasDescriptionPane,
OnDataContextChangedRemoveDescriptionPaneIfUnused, the DescriptionPane_*
test names, and the axaml comments' "description pane" wording).
RegionKeyboard becomes WritingSystemKeyboards.Activate, joining the
writing-system-centered keyboard vocabulary; its summary now describes
the per-WS keyboard activation for editor rows on the Avalonia surface
and keeps the lives-in-xWorks-because-LCModel note. Both callers
(RecordEditView.Avalonia.cs, ReversalIndexEntryPlugin.cs) follow. The
EntryGo automation ids ("EntryGo.DescriptionRegion",
"EntryGo.ResultsRegion") and snapshot capture names stay unchanged by
the literal rule.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stage (d) of the Region->Detail rename: every Region-named test fixture follows its subject (DetailEditingTests, DetailModelTests, DetailMenuTests, DetailOrcTests, DetailEditContextEditingTests, DetailComposerTests, DetailConsolidationTests, the Detail* hosting fixtures, DataTreeTests / DataTreeMoveReachabilityTests for the bare twin, EditorKindMapDetailCategoryTests, SlicePluginResolutionOrderTests, and the FullEntryRegion* fixtures as DetailComposer*Tests), along with every Region-bearing test method name (DetailEditView_*, Detail_ScrollsLikeLegacyAutoScroll, ClassifyDetailFieldKind_*, and the rest). Files rename to match their fixtures; the two csproj comments naming RegionPreviewTests.cs now say DetailPreviewTests.cs, and stale comment references to the old fixture names are updated. Snapshot artifacts are transient (*.received.png / *.diff.png are gitignored) and no committed baseline embeds a renamed test name. Automation-id and assertion-message literals that mention the old names stay unchanged by the literal rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stage (e), the final pass of the Region->Detail rename: the resx row FwAvalonia.RegionName becomes FwAvalonia.DetailAreaName with the value "Detail area" (in place, order preserved), the FwAvaloniaStrings accessor follows, and DataTree's automation Name consumer reads the new accessor. Comment prose across FwAvalonia, FwAvaloniaDialogs, xWorks/Avalonia, and the launcher files drops the region coinage for "detail view" / "detail area" / "detail surface" wording (sentence-level), and the EntryGo dialog family says "description pane". Project-file and axaml comments follow. Left deliberately unchanged: runtime string literals (the RegionDataTree/RegionEditor automation ids and their test lookups, the DetailComposer/DetailEditContextHolder trace prefixes that still say RegionComposer/RegionEditContextHolder, DialogSnapshot capture names, assertion messages, the avalonia-region-first-slice.png artifact name, and the EntryGo.*Region automation ids), the region-manifest.md document references, geographic "region" test data, and #region pragmas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Region->Detail identifier sweep deliberately left type-derived string literals unchanged; this commit moves them, keeping every literal and every lookup/assertion that reads it in the same change: - Automation ids: RegionDataTree(.Scroll/.Splitter) -> DataTree(...), RegionEditor.* -> DetailEditor.*, RegionPreviewWindow -> DetailPreviewWindow, EntryGo.DescriptionRegion/ResultsRegion -> EntryGo.DescriptionPane/ResultsPane, with all test lookups. - WinForms identity: Name "RegionHostControl" -> "DetailHostControl"; DataTree Name/id "RegionDataTree" -> "DataTree". AccessibleName "RecordEditView.AvaloniaHost" is intentionally unchanged. - Log/exception text: "RegionComposer:" -> "DetailComposer:", "RegionEditContextHolder.Settle:" -> "DetailEditContextHolder. Settle:", "A region editor plugin must claim..." -> "A slice plugin must claim...", the Region-worded Logger messages in RecordEditView.Avalonia.cs, and SliceFactory's "Custom region field" traces -> "Custom slice field". - DialogSnapshot capture names (gitignored artifacts): Region-* -> Detail-*, EntryGo-09-no-description-region -> ...-pane, avalonia-region-first-slice.png -> avalonia-detail-first-slice.png. - Assertion messages and the DetailViewingServices descriptors that said "region" in the detail sense now use detail wording; stale type mentions RegionLinkRequest/RefreshAvaloniaRegion/RegionSelectionRange now name DetailLinkRequest/RefreshAvaloniaDetail/DetailSelectionRange. - IFwClipboard.cs: reworded the fidelity note so "legacy" no longer reads as describing TsString itself; the shared format is the existing "TsString" clipboard format native-Views copy/paste uses. Deliberately untouched: FwOptionPicker trace-switch id and FieldWorks.Diagnostics.dev.config, BCP-47 Region* (writing systems), RegionsOC/LIFT/morphology XSL, #region pragmas, region-manifest.md references, image-diff "region" wording in RenderVerification, and geographic test data. Gates: build 0 errors; FwAvaloniaTests 633/634 (1 skip); FwAvaloniaDialogsTests 298/298; FwAvaloniaPreviewHostTests 3/3 (UIA); xWorksTests ~Detail|~Composer|~RecordEditView|~Slice|~DataTree 225/225 (226/226 with ~Region, matching the pre-rename baseline set); LexTextControlsTests 348/351 (3 skips). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mechanical name/path staleness fixes across .claude/skills/**: every
old type/file/path token is swapped for its verified landed equivalent
(each target confirmed to exist in the tree before writing):
- LexicalEditRegion{Model,Mapper,View,Field} -> DetailModel,
DetailModelProjector, DataTree, DetailField;
FullEntryRegionComposer -> DetailComposer (with its
Src/xWorks/Avalonia/Composer/ path); ComposedEntryRegion ->
ComposedDetail; RegionEditContext{Base,Holder} ->
DetailEditContext{Base,Holder}; ComposedRegionEditContext ->
ComposedDetailEditContext; IRegionEditContext -> IDetailEditContext;
IRegionValueProvider -> IDetailValueProvider; LcmRegionEditSession ->
LcmDetailEditSession.
- Plugin registry: RegionEditorPlugins(.cs) -> SlicePlugins(.cs) at
Src/xWorks/Avalonia/Plugins/; IRegionEditorPlugin -> ISlicePlugin;
RegionEditorBuildContext -> SlicePluginBuildContext;
RegionEditorPluginRegistry -> SlicePluginRegistry;
RegionFieldControlFactory -> SliceFactory; RegionFieldKind ->
DetailFieldKind; LexemeEditorBurnDownTests ->
LexemeEditorInventoryTests (Avalonia/Plugins path).
- Surfaces and features: LexicalEditSurface* -> EditSurface*;
LexicalEditFeature* -> LexiconFeature*; LexicalEditorDriver ->
DetailEditorDriver.
- Controls and seams: FwOptionPicker -> FwOptionChooser;
RegionMenuFlyout -> DetailMenuFlyout; RegionFocusMemory(Tests) ->
DetailFocusMemory(Tests); RegionWsValue -> DetailWsValue;
IRegionLifetime -> IDetailLifetime; ILexicalRefreshCoordinator ->
IDetailRefreshCoordinator; ISettlePendingEdits -> IPrepareToGoAway;
Region*Tests -> Detail*Tests.
- Dialog kit files: OptionsDialogView/ViewModel + OptionsState ->
LexOptionsDlgView/ViewModel/State; InsertEntryDialogView/ViewModel ->
InsertEntryDlgView/ViewModel; AddNewSenseDialogView/ViewModel and
MsaCreatorDialogView/ViewModel -> the *Dlg* names; FwMsaGroupBox.cs
-> MSAGroupBox.cs. Landed test/launcher names that keep the old
spelling (OptionsDialogTests, InsertEntryDialogTests,
AvaloniaOptionsDialogLauncher, Lcm*DialogLauncher) are untouched.
- Paths: Src/Common/FwAvalonia/Region/ -> .../Detail/; the moved
launcher (LexTextControls/Avalonia/), preview-host tests
(FwAvaloniaPreviewHost/FwAvaloniaPreviewHostTests/), and relocated
xWorks tests (Avalonia/Hosting/ for WinFormsUiaSmokeTests and
RecordEditViewActiveHostContractTests); ISeams.cs citations now point
at Src/Common/FwAvalonia/Seams/ (the file was split per interface).
Deliberately left (no landed equivalent to swap to; for the separate
alignment pass): browse-table machinery mentions
(SupportedAvaloniaBrowseToolNames, Phase1FollowUpBrowseTools,
ResolveBrowse_* tests, BrowseTableDriver, BrowseEditorIntegrationTests,
ClerkRoutedFilterTests), backed-out feature references (media seams,
RegionPictureEditor, FwGenDateField, rule-formula/interlinear types,
TreeSpikeAndRtlTests), prose uses of "region", and openspec/** by
decision.
Docs-only change; no code touched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three conversion skills drive the human-in-the-loop migration: convert-dialog and convert-slice (developer-gated phases: analysis, alignment, grilled integration-test plan, exemplar mapping with the capability-table verdict for custom controls, scaffold/implement, verify), and create-integration-test (drive + assert + snapshot per plan item, locations by the test-mirroring rule). Working documents live under Docs/migration/working/ (gitignored) with state-manifest resume and self-re-evaluating dependency gates (children and custom controls convert first). fieldworks-code-commenting encodes the repo comment standard. Docs/migration/ carries the design overview and the two developer playbooks the skills serve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Initial testing surfaced two defects. The live-app capture route owns the FieldWorks lifecycle (relaunch-per-tool, CloseMainWindow), so pointing a conversion at it killed the session the developer was exploring in; the conversion skills now forbid touching a running instance, require asking before any build or test run (a build fails on binaries the app holds locked), mark the analysis phase read-only so it runs alongside the developer, and default screenshot capture to the developer while they are in the app. The winapp skill warns at its own entry point. Gates were prose, so the agent reported, linked, summarized, and continued past them. A gate is now an interactive question that ends the turn, and summarizing a report in chat is banned -- the document is the artifact, and a summary invites skipping the review the gate exists for. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
VM also reads as virtual machine, so it is expanded everywhere in the docs and skills; the XAML xmlns:vm namespace alias stays, being real syntax. The comment standard now bans ambiguous abbreviations generally, in comments, documents, commit messages, and developer-facing communication. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The skills reported progress as "Phase 2" and "phase 4 gate passed", which means nothing to a developer who has not read the skill file. Each stage now has a name that says what is happening -- Understanding the dialog, Agreeing on how it works, Deciding what to prove, Planning the replacement, Building it, Proving it works -- and both skills carry a rule to use those names in plain language and never a numbered-stage shorthand. The playbooks list the stage names so the developer recognizes them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 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
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 Avalonia foundation for FieldWorks
LT-22625
FieldWorks can now convert a WinForms surface to Avalonia by a specified
procedure instead of by improvisation. Four detail surfaces and a family of
dialogs are here as proof that the procedure works — but the procedure, the
architecture it targets, and the rules that keep it honest are the deliverable.
Open Tools → Options → Interface, set New UI (preview) to New, and
Lexicon Edit, the Lexicon Edit popup, Notebook, and Grammar's Parts of Speech
render in Avalonia, live, without a restart. Leave it at the default and
FieldWorks does exactly what
maindoes.What "foundation" means here
A conversion is now a procedure with real stops. Understanding the dialog
or slice → agreeing on how it works → deciding what to prove → planning the
replacement → building it → proving it works. The middle three 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 explores the live
app; scaffolds never merge until the last stage passes.
Unfinished work is visible, not absent. Anything the Avalonia path cannot
yet render becomes a labeled
Unsupportedrow with a stable automation id —never a silent omission. Grep for those rows and you have the remaining
worklist.
SlicePluginRegistry.Registerthrows on a double claim for the samereason: one owner per legacy class, no silent overwrite.
Only reachable code ships. The Avalonia browse/bulk-edit surface was
removed rather than merged inert, and six scalar field kinds were deleted
rather than half-built. What is here is reachable and honest; what is not here
says so on screen.
The extension points are small. A new slice is an
ISlicePluginkeyed bythe legacy layout's
class=string, so migrating one edits no layout XML. A new surface enters the feature catalog only after its current product route, plugin, lifecycle, accessibility, localization, fallback, and workflow evidence all agree.Scope, stack, verification
485 files, +82,962 / −465.
UIModedefaults toLegacy, and both gatepredicates fail closed, so the default path is
main's. PRs #965-#967 areretired experiments, not a merge stack; their branches remain only for
archaeology. #968 is DRAFT, never merge.
Whole-solution build and full test verification were green before the final
documentation-only lesson update. Current CI is the source of truth for this
head.
Reading this a year from now — start here
The sections below are the decision record for the start of the FieldWorks
WinForms→Avalonia transition. They exist because the branch's working
documents — research, audits, rejected designs — were deliberately deleted
from the tree rather than merged. The durable contracts were promoted into
openspec/specs/; the reasoning that explains them is here.The indexed Avalonia migration lesson cards
preserve the reusable observations, rejected assumptions, evidence needs, and
decision boundaries from the retired follow-ups. They constrain future research;
they do not authorize restoring old code.
The layer cake — how an XML view definition becomes an Avalonia control
DetailFieldKindis deliberately small:Text,Chooser,ReferenceVector,Header,Custom,Unsupported.Two properties worth preserving. Composition is separate from rendering —
the new
DataTreeonly renders a pre-builtDetailModelsnapshot; it does notwalk XML and does not hold live state across data changes. And the view layer
is LCModel-free:
DetailField/DetailModelcarry primitives, strings anddelegates, never an
ICmObject, which is what lets the whole detail path betested headlessly without a language project.
Compilation is memoized process-wide, deliberately not with
Lazy<T>, so an IOfailure is not cached as permanent.
CompileAsyncexists and is tested, but theproduct path still composes synchronously on the UI thread — first-compile
latency is absorbed there today, mitigated only by the cache on later shows.
Decisions, and why
The UI gate is split across two assemblies on purpose.
FwUtils.UIModeGates.ShouldUseAvaloniaUI(26 call sites, 20 files) referencesno Avalonia type, so a legacy-mode process evaluates it without the CLR loading
Avalonia at all. Per-tool resolution (
EditSurfaceResolver,EditSurfaceRegistry,EditSurfaceSelectionService) lives inFwAvaloniaandis only reachable from code that has already loaded it. Every legacy call site
pairs the check with a
[MethodImpl(NoInlining)]helper holding the Avaloniabranch, so the JIT never resolves an Avalonia type on a Legacy path. Write the
obvious inline
if (ShouldUseAvaloniaUI(...)) new AvaloniaThing()and youdefeat the whole arrangement — every non-adopting user starts paying to load
Avalonia.
net48 pins Avalonia to the 11.3.x line. 11.3.x is the last line shipping
netstandard2.0 assemblies, which is what lets Avalonia load in-process beside
WinForms on net48. A bump past it breaks hosting, and not necessarily at
compile time. Stay on 11.x until WinForms is gone.
No Avalonia modal windows during coexistence. Avalonia modals are not
supported while WinForms owns the message loop, so a converted dialog is a
WinForms-owned modal hosting Avalonia content (
AvaloniaDialogHost), andShowModalthrows off the WinForms UI thread. A greenfield-AvaloniaWindow.ShowDialog()port would corrupt the message loop.The process is deliberately DPI-unaware. A WPF dialog once triggered DPI
awareness and blurred the entire application. DPI awareness arrives only after
all WinForms is gone; expect shimming until then.
FinalizerSafeSynchronizationContextprevents a process kill, not a glitch.Avalonia's MicroCom proxies capture the ambient
SynchronizationContextandpost native
Releasecalls from finalizer threads. If that lands after theWinForms marshaling window is torn down — project switch, shutdown, idle GC —
the post throws on the finalizer thread and takes the process with it. It is
installed once, before
SetupWithoutStarting(). Reordering the hostingbootstrap can silently reintroduce a GC-timing-dependent crash.
Routing keys off the legacy
class=attribute.ISlicePlugin.LegacyClassNamereuses the exact legacy string, so migrating a slice edits zero layout XML. The
alternative — minting new ids in the IR and updating every layout file — is a
far larger and riskier diff per slice.
DataTreeandSliceare reused as names for restructured types. These aredeliberate cross-namespace twins, disambiguated with
usingaliases at callsites; the rule is never to rename a type to dodge the collision. They cover
about half of what the legacy types did — state came out of the rows,
composition came out of the tree, writes came out of the keystroke path.
Seams are narrow by design. A seam is a substitution point in Feathers'
sense: product implementation and test double swap without editing a call site.
Seams/holds six of them and nothing else — an eviction pass moved asynchronization-context guard and a morph-type helper out to live beside their
consumers. Direct Avalonia APIs remain explicitly allowed at the UI edge. This
is not "wrap everything."
TsStringstays the shared text model. Clipboard and drag/drop betweenWinForms and Avalonia use the existing
"TsString"clipboard format carryingthe same XML native Views copy/paste already used. The migration replaces
rendering and editing, not the data model.
FieldWorks-owned Avalonia strings are
.resx, not XLIFF. This reversed anearlier decision that routed them through
LocalizationManagerdynamic stringsunder the Palaso app id. L10NSharp stays reserved for Palaso/FlexBridge/Chorus
UI.
Paths not taken — the section that stops a dead end being re-proposed
Plan A: extracting a model layer out of
DataTree. The roadmap originallyspecified pulling
DataTreeModel/SliceSpec/IDataTreeViewout ofDataTreeso both frameworks would share model types. Execution went the other way —
straight to the typed IR, bypassing
DataTreeinternals entirely. What tippedit: wiring new ports into
DataTreeinternals only serves code that getsdeleted at cutover, so it is throwaway work by construction.
DataTreeisfrozen legacy, slated for wholesale deletion at end of coexistence.
openspec/specs/avalonia-migration-roadmap/spec.mdcarries a superseded-requirementbanner pointing here for this rationale.
Every off-the-shelf dense tree/table control.
TreeDataGrid— its FOSS linewas archived 2025-10-13 and editing moved behind the commercial Avalonia
Accelerate license, incompatible with FieldWorks' redistribution model; this is
a permanent exclusion, not a revisit-later.
ItemsRepeater— on an upstreamdeprecation path, no selection or automation-peer support.
Avalonia.Controls.DataGrid— unmaintained Silverlight port with knownvirtualization and editing defects. Stock
TreeView— does not virtualize;retained only for small bounded popups.
Canonical view-definition storage. The recommendation was layered JSON:
shipped definitions plus sparse per-project patches keyed by
StableId.Rejected: YAML (new net48 dependency, whitespace hazards); new-schema XML
(indistinguishable at a glance from legacy
.fwlayout, which invitescopy-pasting the very vocabulary the migration exists to retire); DB-backed
definitions (opaque to diff, breaks the off-thread immutable-snapshot compile,
removes support staff's inspect-and-delete recovery); C# builders as the sole
store (cannot express customer overrides). This was a recommendation, not a
closed decision.
A launcher/companion-strip middle tier for slice conversion. Rejected so
resolution stays strictly plugin-registry-or-
Unsupportedand the unmigratedbacklog stays visible instead of hiding behind a half-migrated tier.
A shared base class for the filterable dropdowns. The three controls diverge
in content shape, popup hosting and selection model, so a common base would
fight the divergent parts. A static helper instead.
Lazy
DataTreeconstruction, considered to shrink the native-Views blastradius in
RecordEditView: 67 call sites plus a subclass injecting its ownDataTree, for zero benefit — the empty constructor already realizes no nativeViews.
Reversals — built, then removed
The Avalonia browse / bulk-edit surface. A complete owned browse stack —
sortable headers, inline cell edit, checkbox bulk-edit, filtering, multi-sort,
drag-reorder columns, RDE new-row entry, diacritic-aware find/replace, CSV
export, per-cell UIA peers, ~200 tests — was built, reviewed, and then pulled
rather than merged inert. Sort, filter, bulk-edit and selection were never
wired through to the real
RecordClerk, so no UI mode could reach theinteractive features; it only ever rendered alongside the hidden-but-functional
legacy
BrowseViewer. Browse and list tools use the legacyBrowseViewer, full stop. Retired #967assumed the dormant implementation still existed and therefore cannot be
rebased or cherry-picked. The browse lesson card
records the reusable contracts and the evidence required before any new design.
Three disconnected tracks, converged. Before this branch opened there were
three: a clean seam layer built but wired to nothing, a typed view-definition
IR consumed by nobody, and the only working end-to-end renderer — a spike stack
on a hand-written three-field lossy DTO — ignoring both. The spike was retired
and the preview host moved onto the same detail-model boundary as product code,
so there is no duplicate UI stack to keep in sync. Surviving runtime types were
renamed by role rather than spike history. This convergence is the reason the
branch exists in the shape it does, and it is visible in no single commit.
The Chorus notes bar. Built, including a reverse-engineered LibChorus
file-format compatibility contract, then removed unregistered along with the
browse surface. The Messages slice keeps its census route.
The dialog-launcher-as-slice-editor seam. Removed in favor of the simpler
plugin-registry-or-
Unsupportedmodel.Six scalar field kinds and the picture/media path.
Boolean,Date,EnumCombo,Integer,Image,Command, plus the picture editor and itsmedia seam, were deleted rather than half-built. Each now renders as an
Unsupportedrow — a scope narrowing, not a defect.Free
Popupdropdowns →Flyout. Free popups render in their own top-leveland misplace under fractional display scaling. A regression test now asserts no
Popupexists in the logical tree while a dropdown is open, so the patterncannot silently return.
The migration's own vocabulary. "Region" — a word invented during the
migration — was replaced throughout with "Detail", matching the legacy
DataTree/Sliceidiom, and the invented "Lexical" prefix was droppedwherever the concept is generic. The user-facing toggle stopped being labeled
"Lexical Edit UI", which named the internal project rather than what the user
was choosing. Migration-invented words are a tax on everyone who arrives later.
Surprising findings — where investigation contradicted the assumption
DataTreesilently drops the same part references the new importerdrops — roughly 63 of them. The importer's "unresolved" list matches a
pre-existing legacy gap exactly; it is not a regression.
~70.5% of elements and ~51.1% of attribute occurrences. That number drove the
explicit per-node diagnostic-drop taxonomy instead of silent loss.
if/ifnotconditionals — 230occurrences across shipped files. Ghost, conditional and chooser-link metadata
was reserved in the schema before the format froze, specifically to avoid an
early breaking
formatVersionbump when those land.dates. Composing from
GenDate.ToLongString()let the parser seed the yearfrom the first digit run, silently corrupting day- or month-precise dates.
buttons stole focus from the text box before reading its selection, collapsing
it to a caret. Invisible to tests that set selection directly instead of
clicking — a warning about that entire class of test.
shipping browse column is both
editable="true"and a simple field. The valueis in the Edit view and in bulk-edit.
ms/key). That is not an end-to-end claim; real layout, shaping, render and IME
latency remain unmeasured. Do not cite it as proof the UI is fast.
What this does NOT authorize — the limits of the XML retirement
XML retirement is per-surface, never global. A surface may disable runtime XML
only when every applicable blocker family clears for the layouts it loads:
custom fields, ghosts, conditionals, menus and hotlinks, styling, table/browse
XML, chooser metadata, parameter substitution, cross-class part resolution,
dynamic editors, native-viewing coupling.
Repo-wide XML deletion additionally waits on browse views in every area,
Interlinear, Notebook/Grammar/Lists detail, and publishing layouts — none of
which this branch authorizes. Any global "XML is gone" claim would be false for
years.
The same applies to the native boundary: a region is not migrated while it
still calls
RootSite/IVwEnv/native Views for display, layout, hit-testing,selection or editor realization. Linguistics services — XAmple, spelling, ICU,
encoding converters, parsers — may remain behind explicit service contracts,
because they do not own the UI. Without that distinction "no native code" would
have been unachievable and therefore meaningless.
Deferred, and what would unblock it
// PARITYmarkersacross 18 files (allomorph type-mismatch warning, link-allomorph and MSA
first-item pickers, Insert Entry duplicate search not matching gloss, MGA
catalog import). Legacy is unaffected by all of them.
does not ship here.
Analysesand the six rule-formula routes stay Legacy.PRs Phase-1 follow-up: Avalonia interlinear editor (Words Analyses) #965 and Phase-1 follow-up: Avalonia rule-formula editors (Grammar) #966 are archaeological sources only; the
interlinear
and rule-formula
cards require fresh characterization and current-tree design before a route
can be proposed for activation.
StTextmulti-paragraph editing — designed, not wired. ORC-embedded runsstay permanently read-only by design. A content census found such content only
in
sttextfields; the trigger to revisit is real project data showing it instring/multistringfields.the MSA/MGA dialogs and the create-new-feature catalogs is deferred.
over-preserving legacy quirks while the surface is still moving. The hidden
DataTree+DTMenuHandlerthat routes legacy right-click commands is acontract-gated command-routing exception, not a viewing exception, and is
named for removal then.
configure-dictionary, MGA help, print/PDF). Blocked on making XULRunner
startup lazy first — today a missing Firefox folder is an unconditional hard
crash at app start — and on validating per-surface Graphite-loss warnings. The
expensive part is the DOM/JS interop ports, not swapping the browser.
warnings rather than retired first. The re-research that forced that reversal:
HarfBuzz's Graphite2 shaping is opt-in, not default-enabled, so "Avalonia uses
Skia/HarfBuzz" does not by itself prove shaping parity.
documented, not fixed: the headless backend does no text hit-testing, so the
defect cannot be reproduced or self-validated there. It needs a real rendered
window.
How it stays off by default — the gate, in detail
Two predicates, both ordinal-ignore-case equality against a single
"New"literal, both failing closed on
null, empty, whitespace, a corrupt settingsfile, or any other value. Neither has a negation or fallback that could return
New accidentally.
Gate 2 is never more permissive than gate 1: before the preference is consulted,
an unregistered tool forces WinForms, and a per-tool opt-out
(
UIModeDisabledTools) forces WinForms. A blank tool name is the one case wheregate 2 adds no narrowing — it defers to the preference, which still defaults to
Legacy.
Every legacy else-branch is equivalent to
main, including theLexReferenceMultiSlicerestructure, traced case by case. The settings-migrationpath is untouched.
Shared code changed outside the gates, in full.
DTMenuHandler.OnDisplayDataTreeInsertgained|| m_dataEntryForm.IsExternalCommandAdapter;DataTree.IsExternalCommandAdapterdefaults to
falseand is settrueonly by New mode's hidden command-routingadapter, so the clause never fires under Legacy and the check evaluates
identically to
main.TsStringWrapperwentinternal→publicwith anadditive
FromXmlfactory andXmlproperty for the clipboard bridge; noexisting member changed behavior.
Visible at the default anyway. The new Options chooser in both the WinForms
and Avalonia dialogs; two new settings (
UIMode,UIModeDisabledTools);per-column
Name/AccessibleNameon legacy browse filter-bar combos instead ofa uniform
"FwComboBox"literal, an intentional screen-reader improvementpinned by a WinForms UIA test; and
FwAvalonia/FwAvaloniaDialogsnowreferenced by
FieldWorks,xWorksandLexTextControls, so the Avaloniabinaries deploy with every build.
Known defect, not fixed here: three
<see cref>references to the deletedLcmChooserDialogLaunchersurvive and will warn as CS1574.How this is tested
Everything is net48 — there is no separate build lane for the Avalonia work,
because the legacy-vs-Avalonia choice is a runtime product switch, not a build
contract.
./build.ps1and./test.ps1remain the only entry points.Three verification surfaces, in the order a migrator uses them:
Avalonia.Headless.NUnit's[AvaloniaTest], with aTestAppBuilderconfigured.UseSkia()andUseHeadlessDrawing = false, sotests capture real rendered frames rather than layout alone.
FwAvaloniaRuntime.AppBuilderOverrideis the seam that lets integrationtests driving
RecordEditViewrun without popping real Win32 windows.Src/Common/FwAvaloniaPreviewHost/) — runs a surfacestandalone, no language project needed. It isolates whether a bug is in our
code or in the WinForms-hosting plumbing.
DialogSnapshot.Capturealways writes a real Skia PNGto a gitignored folder even on a passing test, for eyeballing, paired with
DialogLayoutAssertas the deterministic tripwire for overlap, zero-areatext and crowding.
ViewDefinitionModel.ToSnapshot()is the semantic lane: a deterministic textdump of the typed IR.
EngineIsolationAuditTestspins isolation from nativeGraphite/Views. A dedicated pin test guards
FwAvaloniaPlatform.IsHeadless,which resolves Avalonia internals by reflection — an Avalonia bump could
relocate them and make headless detection silently return false forever with no
compile error.
This change is