LT-22625 Phase-1 base: Avalonia migration spine (UIMode defaults Legacy)#964
LT-22625 Phase-1 base: Avalonia migration spine (UIMode defaults Legacy)#964johnml1135 wants to merge 7 commits into
Conversation
3a905c6 to
b423a19
Compare
This comment has been minimized.
This comment has been minimized.
…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>
10d7181 to
af20c5b
Compare
This comment has been minimized.
This comment has been minimized.
…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.
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>
6636e17 to
40ae9d1
Compare
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>
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>
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>
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>
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>
- 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>
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.
|
@C:/Users/johnm/AppData/Local/Temp/claude/C--Users-johnm-Documents-repos-FieldWorks-worktrees-010-advanced-entry-view/2b14e829-7fba-4b8e-9d29-78ed6e0d2c6c/scratchpad/pr964-audit-final.md |
This comment has been minimized.
This comment has been minimized.
Findings from an adversarial audit of the FwUtils/LexText slice of PR #964: - Move the shared New-mode gate to FwUtils.UIModeGates and extract every gated Avalonia branch into a [MethodImpl(NoInlining)] helper. The predicate lived on AvaloniaOptionsDialogLauncher, whose generic base class forces the CLR to load FwAvaloniaDialogs/Avalonia when the declaring type loads, so merely checking the gate on the Legacy path loaded the Avalonia stack; inline branch bodies likewise forced the load at JIT of legacy-reachable methods. Now Legacy code paths never resolve Avalonia types unless a gate passes. - Label the Options dialog's Lexical Edit UI "New" mode as beta in both the WinForms (LexOptionsDlg) and Avalonia (OptionsDialogView) dialogs, per dialog parity rules. - Document LcmChooserDialogLauncher's intentionally-unwired staging status so it doesn't read as dead code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LRtgBkxYxQfBMGuZGKqEw
Introduces the seam that lets FieldWorks host either the legacy WinForms editing surface or the new Avalonia one, gated entirely on a UIMode setting that defaults to "Legacy". With that default, every call site added by this branch resolves to its original, unchanged WinForms code path; the Avalonia surface only activates when a user explicitly opts in via the Options dialog. Key pieces: - LexicalEditSurface/LexicalEditSurfaceResolver and a fenced RegionEditContextHolder in RecordEditView, choosing between the legacy DataTree and the Avalonia LexicalEditHostControl. - ~26 dialog-launcher call sites (Insert/Merge/Go To Entry, MSA and grammatical-info choosers, LexReference launchers) gated by the shared FwUtils.UIModeGates.ShouldUseAvaloniaUI(uiMode) predicate. - FwAvalonia/FwAvaloniaDialogs infrastructure, wired in but inert until UIMode=New. Legacy loader isolation. The gate predicate lives in FwUtils with no Avalonia-referencing types, and every gated branch that touches an Avalonia launcher is extracted into a [MethodImpl(NoInlining)] helper, so Legacy execution never resolves an Avalonia type unless a gate actually passes. Startup localization method-lookup gets its own try/catch, so a missing/corrupt FwAvalonia.dll costs only the Avalonia dynamic strings, never the Chorus/Palaso localization managers. UI. A "Lexical Edit UI" mode chooser is added to Tools > Options on both the WinForms (LexOptionsDlg) and Avalonia (OptionsDialogView) sides, each carrying a "Beta: the New mode is incomplete" note. Also fixes a RecordEditView.Dispose leak (m_dataEntryForm was only detached, not disposed, if the legacy surface was never initialized), closes a coverage gap on the UIMode PropTable-seeding path, and ships LcmChooserDialogLauncher as tested-but-unwired staging infrastructure (private constructor; migrates real chooser sites in a later phase). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| return; | ||
|
|
||
| if (!m_panel.Controls.Contains(m_dataEntryForm)) | ||
| m_panel.Controls.Add(m_dataEntryForm); |
There was a problem hiding this comment.
Disclosure (low severity, no behavior change): m_dataEntryForm now stays nested inside m_panel rather than being reparented as a sibling. This adds one level to the UI-Automation tree for Legacy users. Visually identical (Dock=Fill either way) — noted for the record.
| } | ||
| } | ||
|
|
||
| private void EnsureLegacySurfaceInitialized() |
There was a problem hiding this comment.
Disclosure (low severity, no behavior change): legacy DataTree setup moved from Init() to lazy first-record-show via EnsureLegacySurfaceInitialized(). Same code, later timing — the surface is now built when the first record is shown rather than during Init().
| assembly 9.0.0.2). Keep this until the host resolves DiagnosticSource directly | ||
| through the main package graph. --> | ||
| <dependentAssembly> | ||
| <assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /> |
There was a problem hiding this comment.
Disclosure (low severity): new process-wide binding redirect for System.Diagnostics.DiagnosticSource, added because FwAvalonia pulls it in. Versions are CPM-pinned solution-wide so there is no conflict today; noting it because it affects assembly resolution for everyone, Legacy included.
Review follow-up on the Phase-1 base PR (terminology and documentation only; no behavior changes). Terminology: the coined word "lane" is replaced everywhere with plain vocabulary chosen per sense — "evidence type" (semantic/visual/ workflow), "environment" (headless vs desktop), "path"/"case" (composer handling paths), "route" (burn-down census), "format" (clipboard payload), and "companion strip" (the mechanism's own name). "Chrome" in prose becomes plain words (decorations, frame, shell); identifiers keep their names. Renames: LexemeEditorBurnDown.LaneAbsorbedClassNames -> ComposerAbsorbedClassNames (with its tests) and MessagesCompanionLaneTests -> MessagesCompanionStripTests. The localization-policy sections and string accessors still carry the old wording; the follow-up localization commit rewrites them together with the code they describe. Skills consistency (review-audit findings): - fieldworks-winapp: stale hidden-desktop guidance removed everywhere; the visible-desktop reality (HEADLESS=false) is stated once in headless-rendering.md with a three-senses-of-headless note; the migration-capture campaign moved to navigation/migration-doc-capture.md. - Parity screenshot naming unified on parity-evidence.md section 6 (visual.legacy.png / visual.avalonia.png / visual.diff.png; manual bundleId form documented); the 01-winforms/02-avalonia scheme is removed. - fieldworks-avalonia-ui: snapshot path corrected to the flat folder the code writes; MinHeight 22 -> 24; Margin literals replaced by tokens in the template; duplicated rules collapsed into their canonical homes. - The stale claim that XAML projects are excluded from FieldWorks.proj is corrected (they build through the normal Src glob); build.ps1's duplicate Avalonia comment is deleted; avalonia.instructions.md now says new projects are picked up via the solution. - dialog-update: Options-dialog symbols marked as worked examples; the skill is now referenced from the hub and fieldworks-avalonia-ui. - Dead seam-catalog glob fixed; architecture-patterns TOC includes section 13; density-vs-styling cross-links added. CONTEXT.md gains glossary entries for the migration's core vocabulary (region, surface, seam, fenced edit session, settle, parity). AGENTS.md points at .mcp.json's documentation (strict JSON, no comments). Directory.Packages.props' Avalonia exception comment now states the dependency precisely (IRegionEditorPlugin.BuildControl returns Avalonia.Controls.Control; compile-only reference). Known residuals: Path3BundleTests emits a JSON key named "lanes" (bundle schema, not prose) and openspec historical records keep their original wording by design. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review decision (PR #964): FieldWorks-owned UI text localizes via .resx + ResourceManager + satellite assemblies, matching the official Avalonia localization guidance and the repo convention; L10NSharp/XLIFF stays reserved for UI supplied by Palaso, FlexBridge, or Chorus. No additional L10NSharp usage is permitted, including borrowing Palaso/Chorus catalog ids from FieldWorks-owned code. This reverses the earlier decision that routed FwAvalonia strings through LocalizationManager dynamic strings under the "Palaso" app id (recorded in architecture-patterns.md sec 11). Code: - FwAvaloniaStrings.resx / FwAvaloniaDialogsStrings.resx are populated from the accessors' inline English (141 + 153 entries) and become the single English source of truth; the previously emptied files carried the seed text only in C#. - FwAvaloniaStrings / FwAvaloniaDialogsStrings keep every property but resolve via ResourceManager; a missing entry falls back to the string id so drift is visible. Borrowed ids renamed to owned keys (Common.Cancel/OK/Help/Error -> FwAvalonia.*/FwAvaloniaDialogs.*). - FwAvaloniaLocalization and FwAvaloniaLocalizationBootstrap are deleted; TestAppBuilder/PreviewHost/xWorksTests bootstrap callers no longer need them (resx satellites load with CurrentUICulture; the xWorksTests bootstrap keeps only its Chorus manager for the legacy NotesBar). - FieldWorks.InitializeLocalizationManager no longer registers the FwAvalonia namespaces with the Palaso manager nor reflects Avalonia methods — FieldWorks strings stop appearing in the translator-facing Palaso XLIFF catalog, and FwAvalonia.dll no longer loads at startup for Legacy users. - L10NSharp package references dropped from FwAvalonia and FwAvaloniaTests; the central L10NSharp.Windows.Forms pin stays (it pins the transitive version) with its comment corrected. - AvaloniaLocalizationTests rewritten: every accessor property must resolve from the neutral resx, and resolution falls back to neutral English under a culture with no satellite. UI-language changes keep the existing restart-required behavior (LexOptionsDlg), so the known resx/x:Static no-live-update caveat changes nothing. Docs updated with the code: CONTEXT.md localization strategy entries; fieldworks-localization-review rewritten around the three strategies (StringTable for XML-configuration labels, .resx for FieldWorks-owned UI text, L10NSharp for Palaso/FlexBridge/Chorus UI); architecture-patterns.md sec 11 records the dated reversal; migration-checklist Phase 8, the migration hub step 8, fieldworks-avalonia-ui, and dialog-conversion.md sec 5 now teach the resx strategy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review decision (PR #964): the base PR ships only reachable surfaces. The Avalonia browse table was inert by design (no tool registered), so every piece that existed solely to serve it is removed rather than shipped dormant; a future PR reintroduces the browse surface as a whole. Removed (no reachable caller under any UIMode): - The four browse dialogs and their view models, inputs, and tests: Configure Columns, Filter For, Date Range Filter, Find/Replace. - The Avalonia browse view layer: LexicalBrowseView, BulkEditBarView, LexicalBrowseHostControl, BrowseCellRenderer, BrowseCsvExporter, the IBrowse* capability interfaces, BrowseColumnModel/ConfigStore, and their test suites (about 200 tests). - The xWorks browse adapters: ClerkBrowseRowSource, ClerkBrowseEditContext, BrowseColumnSpec, BrowseColumnEditSpec, and their tests. - RecordBrowseView's inert overlay and IBulkEditBarHost implementation and BrowseViewer's IBrowseColumnSource implementation - both files are restored to their pre-PR state (the FilterBar accessible-name change is unrelated and stays). - LexicalEditSurfaceResolver.ResolveBrowse and the empty/staged browse tool arrays, plus their resolver tests. - LcmChooserDialogLauncher (self-documented STAGING, private ctor, no caller) and its tests. - The unregistered Chorus notes-bar stack (ChorusNotesPlugin, ChorusNotesBarControl/Store, contract tests). The Messages slice keeps its ExplicitlyDeferred census route with an updated citation; the composer's read-only placeholder behavior is unchanged and stays tested (MessagesCompanionStripTests). Dead-code sweep driven by compiler and reference greps: 108 string accessor properties and their resx entries (browse/bulk/filter, unshipped interlinear labels, Chorus notes strings), the browse-only density tokens, the browse fixtures in VisualSnapshotTests and the BrowseTableDriver in the headless workflow harness, and stale browse/ChorusNotes claims in the skills and code comments. Whole-solution build (including test projects) green; FwAvaloniaTests 635/635; burn-down census, companion-strip, and edge-case fixtures 21/21. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review resolutions (PR #964), each independently verified: UIMode startup ordering (the New-mode restart bug): LoadUI runs inside the FwXWindow constructor and RecordEditView resolves its surface while the content views are being built, but the UIMode property was seeded into the PropertyTable only after NewMainAppWnd returned (and without broadcast) - so a persisted UIMode=New came up on the Legacy surface until the setting was toggled again. FwXWindow.InitMediatorValues now seeds UIMode/UIModeDisabledTools from the app settings before LoadUI, via a testable helper; the too-late seeding in FieldWorks.CreateAndInitNewMainWindow is removed. Both Options dialogs now compare the chosen mode against the PropertyTable's live value (falling back to settings), so OK heals a table/settings disagreement instead of skipping it as "no change". The three separate normalize-UI-mode implementations collapse onto the new LexicalEditSurfaceResolver.NormalizeUIMode (fail-closed; only "New" selects New). New tests: FwXWindowUIModeSeedingTests and resolver normalization cases. Edit-session task identity: LcmRegionEditSession detected "the clerk force-ended my task" (LT-16673) by undo-stack depth alone; if another component opened a new task before the stale session settled, a stale Commit/Cancel would end or roll back the unrelated task. The guard now also pins the undoable-sequence count captured at open, so the stale closers no-op unless the open task is provably the session's own. Two new lifecycle tests pin the interloper scenarios. Finalizer-safe context diagnostics: the ambient wrapper swallowed every failed Post, including async continuations dropped during teardown (a silent hang with skipped finally blocks). Swallowed posts are now classified: MicroCom finalizer Releases (the crash class the wrapper exists for, identified by the callback's MicroCom.* declaring type) trace at verbose level; anything else logs a warning through the FwAvaloniaHostInterop trace switch and routes through a loud-in-Debug drop handler (Debug.Fail by default; replaceable in tests). A pin test fails if an Avalonia bump relocates MicroCom.Runtime.MicroComProxyBase. Legacy persistence key restored: DataTreePersistContext now reproduces the original inline logic byte-for-byte - a config without a persistContext attribute yields "<vector>..DataTree" again, so users' persisted DataTree state is not silently reset. AvaloniaDialogHostTests: documented the sanctioned exception to the no-WinForms-Forms-in-tests rule (bare never-shown Form property bags whose subject IS Form property manipulation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review resolutions (PR #964) for the two New-mode parity deferrals that could silently pick the wrong object. Dependent auxiliary selection (the missing Go-family pattern): legacy BaseGoDlg children add per-entry controls under the matching list (LinkMSADlg's grammatical-info combo, LinkAllomorphDlg's allomorph combo); the Avalonia EntryGo dialog had no equivalent and both launchers silently used the entry's FIRST MSA/form. EntryGoDialogInput gains an opt-in auxiliary spec (label + a resolver returning key/display options for the selected result). When supplied, the dialog becomes two-stage: picking a result populates the auxiliary picker (a lone option auto-selects), Enter/double-click selects rather than commits, and OK is enabled only when both an entry and an option are chosen; the result carries the chosen key. Consumers without the spec keep the single-stage commit-on-select behavior unchanged (including the absent OK button). LcmLinkMsaDialogLauncher offers ALL of the chosen entry's MSAs (InterlinearName, legacy order) and applies the chosen one; LcmLinkAllomorphDialogLauncher offers the non-abstract forms (lexeme form first) and applies the chosen one. The pattern is documented as the Go-family exemplar in dialog-conversion.md 2c. Allomorph type-mismatch prompt: the legacy AddAllomorphDlg flow warns (CreateAllomorphTypeMismatchDlg) when the typed form's inferred morph type conflicts with the entry's existing allomorphs; the Avalonia launcher skipped it. The legacy condition is ported verbatim and the decision runs through an injectable confirmation seam (defaulting to an owner-parented FwMessageBox Yes/No with the legacy wording): No adds nothing, Yes creates the allomorph with the legacy MSA-ensuring switch in one undoable step. New strings follow the .resx strategy (9 accessor/resx pairs, seeded from the legacy wording). Tests: 8 new EntryGo headless tests plus new launcher fixtures (5 LinkMsa, 6 LinkAllomorph, 7 AddAllomorph); full FwAvaloniaDialogsTests suite green (279/279). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment-only cleanup of the preceding commits (one asserted string pair
changed in step): review/stage markers dropped ("LT-22625 review",
"(review, July 2026)", "Task 21", wave/D-label narration, "FUTURE PRs"
roadmaps); "in this PR" phrasing replaced with state-based wording
("not yet migrated"), including the ExplicitlyDeferred reason string
that LexemeEditorBurnDownTests asserts and two assertion messages;
change-narration removed ("preserving the prior behavior",
"byte-identical to the original inline logic" - the persisted-key
constraint now stands on its own; "parity with the pre-auxiliary
surface" - duplicate comment dropped); test-name narration in
FinalizerSafeSynchronizationContext corrected to a fixture-agnostic
pin-test note. Legacy-parity references (combo order, fallback
selection, wording sources) are kept: they document behavioral
contracts, not history.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase-1 base: Avalonia migration spine
https://jira.sil.org/browse/LT-22625
First of a 4-PR stack landing Phase 1 of the WinForms→Avalonia migration. It introduces the seam that lets FieldWorks host either the legacy WinForms editing surface or the new Avalonia one, gated entirely on a
UIModesetting that defaults toLegacy. At that default every call site added here resolves to its original, unchanged WinForms path; the Avalonia surface activates only when a user explicitly opts in via Tools → Options.What this PR contains
FullEntryRegionComposer), view-definition IR, owned controls, framework-neutral seam contracts, and the plugin registry.UIMode=New:lexiconEdit,lexiconEditPopup,notebookEdit,posEdit.FwUtils.UIModeGates.ShouldUseAvaloniaUI(uiMode)predicate.FwAvalonia/FwAvaloniaDialogsinfrastructure and the landed openspec change specs, plus the migration skills.RecordBrowseView/BrowseViewergained a full table overlay for list/browse tools — Configure Columns, Filter For/Restrict Date/Choose List flyouts, and a completeIBulkEditBarHost(bulk edit/copy/clear, Delete Rows, replace) — sitting on a large additiveBrowseViewerAPI (IBrowseColumnSource+ ~20 new methods). It activates per-tool throughLexicalEditSurfaceResolver.SupportedAvaloniaBrowseToolNames, which is empty in this PR, so every browse/list tool falls back to the legacyBrowseViewerregardless ofUIMode— asserted byInertFollowUpSurfacesFallBackToLegacy_BrowseTable.phase1-followup-table(see Stack) activates it one tool at a time.How Legacy stays safe
ShouldUseAvaloniaUIreturnstrueonly for an ordinal-ignore-case"New";null, empty, whitespace, or any other value → Legacy. Every one of the ~26 gate sites uses this single predicate.FwUtils, which references no Avalonia type, and every gated branch that touches an Avalonia launcher is extracted into a[MethodImpl(NoInlining)]helper. Legacy execution therefore never resolves an Avalonia type unless a gate actually passes. Startup localization method-lookup has its owntry/catch, so a missing/corruptFwAvalonia.dllcosts only the Avalonia dynamic strings, never the Chorus/Palaso localization managers.main(including theLexReferenceMultiSlicerestructure, traced case-by-case); the settings migration path is untouched; no static-init or reflection route reaches the new launchers ungated.DTMenuHandler.OnDisplayDataTreeInsert's enablement check gained|| m_dataEntryForm.IsExternalCommandAdapter.DataTree.IsExternalCommandAdapterdefaults tofalseand is settrueonly by New-mode's hidden command-routing adapter (RecordEditView.Avalonia.cs, tasks 13.4/15.4), which exists purely to keep a legacyDataTreealive for right-click menu routing while the Avalonia surface is on screen. Legacy never sets it, so the added clause never fires there — the check evaluates identically tomain— but it's a textual change to shared code outside the ~26 gate sites, so it's called out here rather than left implicit.InstallNewColumnsgained one line,m_avaloniaColumnFinders = null;, invalidating a cache field that only the (currently unreachable — see the Stage-3 browse mirror above) new methods ever read. Everything elseBrowseViewergained is new members bolted onto the class; no other existing method body changed.TsStringWrapperwidened frominternaltopublic, plus an additiveFromXmlfactory/Xmlproperty, so the cross-framework clipboard bridge (FwTsStringClipboard, task 3.13) can carry rich-text XML between the WinForms and Avalonia clipboard paths. No existing member's behavior changed.Legacy-visible changes (things existing users see even at the default)
The gate keeps behavior on Legacy identical, but this PR does make a few changes visible to all users, called out here so review isn't surprised:
LexOptionsDlg) and Avalonia (OptionsDialogView) dialogs, each carrying a "Beta: the New mode is incomplete" note.UIMode(defaultLegacy) andUIModeDisabledTools(default empty — a per-tool opt-out list read only after New mode is active).Name/AccessibleNamevalues instead of a uniform"FwComboBox"literal — an intentional screen-reader improvement, pinned by a WinForms UIA test.FieldWorks/xWorks/LexTextControlsreferenceFwAvalonia/FwAvaloniaDialogs, so the Avalonia binary stack deploys with every build andFwAvalonia.dllloads on core paths (behindFuncindirection + theNoInlininggates). "Legacy installs don't need Avalonia" is no longer true.New-mode parity deferrals
The New-mode (opt-in) path is narrower than the legacy dialogs it replaces in ~8 places, each marked
// PARITYin-code (e.g. allomorph type-mismatch warning, link-allomorph/MSA first-item pickers, Insert Entry duplicate search not matching gloss, MGA catalog import unported). Legacy is unaffected.LcmChooserDialogLauncherships with no production caller (private ctor, unreachable by design) — it is tested staging infrastructure for the futureReallySimpleListChoosermigration (~54 sites), documented as STAGING in its doc comment.Split out for separate assessment
A shared layout part (
LexSense-Detail-Pictures) would make sense pictures appear in the legacy Lexicon Edit pane (they have been silently omitted there for over a decade). It is a plausible legacy bug-fix but an ungated, legacy-visible behavior change, so it is not in this PR — it is proposed separately so the team can decide on it independently.Stack (each merges into the one above)
mainphase1-followup-interlinear→phase1-basephase1-followup-rule→phase1-followup-interlinearphase1-followup-table→phase1-followup-ruleVerification
Whole-solution build green; full CI-equivalent test run passes (7/7 CI checks). All ~26 gate sites verified fail-closed; every legacy else-branch verified equivalent to
main; TonePars/XAmpleParserare byte-identical tomain(this PR does not touch ParserCore/pcpatrflex). Draft until the stack is reviewed top-to-bottom.This change is