Skip to content

LT-22625 Phase-1 base: Avalonia migration spine (UIMode defaults Legacy) - #964

Open
johnml1135 wants to merge 50 commits into
mainfrom
phase1-base
Open

LT-22625 Phase-1 base: Avalonia migration spine (UIMode defaults Legacy)#964
johnml1135 wants to merge 50 commits into
mainfrom
phase1-base

Conversation

@johnml1135

@johnml1135 johnml1135 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

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 UIMode setting that defaults to Legacy. 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

  • The Avalonia migration framework: region/composer (FullEntryRegionComposer), view-definition IR, owned controls, framework-neutral seam contracts, and the plugin registry.
  • The base detail-editor surfaces active under UIMode=New: lexiconEdit, lexiconEditPopup, notebookEdit, posEdit.
  • ~26 dialog-launcher call sites (Insert/Merge/Go To Entry, MSA and grammatical-info choosers, LexReference launchers, Interlinear's Add Allomorph/Add New Sense, and the three Options-dialog launch sites) each gated by the shared FwUtils.UIModeGates.ShouldUseAvaloniaUI(uiMode) predicate.
  • FwAvalonia/FwAvaloniaDialogs infrastructure and the landed openspec change specs, plus the migration skills.
  • A Stage-3 Avalonia browse mirror, shipped but currently inert. RecordBrowseView/BrowseViewer gained a full table overlay for list/browse tools — Configure Columns, Filter For/Restrict Date/Choose List flyouts, and a complete IBulkEditBarHost (bulk edit/copy/clear, Delete Rows, replace) — sitting on a large additive BrowseViewer API (IBrowseColumnSource + ~20 new methods). It activates per-tool through LexicalEditSurfaceResolver.SupportedAvaloniaBrowseToolNames, which is empty in this PR, so every browse/list tool falls back to the legacy BrowseViewer regardless of UIMode — asserted by InertFollowUpSurfacesFallBackToLegacy_BrowseTable. phase1-followup-table (see Stack) activates it one tool at a time.

How Legacy stays safe

  • Fail-closed gate. ShouldUseAvaloniaUI returns true only 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.
  • Loader isolation. The gate lives in 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 own try/catch, so a missing/corrupt FwAvalonia.dll costs only the Avalonia dynamic strings, never the Chorus/Palaso localization managers.
  • Legacy else-branches unchanged. Every legacy branch of every gated site is byte-equivalent to main (including the LexReferenceMultiSlice restructure, traced case-by-case); the settings migration path is untouched; no static-init or reflection route reaches the new launchers ungated.
  • One shared (non-gated) site touched, but inert under Legacy. DTMenuHandler.OnDisplayDataTreeInsert's enablement check gained || m_dataEntryForm.IsExternalCommandAdapter. DataTree.IsExternalCommandAdapter defaults to false and is set true only by New-mode's hidden command-routing adapter (RecordEditView.Avalonia.cs, tasks 13.4/15.4), which exists purely to keep a legacy DataTree alive 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 to main — but it's a textual change to shared code outside the ~26 gate sites, so it's called out here rather than left implicit.
  • BrowseViewer's one existing-method touch is additive-only. InstallNewColumns gained 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 else BrowseViewer gained is new members bolted onto the class; no other existing method body changed.
  • TsStringWrapper widened from internal to public, plus an additive FromXml factory/Xml property, 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:

  • New Options control. A "Lexical Edit UI" mode chooser appears on Tools → Options → Interface, in both the WinForms (LexOptionsDlg) and Avalonia (OptionsDialogView) dialogs, each carrying a "Beta: the New mode is incomplete" note.
  • Two new settings. UIMode (default Legacy) and UIModeDisabledTools (default empty — a per-tool opt-out list read only after New mode is active).
  • Filter-bar accessibility identity. Legacy browse filter-bar combos now expose per-column Name/AccessibleName values instead of a uniform "FwComboBox" literal — an intentional screen-reader improvement, pinned by a WinForms UIA test.
  • Avalonia now ships with every install. FieldWorks/xWorks/LexTextControls reference FwAvalonia/FwAvaloniaDialogs, so the Avalonia binary stack deploys with every build and FwAvalonia.dll loads on core paths (behind Func indirection + the NoInlining gates). "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 // PARITY in-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. LcmChooserDialogLauncher ships with no production caller (private ctor, unreachable by design) — it is tested staging infrastructure for the future ReallySimpleListChooser migration (~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)

  1. thismain
  2. phase1-followup-interlinearphase1-base
  3. phase1-followup-rulephase1-followup-interlinear
  4. phase1-followup-tablephase1-followup-rule

Verification

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/XAmpleParser are byte-identical to main (this PR does not touch ParserCore/pcpatrflex). Draft until the stack is reviewed top-to-bottom.


This change is Reviewable

@github-actions

This comment has been minimized.

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
johnml1135 force-pushed the phase1-base branch 2 times, most recently from 10d7181 to af20c5b Compare July 1, 2026 16:23
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

NUnit Tests

    1 files  ±    0      1 suites  ±0   11m 44s ⏱️ + 1m 8s
5 776 tests +1 474  5 695 ✅ +1 466  81 💤 +8  0 ❌ ±0 
5 785 runs  +1 474  5 704 ✅ +1 466  81 💤 +8  0 ❌ ±0 

Results for commit f409b5a. ± Comparison against base commit 84a4850.

♻️ This comment has been updated with latest results.

@github-actions

This comment has been minimized.

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>
@github-actions

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
johnml1135 force-pushed the phase1-base branch 2 times, most recently from 6636e17 to 40ae9d1 Compare July 3, 2026 20:54
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>
@codecov-commenter

codecov-commenter commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.11628% with 210 lines in your changes missing coverage. Please review.
✅ Project coverage is 36.42%. Comparing base (84a4850) to head (f409b5a).
⚠️ Report is 16 commits behind head on main.

Files with missing lines Patch % Lines
Src/Common/FwAvalonia/AvaloniaDialogHost.cs 54.54% 34 Missing and 16 partials ⚠️
Src/Common/FwAvalonia/AvaloniaRegionHostControl.cs 48.80% 33 Missing and 10 partials ⚠️
...Controls/DetailControls/MorphTypeAtomicLauncher.cs 56.94% 19 Missing and 12 partials ⚠️
Src/Common/Controls/XMLViews/FilterBar.cs 40.47% 16 Missing and 9 partials ⚠️
Src/Common/FwAvalonia/CompositeDisposable.cs 31.03% 15 Missing and 5 partials ⚠️
Src/Common/FwAvalonia/FwAvaloniaPlatform.cs 61.53% 7 Missing and 8 partials ⚠️
Src/Common/FwAvalonia/FilterableDropdownSupport.cs 83.33% 2 Missing and 9 partials ⚠️
Src/Common/FieldWorks/WelcomeToFieldWorksDlg.cs 0.00% 7 Missing and 1 partial ⚠️
Src/Common/FwAvalonia/FwAvaloniaApp.cs 50.00% 3 Missing ⚠️
Src/Common/FwAvalonia/FwAvaloniaRuntime.cs 80.00% 1 Missing and 2 partials ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #964      +/-   ##
==========================================
+ Coverage   32.99%   36.42%   +3.43%     
==========================================
  Files        1202     1360     +158     
  Lines      278168   296299   +18131     
  Branches    37151    40316    +3165     
==========================================
+ Hits        91781   107930   +16149     
- Misses     158537   159018     +481     
- Partials    27850    29351    +1501     
Files with missing lines Coverage Δ
Src/Common/Controls/DetailControls/DataTree.cs 43.73% <ø> (+6.18%) ⬆️
Src/Common/FieldWorks/FieldWorks.cs 0.73% <ø> (ø)
Src/Common/Framework/MainWindowDelegate.cs 2.40% <ø> (ø)
Src/Common/FwAvalonia/CompactDialogStyles.cs 100.00% <100.00%> (ø)
Src/Common/FwAvalonia/FwAvaloniaDensity.cs 100.00% <100.00%> (ø)
Src/Common/FwAvalonia/FwCheckBoxStyle.cs 100.00% <ø> (ø)
Src/Common/FwAvalonia/FwPosChooser.cs 87.82% <ø> (ø)
Src/Common/FwAvalonia/FwRadioButtonStyle.cs 100.00% <ø> (ø)
Src/Common/FwAvalonia/FwSurfaceStyles.cs 94.28% <ø> (ø)
.../Common/FwAvalonia/InputKeyClaimingAvaloniaHost.cs 41.17% <ø> (ø)
... and 128 more

... and 145 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

This comment has been minimized.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

⚠️ Commit Message Format Issues ⚠️
commit 973a98a090:
1: T1 Title exceeds max length (78>72): "Rename the composer, edit contexts, and slice plugins to the Detail vocabulary"

commit 8d21d03385:
3: B1 Line exceeds max length (456>80): "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."

commit ac50ecc427:
13: B1 Line exceeds max length (86>80): " LexiconFirstSliceEditContextEdgeCaseTests, RegionEditContextEditingTests,"
14: B1 Line exceeds max length (81>80): " RegionEditSessionLifecycleTests, StructuredText{Adapter,Integration,"
24: B1 Line exceeds max length (82>80): " RegionContextMenuCompositionTests, RegionObjectCommandExecutionTests,"

commit 896e92dc58:
4: B1 Line exceeds max length (81>80): "the project root into a flat Avalonia/ folder, so the Avalonia launchers/adapters"

commit 5f56b0b794:
6: B1 Line exceeds max length (88>80): " InsertEntryDialog* -> InsertEntryDlg* (InsertEntryPayload -> InsertEntryDlgPayload)"
7: B1 Line exceeds max length (89>80): " AddNewSenseDialog* -> AddNewSenseDlg* (AddNewSensePayload -> AddNewSenseDlgPayload)"
8: B1 Line exceeds max length (88>80): " MsaCreatorDialog* -> MsaCreatorDlg* (MsaCreatorPayload -> MsaCreatorDlgPayload)"
9: B1 Line exceeds max length (86>80): " OptionsDialog* -> LexOptionsDlg* (OptionsState -> LexOptionsDlgState)"

commit d82088fe43:
1: T1 Title exceeds max length (74>72): "Audit PR comments: strip migration framing, doc markers, and absence notes"

commit f409b5aad6:
25: B1 Line exceeds max length (83>80): "still backs AttachedHandlerCount for the recycling assertions. Behavior-preserving."

commit d200a34016:
13: B1 Line exceeds max length (81>80): " ComposedRegionEditContext looks up the one handler and calls the relevant slot,"
14: B1 Line exceeds max length (82>80): " rejecting an unsupported gesture by finding a null slot - the same rejection the"
18: B1 Line exceeds max length (81>80): "- Decompose the two long walkers by concern (pure mechanical extraction, no logic"
21: B1 Line exceeds max length (81>80): " ResolveTextRowWritingSystems / BuildTextRowValues / RegisterTextRowEditHandler;"
25: B1 Line exceeds max length (81>80): "Behavior-preserving: staging, undo labels, and rejection semantics are unchanged."

commit de9fe70e99:
10: B1 Line exceeds max length (83>80): " optional IStructuredTextEditing interface. The one caller (FwStructuredTextField)"
12: B1 Line exceeds max length (81>80): " same rejection the core methods returned on a context with no StText rows. Only"
13: B1 Line exceeds max length (82>80): " the composed context and the preview/test doubles implement it; the base and the"
14: B1 Line exceeds max length (83>80): " in-memory/reversal contexts drop their stubs. Adding a future kind's ops is now a"
15: B1 Line exceeds max length (82>80): " new sub-interface plus one implementer, not a shotgun edit across every context."
19: B1 Line exceeds max length (81>80): " was removed from the Avalonia region (the picture slice renders the Unsupported"
22: B1 Line exceeds max length (81>80): " IPictureEditing that nothing implements or consumes would be empty ceremony, so"

commit 45a569a188:
18: B1 Line exceeds max length (81>80): "the create-feature / add-value event docs and class doc for the present contract."

commit 88d4a587c8:
36: B1 Line exceeds max length (81>80): "(EntryDlgListener) uses the persistProvider overload, which never shows it. Those"
40: B1 Line exceeds max length (81>80): "(only the visible marker is added), the configurable multi-column matching browse"

commit df74847c7c:
1: T1 Title exceeds max length (74>72): "Trim the lexical-edit region to string, list-choice, and one native plugin"
6: B1 Line exceeds max length (85>80): "Kept: the string editors (FwMultiWsTextField single/multi-WS, FwStructuredTextField),"
7: B1 Line exceeds max length (88>80): "the list-choice editors (FwChooserField, FwReferenceVectorField including the composer's"
9: B1 Line exceeds max length (88>80): "LexReferenceMultiSlice), the structural Header / Literal / Unsupported rows, and the one"
10: B1 Line exceeds max length (88>80): "IRegionEditorPlugin contract with ReversalIndexEntryPlugin as the sole native-conversion"
13: B1 Line exceeds max length (90>80): "Deleted: the RegionFieldKind values Boolean/Date/EnumCombo/Integer/Image/Command and their"
14: B1 Line exceeds max length (88>80): "factory build methods; the FwGenDateField editor; the picture path (RegionPictureEditor,"
15: B1 Line exceeds max length (90>80): "LcmRegionMediaServices, the IRegionMediaServices media seam, the RegionFieldControlFactory"
18: B1 Line exceeds max length (86>80): "WinFormsLegacyDialogLauncher / RegionEditorServices); and the WinForms companion strip"
19: B1 Line exceeds max length (92>80): "(AvaloniaCompanionSlices, ComposedCustomEditorField, ComposedEntryRegion.CustomEditorFields,"
21: B1 Line exceeds max length (86>80): "RegionPictureDialogResult are retained (relocated into LexicalEditRegionModel) for the"
24: B1 Line exceeds max length (93>80): "All dropped editors now render the labeled Unsupported worklist row (never silently omitted):"
25: B1 Line exceeds max length (86>80): "the dropped scalar kinds fall through the composer's field dispatch to the Unsupported"
26: B1 Line exceeds max length (89>80): "fallback, and an unclaimed editor="Custom" slice (other than the reference-vector routes)"
27: B1 Line exceeds max length (93>80): "resolves plugin registry -> Unsupported. The Unsupported row now carries its object and slice"
30: B1 Line exceeds max length (82>80): "FwMultiWsTextField lost its media dependency: the mediaServices ctor param and the"
31: B1 Line exceeds max length (93>80): "audio play/record row are gone; a voice/audio writing-system alternative renders as read-only"
32: B1 Line exceeds max length (96>80): "text (no in-pane player). The WS font/RTL/keyboard, the per-run rich-text seam (TrySetRichText),"
33: B1 Line exceeds max length (82>80): "the Character Style / WS retag / Insert Link / Delete Object context menu, and the"
36: B1 Line exceeds max length (91>80): "Tests: the burn-down census is rewritten to assert builtins == { ReversalIndexEntrySlice },"
37: B1 Line exceeds max length (90>80): "that the reference-vector classes are composer-absorbed (not plugin-claimed), and that the"
38: B1 Line exceeds max length (95>80): "formerly launcher-routed slices plus the Chorus notes bar are unclaimed; a composer test proves"
39: B1 Line exceeds max length (94>80): "an unclaimed custom slice composes as Unsupported and a plugin claim composes as a Custom row."
40: B1 Line exceeds max length (96>80): "Tests of deleted code were removed; factory/compose/visual tests were updated so dropped editors"
43: B1 Line exceeds max length (93>80): "Docs updated to the forward conversion path: the winforms-free-lexeme-editor decision doc and"
44: B1 Line exceeds max length (96>80): "native-views-audit describe the one plugin contract with the single native exemplar, no launcher"
45: B1 Line exceeds max length (98>80): "stopgap, no companion strip, and Unsupported rows as the burn-down worklist; the migration skill's"
46: B1 Line exceeds max length (92>80): "plugin-registry section and control-exemplar map are trimmed to the same shape with a worked"
47: B1 Line exceeds max length (89>80): ""convert a custom slice to a native editor" example anchored on ReversalIndexEntryPlugin."

commit ca58e388f6:
1: T1 Title exceeds max length (76>72): "Host the POS-chooser and option-picker dropdowns in flyouts, not free popups"

commit 32c925b51c:
1: T1 Title exceeds max length (82>72): "Fix Shift+Arrow selection in the multi-WS field; pin selection with headless tests"

commit de52e69131:
13: B1 Line exceeds max length (82>80): "multistring signal is carried on the row (LexicalEditRegionField.IsMultiStringRow,"
22: B1 Line exceeds max length (81>80): "single-string row yields the field menu plus mnuDataTree-Object, and that the two"

commit 02e14541af:
20: B1 Line exceeds max length (82>80): "inline affordances; the per-run font-swap display and the audio row are untouched."
23: B1 Line exceeds max length (83>80): "host menu as its single right-click surface (it owns the field's Swap/Convert-style"
24: B1 Line exceeds max length (81>80): "commands and Avalonia items cannot be merged into it), so the relocated rich-text"
33: B1 Line exceeds max length (81>80): "browse/in-cell path (showWritingSystemAbbreviation == false) still suppresses the"
37: B1 Line exceeds max length (81>80): "Tests: the FwAvaloniaTests that asserted the inline affordance buttons now assert"
41: B1 Line exceeds max length (81>80): "Added coverage that a text row draws no inline affordance buttons (the operations"

jasonleenaylor and others added 29 commits July 28, 2026 15:33
InputKeyClaimingAvaloniaHost had two consecutive <summary> blocks above
InputKeyClaimPolicy: the first (host-describing) was misattributed to the
policy class and made a duplicate summary, while the host class itself had
no doc. Moved the host-describing summary onto InputKeyClaimingAvaloniaHost,
left exactly one summary on InputKeyClaimPolicy, and added a summary to the
host constructor (it had a <param> but no summary).

Trimmed three over-long class summaries that narrated mechanism (HOW) down
to purpose plus the non-obvious contracts: EntryGoDialogViewModel (kept
commit-on-select single-stage and the opt-in two-stage auxiliary/description
contracts), EntryGoDialogView code-behind (condensed to the MVVM-boundary
statement), and FwMultiWsTextField (kept the "so a bold value can't crowd
or overlap the abbreviation" rationale, dropped the two-column-Grid detail).
Also de-jargoned residual inline section markers in FwFieldControls.cs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FwAvaloniaPlatform.IsHeadless resolves Avalonia internals by string name
(AvaloniaLocator in Avalonia.Base; IWindowingPlatform in Avalonia.Controls;
AvaloniaLocator.Current and its GetService method). Because these are not
public API, a version bump can relocate them with no compile break, leaving
the reflection returning null forever - silently reporting "not headless"
and disabling the headless-embed no-op guard for the entire suite.

Added a pin test mirroring the existing MicroCom namespace pin: it asserts
each reflected type/member still resolves in the referenced Avalonia (the
AvaloniaLocator type, the IWindowingPlatform type, the static Current
property, and the GetService(Type) method), failing loudly with a message
telling the maintainer to update FwAvaloniaPlatform if a bump moved them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The core edit-context interface carried every specialized edit gesture, so
adding a field kind's ops meant a widening edit that forced every implementer
(base, in-memory, composed, preview, reversal, the test fake) to stub a new
method. Factor the specialized clusters out.

- Move the four StText paragraph-CRUD methods (TrySetParagraphText,
  TrySetParagraphStyle, TryInsertParagraph, TryDeleteParagraph) into a new
  optional IStructuredTextEditing interface. The one caller (FwStructuredTextField)
  queries `ctx as IStructuredTextEditing` and treats null as "unsupported" - the
  same rejection the core methods returned on a context with no StText rows. Only
  the composed context and the preview/test doubles implement it; the base and the
  in-memory/reversal contexts drop their stubs. Adding a future kind's ops is now a
  new sub-interface plus one implementer, not a shotgun edit across every context.

- Remove the five picture methods (TryInsertPicture, TryReplacePictureFile,
  TryDeletePicture, TrySetPictureMetadata, TryInsertPictureOrc). Picture editing
  was removed from the Avalonia region (the picture slice renders the Unsupported
  row), no caller invoked them, and the composed context did not override them -
  they were dead stubs in every implementer. Wrapping dead surface in an
  IPictureEditing that nothing implements or consumes would be empty ceremony, so
  the cluster is removed instead. RegionPictureMetadata (used by the
  picture-properties dialog) is unaffected.

Behavior-preserving: no gesture that succeeded before is rejected now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The composed edit context routed writes through nine parallel
Dictionary<stableId, Func<...>> maps (text, rich-text, option, reference
add/remove, and four paragraph maps). Each field's edit behavior was scattered
across up to nine untyped Func maps that all had to be kept in sync by matching
the same string stable id - the flagged #1 maintainability concern.

- Replace the nine maps with a single Dictionary<stableId, FieldEditHandler>.
  FieldEditHandler carries one field's text/rich-text/option/reference/paragraph
  write delegates together (null where the field's kind does not support a
  gesture). A walker builds up a field's handler via ComposeState.HandlerFor;
  ComposedRegionEditContext looks up the one handler and calls the relevant slot,
  rejecting an unsupported gesture by finding a null slot - the same rejection the
  nine-map lookups produced on a missing key. The nine-argument constructor
  collapses to one handler-map argument.

- Decompose the two long walkers by concern (pure mechanical extraction, no logic
  change): WalkTextField (~200 lines) now delegates writing-system resolution,
  per-WS value/audio building, and edit-handler registration to
  ResolveTextRowWritingSystems / BuildTextRowValues / RegisterTextRowEditHandler;
  WalkOtherField's three switch-case bodies move to WalkReferenceAtomicField /
  WalkOwningAtomicField / WalkReferenceVectorField, leaving a thin dispatcher.

Behavior-preserving: staging, undo labels, and rejection semantics are unchanged.
The three xWorks tests that construct ComposedRegionEditContext directly were
updated to build handler maps; their assertions are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The three filterable region controls each carried their own copy of the same
filter-box-over-list machinery. FwOptionPicker (flat list + optional flyout),
FwPosChooser (tree + flyout), and FwFeatureStructureEditor (inline tree)
duplicated the pointer-release-over-own-item guard and the compact ListBoxItem
theme; the two tree controls additionally duplicated the name-contains filter
(the tree/flat-list visibility swap), the highlight-move helper, and the
compact TreeViewItem theme; the two flyout controls duplicated the chromeless
FlyoutPresenter theme.

A new FilterableDropdownSupport static helper in FwAvalonia factors out only
that genuine overlap: IsReleaseOverOwnItem and CompactListItemTheme (all
three), ApplyNameFilter, MoveListHighlight and CompactTreeItemTheme (the two
tree controls), and ChromelessPresenterTheme (the two flyout controls). A
static helper rather than a shared base class: the three differ in content
shape, popup hosting, and selection model, so a common base would fight the
divergent parts; shared state is passed in per call instead.

What stayed per control: FwOptionPicker keeps its own ApplyFilter and
MoveHighlight (search-delegate results and unavailable-option skipping are
genuinely different from the tree controls' name filter), plus its
multi-select, dropdown, and option-template logic; the tree controls keep
their tree building, selection, and create-row behavior. Behavior-preserving.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ~770-line constructor built every writing-system row inline. It now loops
over field.Values calling a private BuildValueRow, which reads as a short
sequence: BuildWsAbbrev, BuildValueBox, WireGhostPrompt, WireBridgeContextMenu
(returns hasBridge), the rich-text editing region, WireWritingSystemKeyboard,
the local context menu, and BuildRowPanel. The self-contained construction and
handler-wiring blocks moved into those focused private methods verbatim.

The rich-text editing region (bidi caret navigation, pointer normalization,
TextChanged staging, Ctrl+B/I/U, the character-style / writing-system /
insert-link / delete-ORC menu items, and clipboard) stays inline in
BuildValueRow rather than splitting into further Wire methods: its handlers and
the value box's context-menu Copy share the same mutable per-row state
(currentRich, lastStaged, pendingRichOverride) through their closures, so
separating them would require a per-row state holder and rewriting the caret
handlers to read that holder instead of the locals. The selection/caret logic
is unchanged, including the D1 caret-ordering fix (CaretIndex set BEFORE
SelectionStart/SelectionEnd on Shift+Arrow); every selection/caret handler is
byte-identical to before. D2 remains out of scope.

The manual List<Action> _teardown is replaced by a CompositeDisposable
(a new local type, since Avalonia 11 on net48 ships no CompositeDisposable and
the repo pulls in no System.Reactive): Dispose delegates to it, and its Count
still backs AttachedHandlerCount for the recycling assertions. Behavior-preserving.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…notes

Comment-only pass over the PR's added/changed code comments against the
revised review standard. No behavior change.

- Migration phase/stage framing stripped: "Phase N", "Stage N",
  "MSA-port Stage N", "Phase-1 §19b Stage N", "DATA-SAFETY (Phase 1, test a)"
  and similar temporal-position/future-stage prefixes and dividers; surviving
  behavior text kept and re-evaluated, comments with only process removed.
- Internal-doc and section markers removed: design/skill .md filenames
  (winforms-free-lexeme-editor.md, canonical-view-definition-design.md,
  dialog-ownership.md, native-views-audit.md, etc.) and pointers into them
  (§19a-§19g, §20.x, §8.3, D1/D3, F-1/F-7, LE-4, T-rubric tags, "task N.N",
  "(6.3/B8)", "(B7)", "19i.x"). Genuine WHY rewritten self-contained.
  LT-##### Jira refs preserved.
- Absence/stale narration deleted or rephrased to current fact
  ("no longer forces read-only" -> "does not force read-only";
  "replacement for the old detached DTO mapping" clause dropped).
- Creation-process / forward-work removed ("is deferred", "once other tools
  are wired", "before a second region reuses this", review-cleanup notes).
- Legacy temporal framing trimmed to plain current behavior; legacy parity
  WHY (naming the legacy equivalent) kept.
- String literals, code identifiers (e.g. Phase1FollowUpSurfaceTools),
  and pre-existing legacy comments left untouched.

Build: build.ps1 -SkipNative -BuildTests -> 0 errors, 0 warnings.
Tests: FwAvaloniaTests 634 (633 pass / 1 skip); FwAvaloniaDialogsTests 298/298.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the migration-invented 'Lexical' prefix from the region data/model
types (settled vocabulary); behavior-preserving identifier rename only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the migration-invented 'Lexical' prefix from the region view control
(LexicalEditRegionView -> RegionDataTree, per the settled vocabulary) and
retire the colliding xWorks LexicalEditRegionEditingTests name in favor of
RegionEditContextEditingTests; behavior-preserving identifier rename only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename the Avalonia region field-control factory to the Slice* vocabulary;
qualified as RegionSliceFactory(/Context/Tests) because a legacy
SliceFactory already exists in Common.Framework.DetailControls.
Behavior-preserving identifier rename only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the migration-invented 'Lexical' prefix from the edit-surface family
(enum, resolver, registry, factory, selection service, and the
RecordEditView surface helpers), per the settled vocabulary;
behavior-preserving identifier rename only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the migration-invented 'Lexical' prefix from the feature catalog and
feature-manager dialog family, moving them to the Lexicon* vocabulary
(Lexicon-area scope); behavior-preserving identifier rename only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the migration-invented 'Lexical' prefix from the Lexicon-tool-specific
region types (first slice, first-slice edit context, host control, and the
region builder now named LexiconEditErrorFallback), per the settled
vocabulary. Extract the writing-system keyboard activation into a dedicated
RegionKeyboard.ActivateForWritingSystem helper and repoint its two callers.
The helper stays in xWorks (not FwAvalonia/Region) because it needs the
LCModel cache and the FwAvalonia view layer is intentionally LCModel-free.
Behavior-preserving.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the migration-invented 'Lexical' prefix from the remaining region
support types (preview window/scenario/data-provider, refresh-coordinator
interface, override migration, and the headless editor driver), moving them
to the generic Region* vocabulary; behavior-preserving identifier rename
only. LexicalEditUiLabel (an options-dialog UI label, not a migration type)
is left as-is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apply the legacy stem + role suffix uniformly across each converted dialog's
type-set (Input/View/ViewModel/Payload/State), renaming files to match:

  InsertEntryDialog*  -> InsertEntryDlg*   (InsertEntryPayload -> InsertEntryDlgPayload)
  AddNewSenseDialog*  -> AddNewSenseDlg*    (AddNewSensePayload -> AddNewSenseDlgPayload)
  MsaCreatorDialog*   -> MsaCreatorDlg*     (MsaCreatorPayload  -> MsaCreatorDlgPayload)
  OptionsDialog*      -> LexOptionsDlg*     (OptionsState       -> LexOptionsDlgState)
  FwMsaGroupBox       -> MSAGroupBox        (the control; no suffix)

These deliberately reuse legacy stems that also live in LexTextControls; the
suffixed names stay unique simple names and MSAGroupBox is an intentional
cross-namespace twin (FwAvaloniaDialogs vs LexTextControls). x:Class /
x:DataType and doc-comment crefs move with the types. The LexTextControls
launchers that construct these dialogs are updated to the new names.

Notes on the twin:
- LcmInflectionFeatureCreateWiring lives in the LexTextControls namespace, so
  its unqualified references to the Avalonia MSAGroupBox now collide with the
  legacy WinForms MSAGroupBox (same-namespace wins). Its two method parameters
  and the type cref are qualified to FwAvaloniaDialogs.MSAGroupBox to keep them
  bound to the Avalonia control. This is required for compilation.
- Test fixture class/file names (InsertEntryDialogTests, FwMsaGroupBoxTests,
  etc.) are left unchanged since they are not part of the enumerated type-set;
  only the type references inside them are updated. The gitignored
  DialogSnapshot label literals ("FwMsaGroupBox-NN-...") are preserved as stable
  ids (they are not committed baselines).

Behavior-preserving. build.ps1 -SkipNative -BuildTests: 0 errors, 0 warnings.
Tests: FwAvaloniaDialogsTests 298/298; FwAvaloniaTests 634/633 passed/1 skipped;
LexTextControlsTests 351 total/348 passed/3 skipped (pre-existing skips).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move the xWorks region-bridge files out of the project root into a dedicated
Avalonia/ tree with coarse role subfolders, so the Avalonia region-editing
bridge reads as one area instead of being scattered among the legacy xWorks
files. Pure git mv (R100); xWorks.csproj is SDK-glob (only Compile Remove is
xWorksTests/**), so no compile-item edits are needed.

  Avalonia/            RegionEditContextBase, RegionEditContextHolder,
                       ISettlePendingEdits, RegionOverrideMigration
  Avalonia/Composer/   RegionComposer, RegionValueFactory, LcmRegionEditSession,
                       LexiconFirstSliceEditContext, LexiconEditErrorFallback
  Avalonia/Plugins/    RegionEditorPlugins, ReversalIndexEntryPlugin
  Avalonia/Hosting/    RecordEditView.Avalonia, AvaloniaRegionRefreshController,
                       XCoreMenuBridge, RecordClerkNavigationContext,
                       FwDragDropData, FwTsStringClipboard, RegionKeyboard

LexiconFirstSlice.cs is left in Src/Common/FwAvalonia/Region (not an xWorks
file). RecordEditView.Avalonia.cs stays a partial of RecordEditView (same
assembly/namespace, so the pairing is unaffected by the folder move) - verified
by the build.

Behavior-preserving folder move. build.ps1 -SkipNative -BuildTests: 0 errors,
0 warnings (xWorks + xWorksTests compile from the new layout).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
…ocabulary

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants