diff --git a/Docs/migration/CONTEXT.md b/Docs/migration/CONTEXT.md new file mode 100644 index 0000000000..6950dcf2ee --- /dev/null +++ b/Docs/migration/CONTEXT.md @@ -0,0 +1,38 @@ +# Migration-doc capture — shared language + +Scoped context for the legacy-screenshot capture work that feeds `Docs/migration/`. +Extends the root [`CONTEXT.md`](../../CONTEXT.md); only adds terms specific to this bounded context. + +## Terms +- **Legacy truth PNG**: a screenshot of a *legacy WinForms* FLEx surface (UIMode=Legacy), + captured as the visual parity baseline for its migration doc. Stored in + `Docs/migration/**/images/-NN.png`. NOT an Avalonia screenshot. +- **Capture target**: a screen that needs a legacy truth PNG — a **tool** (Area/Tool screen), + a **list editor**, or a **dialog**. Tracked in `INVENTORY.md`. +- **Launch-per-tool capture** (option 2): drive FLEx to a **tool** by launching + `FieldWorks.exe -db "" ""` where the link + (`silfw://localhost/link?app=flex&database=&tool=&guid=`, see + `FwLinkArgs.kFwUrlPrefix`) makes the shell open that tool at startup + (`FieldWorks.cs` → `FwAppArgs` → `HasLinkInformation`). Then screenshot the main window. +- **Dialog screenshot harness** (option 3): a standalone WinForms host that opens a + **language project** `LcmCache` (the `LCMBrowser` pattern), constructs a legacy dialog + (`new XDlg()` + `SetDlgInfo(cache, …)` — FLEx's two-phase dialog init), shows it with + seeded data, and renders it to a PNG. Reaches dialogs that navigation can't (conditional + delete/restore/warning dialogs) and doubles as the future legacy↔Avalonia parity rig. +- **Reachable set**: capture targets achievable by option 2 or 3. EXCLUDES Views/Gecko-coupled + dialogs (need a live `IVwRootBox`/`IVwSelection`, e.g. `RelatedWords`) and the 12 non-visual + Phase-2 internals — those stay **on-pickup**. +- **Capture manifest**: machine-readable list (toolId / dialog factory → target doc + image + path) the script and harness iterate over; derived from `INVENTORY.md`. + +## Invariants +- Capture in **UIMode=Legacy** only (this is the legacy baseline). The Avalonia surface has a + separate headless path. +- Data source for representative states: the **Sena 3** language project. +- A capture writes to the image path its doc already references; capturing never edits the + language project (open read-only / discard; close with Cancel/Escape). + +## Open questions +- Headless rendering: `Control.DrawToBitmap` works for ordinary dialogs but not for embedded + native **Views** content — confirm per-dialog during the harness build; fall back to a + visible/VDD desktop + window screenshot where DrawToBitmap is blank. diff --git a/Docs/migration/INVENTORY.md b/Docs/migration/INVENTORY.md new file mode 100644 index 0000000000..6811cddaa5 --- /dev/null +++ b/Docs/migration/INVENTORY.md @@ -0,0 +1,266 @@ +# WinForms → Avalonia migration inventory (master index) + +Auto-generated index of every in-scope WinForms component with a migration doc. +Goal: **100% coverage — no in-scope component without a tracked doc.** Each entry is a +lightweight stub (`_STUB_TEMPLATE.md`); deepen to the full format (`_TEMPLATE.md`) + +capture legacy PNGs (`fieldworks-winapp` skill) when its JIRA ticket is picked up. +See [README.md](./README.md) for the canonical-screen map and Phase-1 strategy. + +**Total components tracked: 227.** + +> Already migrated / kept canonical (not stubbed here — see README canonical-screen map): +> Lexicon Browse pane, Lexicon Edit entry pane (+ notebookEdit/posEdit), ChooserDialog, +> OptionsDialog, InsertEntryDialog, EntryGoDialog. Split to own follow-up PRs: the Words +> interlinear `Analyses` editor and the 6 Grammar rule tools (their tool stubs note this). + +## Phase 1 — user-facing surfaces + +### Deferred dialogs (detailed docs) (13) + +- [Add New Sense](./add-new-sense.md) +- [Configure Columns](./configure-columns.md) +- [Create Feature](./create-feature.md) +- [Date Range Filter](./date-range-filter.md) +- [Delete Confirmation](./delete-confirmation.md) +- [Feature Chooser](./feature-chooser.md) +- [Filter For](./filter-for.md) +- [Find Replace](./find-replace.md) +- [Lex Reference Details](./lex-reference-details.md) +- [Msa Creator](./msa-creator.md) +- [Picture Properties](./picture-properties.md) +- [Special Character](./special-character.md) +- [Writing System Properties](./writing-system-properties.md) + +### Dialogs (107) + +- [Add Allomorph Dlg](./dialogs/add-allomorph-dlg.md) +- [Add Converter](./dialogs/add-converter.md) +- [Add Custom Field](./dialogs/add-custom-field.md) +- [Add List](./dialogs/add-list.md) +- [Add New User](./dialogs/add-new-user.md) +- [Add New Vern Lang Warning](./dialogs/add-new-vern-lang-warning.md) +- [Advanced Mt Dialog](./dialogs/advanced-mt-dialog.md) +- [Anthro Field Mapping Dlg](./dialogs/anthro-field-mapping-dlg.md) +- [Apply Style](./dialogs/apply-style.md) +- [Archive With Ramp](./dialogs/archive-with-ramp.md) +- [Backup Project](./dialogs/backup-project.md) +- [Basic Find](./dialogs/basic-find.md) +- [Cant Restore Linked Files To Original Location](./dialogs/cant-restore-linked-files-to-original-location.md) +- [Change Default Backup Dir](./dialogs/change-default-backup-dir.md) +- [Choose Lang Project](./dialogs/choose-lang-project.md) +- [Choose Text Writing System Dlg](./dialogs/choose-text-writing-system-dlg.md) +- [Chooser](./dialogs/chooser.md) +- [Combine Import Dlg](./dialogs/combine-import-dlg.md) +- [Complex Conc Morph Dlg](./dialogs/complex-conc-morph-dlg.md) +- [Complex Conc Tag Dlg](./dialogs/complex-conc-tag-dlg.md) +- [Complex Conc Word Dlg](./dialogs/complex-conc-word-dlg.md) +- [Concordance Dlg](./dialogs/concordance-dlg.md) +- [Configure Interlin Dialog](./dialogs/configure-interlin-dialog.md) +- [Configure List](./dialogs/configure-list.md) +- [Conflicting Save](./dialogs/conflicting-save.md) +- [Create Allomorph Type Mismatch Dlg](./dialogs/create-allomorph-type-mismatch-dlg.md) +- [Delete Project](./dialogs/delete-project.md) +- [Delete Writing System Warning](./dialogs/delete-writing-system-warning.md) +- [Dictionary Config Mgr](./dialogs/dictionary-config-mgr.md) +- [Dictionary Configuration Import](./dialogs/dictionary-configuration-import.md) +- [Dictionary Configuration Manager](./dialogs/dictionary-configuration-manager.md) +- [Dictionary Configuration Node Rename](./dialogs/dictionary-configuration-node-rename.md) +- [Dictionary Configuration](./dialogs/dictionary-configuration.md) +- [Discourse Export Dialog](./dialogs/discourse-export-dialog.md) +- [Edit Morph Breaks Dlg](./dialogs/edit-morph-breaks-dlg.md) +- [Export Dialog](./dialogs/export-dialog.md) +- [Export Semantic Domains](./dialogs/export-semantic-domains.md) +- [Export Translated Lists](./dialogs/export-translated-lists.md) +- [Files To Restore Are Older](./dialogs/files-to-restore-are-older.md) +- [Filter Texts Dialog](./dialogs/filter-texts-dialog.md) +- [Find Example Sentence Dlg](./dialogs/find-example-sentence-dlg.md) +- [Flexbridge First Send Receive Instructions Dlg](./dialogs/flexbridge-first-send-receive-instructions-dlg.md) +- [Font](./dialogs/font.md) +- [Hc Max Compound Rules Dlg](./dialogs/hc-max-compound-rules-dlg.md) +- [Headword Numbers](./dialogs/headword-numbers.md) +- [Help About](./dialogs/help-about.md) +- [Import Char Mapping Dlg](./dialogs/import-char-mapping-dlg.md) +- [Import Date Format Dlg](./dialogs/import-date-format-dlg.md) +- [Import Enc Cvtr Dlg](./dialogs/import-enc-cvtr-dlg.md) +- [Import Match Replace Dlg](./dialogs/import-match-replace-dlg.md) +- [Import Word Set Dlg](./dialogs/import-word-set-dlg.md) +- [Insert Record Dlg](./dialogs/insert-record-dlg.md) +- [Insert Variant Dlg](./dialogs/insert-variant-dlg.md) +- [Interlinear Export Dialog](./dialogs/interlinear-export-dialog.md) +- [Interlinear Import Dlg](./dialogs/interlinear-import-dlg.md) +- [Lex Import Wizard Char Marker Dlg](./dialogs/lex-import-wizard-char-marker-dlg.md) +- [Lex Import Wizard Language](./dialogs/lex-import-wizard-language.md) +- [Lex Import Wizard Marker](./dialogs/lex-import-wizard-marker.md) +- [Lift Export Message](./dialogs/lift-export-message.md) +- [Lift Import Dlg](./dialogs/lift-import-dlg.md) +- [Lingua Links Import Dlg](./dialogs/lingua-links-import-dlg.md) +- [Link Allomorph Dlg](./dialogs/link-allomorph-dlg.md) +- [Link Entry Or Sense Dlg](./dialogs/link-entry-or-sense-dlg.md) +- [Link Msa Dlg](./dialogs/link-msa-dlg.md) +- [Link Variant To Entry Or Sense](./dialogs/link-variant-to-entry-or-sense.md) +- [Master Category List Dlg](./dialogs/master-category-list-dlg.md) +- [Master Inflection Feature List Dlg](./dialogs/master-inflection-feature-list-dlg.md) +- [Master Phonological Feature List Dlg](./dialogs/master-phonological-feature-list-dlg.md) +- [Merge Entry Dlg](./dialogs/merge-entry-dlg.md) +- [Merge Object](./dialogs/merge-object.md) +- [Merge Writing System](./dialogs/merge-writing-system.md) +- [Mga Dialog](./dialogs/mga-dialog.md) +- [Mga Html Help Dialog](./dialogs/mga-html-help-dialog.md) +- [Missing Old Fieldworks](./dialogs/missing-old-fieldworks.md) +- [Move Or Copy Files](./dialogs/move-or-copy-files.md) +- [New Lang Project](./dialogs/new-lang-project.md) +- [Notebook Export](./dialogs/notebook-export.md) +- [Occurrence Dlg](./dialogs/occurrence-dlg.md) +- [Overwrite Existing Project](./dialogs/overwrite-existing-project.md) +- [Parser Parameters Dlg](./dialogs/parser-parameters-dlg.md) +- [Project Location](./dialogs/project-location.md) +- [Project Properties](./dialogs/project-properties.md) +- [Record Go Dlg](./dialogs/record-go-dlg.md) +- [Related Words](./dialogs/related-words.md) +- [Respeller Dlg](./dialogs/respeller-dlg.md) +- [Restore Defaults Dlg](./dialogs/restore-defaults-dlg.md) +- [Restore Linked Files To Projects Folder](./dialogs/restore-linked-files-to-projects-folder.md) +- [Restore Project](./dialogs/restore-project.md) +- [Reversal Entry Go Dlg](./dialogs/reversal-entry-go-dlg.md) +- [Select Clauses Dialog](./dialogs/select-clauses-dialog.md) +- [Sfm To Texts And Words Mapping Dlg](./dialogs/sfm-to-texts-and-words-mapping-dlg.md) +- [Splash Screen](./dialogs/splash-screen.md) +- [Styles Modified](./dialogs/styles-modified.md) +- [Styles](./dialogs/styles.md) +- [Summary Dialog Form](./dialogs/summary-dialog-form.md) +- [Swap Lexeme With Allomorph Dlg](./dialogs/swap-lexeme-with-allomorph-dlg.md) +- [Try A Word Dlg](./dialogs/try-a-word-dlg.md) +- [Update Report](./dialogs/update-report.md) +- [Upload To Webonary](./dialogs/upload-to-webonary.md) +- [User Properties](./dialogs/user-properties.md) +- [Utility](./dialogs/utility.md) +- [Valid Characters](./dialogs/valid-characters.md) +- [View Hidden Writing Systems](./dialogs/view-hidden-writing-systems.md) +- [Warning Not Using Default Linked Files Location](./dialogs/warning-not-using-default-linked-files-location.md) +- [Webonary Log Viewer](./dialogs/webonary-log-viewer.md) +- [Xml Diagnostics](./dialogs/xml-diagnostics.md) +- [Xml Doc Configure](./dialogs/xml-doc-configure.md) + +### Choosers & launchers (28) + +- [Atomic Reference Launcher](./choosers-launchers/atomic-reference-launcher.md) +- [Audio Visual Launcher](./choosers-launchers/audio-visual-launcher.md) +- [Configure Writing Systems Dlg](./choosers-launchers/configure-writing-systems-dlg.md) +- [Entry Sequence Reference Launcher](./choosers-launchers/entry-sequence-reference-launcher.md) +- [Gen Date Chooser Dlg](./choosers-launchers/gen-date-chooser-dlg.md) +- [Gen Date Launcher](./choosers-launchers/gen-date-launcher.md) +- [Ghost Lex Ref Launcher](./choosers-launchers/ghost-lex-ref-launcher.md) +- [Ghost Reference Vector Launcher](./choosers-launchers/ghost-reference-vector-launcher.md) +- [Lex Reference Collection Launcher](./choosers-launchers/lex-reference-collection-launcher.md) +- [Lex Reference Pair Launcher](./choosers-launchers/lex-reference-pair-launcher.md) +- [Lex Reference Sequence Launcher](./choosers-launchers/lex-reference-sequence-launcher.md) +- [Lex Reference Tree Branches Launcher](./choosers-launchers/lex-reference-tree-branches-launcher.md) +- [Lex Reference Tree Root Launcher](./choosers-launchers/lex-reference-tree-root-launcher.md) +- [Lex Reference Unidirectional Launcher](./choosers-launchers/lex-reference-unidirectional-launcher.md) +- [Master Category List Chooser Launcher](./choosers-launchers/master-category-list-chooser-launcher.md) +- [Morph Type Atomic Launcher](./choosers-launchers/morph-type-atomic-launcher.md) +- [Morph Type Chooser](./choosers-launchers/morph-type-chooser.md) +- [Msa Dlg Launcher](./choosers-launchers/msa-dlg-launcher.md) +- [Phone Env Reference Launcher](./choosers-launchers/phone-env-reference-launcher.md) +- [Possibility Atomic Reference Launcher](./choosers-launchers/possibility-atomic-reference-launcher.md) +- [Possibility Vector Reference Launcher](./choosers-launchers/possibility-vector-reference-launcher.md) +- [Record Reference Vector Launcher](./choosers-launchers/record-reference-vector-launcher.md) +- [Rev Entry Senses Collection Reference Launcher](./choosers-launchers/rev-entry-senses-collection-reference-launcher.md) +- [Semantic Domain Reference Launcher](./choosers-launchers/semantic-domain-reference-launcher.md) +- [Semantic Domains Chooser](./choosers-launchers/semantic-domains-chooser.md) +- [Simple Integer Match Dlg](./choosers-launchers/simple-integer-match-dlg.md) +- [User Interface Chooser](./choosers-launchers/user-interface-chooser.md) +- [Vector Reference Launcher](./choosers-launchers/vector-reference-launcher.md) + +### Tool screens (34) + +- [Adhoc Coprohib Edit](./tools/adhoc-coprohib-edit.md) +- [Analyses](./tools/analyses.md) +- [Bulk Edit Entries Or Senses](./tools/bulk-edit-entries-or-senses.md) +- [Category Browse](./tools/category-browse.md) +- [Complex Concordance](./tools/complex-concordance.md) +- [Compound Rule Advanced Edit](./tools/compound-rule-advanced-edit.md) +- [Concordance](./tools/concordance.md) +- [Corpus Statistics](./tools/corpus-statistics.md) +- [Environment Edit](./tools/environment-edit.md) +- [Features Advanced Edit](./tools/features-advanced-edit.md) +- [Grammar Sketch](./tools/grammar-sketch.md) +- [Interlinear Edit](./tools/interlinear-edit.md) +- [Lexicon Browse](./tools/lexicon-browse.md) +- [Lexicon Classified Dictionary](./tools/lexicon-classified-dictionary.md) +- [Lexicon Dictionary](./tools/lexicon-dictionary.md) +- [Lexicon Edit Popup](./tools/lexicon-edit-popup.md) +- [Lexicon Edit](./tools/lexicon-edit.md) +- [Lexicon Problems](./tools/lexicon-problems.md) +- [Natural Classedit](./tools/natural-classedit.md) +- [Notebook Browse](./tools/notebook-browse.md) +- [Notebook Document](./tools/notebook-document.md) +- [Notebook Edit](./tools/notebook-edit.md) +- [Phoneme Edit](./tools/phoneme-edit.md) +- [Phonological Features Advanced Edit](./tools/phonological-features-advanced-edit.md) +- [Phonological Rule Edit](./tools/phonological-rule-edit.md) +- [Pos Edit](./tools/pos-edit.md) +- [Prod Restrict Edit](./tools/prod-restrict-edit.md) +- [Rapid Data Entry](./tools/rapid-data-entry.md) +- [Reversal Tool Bulk Edit Reversal Entries](./tools/reversal-tool-bulk-edit-reversal-entries.md) +- [Reversal Tool Edit Complete](./tools/reversal-tool-edit-complete.md) +- [Spelling](./tools/spelling.md) +- [Tool Bulk Edit Phonemes](./tools/tool-bulk-edit-phonemes.md) +- [Tool Bulk Edit Wordforms](./tools/tool-bulk-edit-wordforms.md) +- [Word List Concordance](./tools/word-list-concordance.md) + +### List editors (CmPossibility) (33) + +- [Affix Category Edit](./lists/affix-category-edit.md) +- [Annotation Def Edit](./lists/annotation-def-edit.md) +- [Anthro Edit](./lists/anthro-edit.md) +- [Chartmark Edit](./lists/chartmark-edit.md) +- [Charttemp Edit](./lists/charttemp-edit.md) +- [Complex Entry Type Edit](./lists/complex-entry-type-edit.md) +- [Confidence Edit](./lists/confidence-edit.md) +- [Dialects List Edit](./lists/dialects-list-edit.md) +- [Domain Type Edit](./lists/domain-type-edit.md) +- [Education Edit](./lists/education-edit.md) +- [Ext Note Type Edit](./lists/ext-note-type-edit.md) +- [Feature Types Advanced Edit](./lists/feature-types-advanced-edit.md) +- [Genres Edit](./lists/genres-edit.md) +- [Languages List Edit](./lists/languages-list-edit.md) +- [Lex Ref Edit](./lists/lex-ref-edit.md) +- [Locations Edit](./lists/locations-edit.md) +- [Morph Type Edit](./lists/morph-type-edit.md) +- [People Edit](./lists/people-edit.md) +- [Positions Edit](./lists/positions-edit.md) +- [Publications Edit](./lists/publications-edit.md) +- [Rec Type Edit](./lists/rec-type-edit.md) +- [Restrictions Edit](./lists/restrictions-edit.md) +- [Reversal Tool Reversal Index Pos](./lists/reversal-tool-reversal-index-pos.md) +- [Role Edit](./lists/role-edit.md) +- [Semantic Domain Edit](./lists/semantic-domain-edit.md) +- [Sense Status Edit](./lists/sense-status-edit.md) +- [Sense Type Edit](./lists/sense-type-edit.md) +- [Status Edit](./lists/status-edit.md) +- [Text Markup Tags Edit](./lists/text-markup-tags-edit.md) +- [Time Of Day Edit](./lists/time-of-day-edit.md) +- [Translation Type Edit](./lists/translation-type-edit.md) +- [Usage Type Edit](./lists/usage-type-edit.md) +- [Variant Entry Type Edit](./lists/variant-entry-type-edit.md) + +## Phase 2 — shell / framework / native rendering (net10 + cross-platform) + +### Shell & framework (6) + +- [App Lifetime Startup](./phase2/shell/app-lifetime-startup.md) +- [Main Window](./phase2/shell/main-window.md) +- [Mediator Propertytable](./phase2/shell/mediator-propertytable.md) +- [Menus Toolbars Statusbar](./phase2/shell/menus-toolbars-statusbar.md) +- [Panes Splitters](./phase2/shell/panes-splitters.md) +- [Sidebar Navigation](./phase2/shell/sidebar-navigation.md) + +### Native rendering (decommission targets) (6) + +- [Buffered Draw](./phase2/native-render/buffered-draw.md) +- [Datatree Slice Framework](./phase2/native-render/datatree-slice-framework.md) +- [Gecko Pdf Preview](./phase2/native-render/gecko-pdf-preview.md) +- [Graphite Engine](./phase2/native-render/graphite-engine.md) +- [Managed Views Host](./phase2/native-render/managed-views-host.md) +- [Views Engine](./phase2/native-render/views-engine.md) diff --git a/Docs/migration/README.md b/Docs/migration/README.md new file mode 100644 index 0000000000..7b1577c528 --- /dev/null +++ b/Docs/migration/README.md @@ -0,0 +1,55 @@ +# WinForms → Avalonia screen migration index + +This folder documents every FieldWorks WinForms screen in the lexical-edit migration +program: what it is, what it looks like (PNGs from live legacy FLEx), and the gotchas a +teammate needs to migrate it. Each **deferred** screen has a doc here + a JIRA ticket. + +> **[INVENTORY.md](./INVENTORY.md) is the master index** — every in-scope component +> (227 and counting), grouped by dialogs / choosers-launchers / tool-screens / list-editors, +> plus Phase-2 shell & native-render under `phase2/`. Stubs use `_STUB_TEMPLATE.md`; deepen to +> `_TEMPLATE.md` on pickup. The detailed deferred-dialog docs below are the worked examples. + +**Phase model.** Phase 1 (this program) = high-value features/bugfix-grade migrations behind +the `UIMode=New` flag (default `Legacy`, so nothing ships to default users yet). Phase 2 = +net10 / multiplatform / shell conversion (gated until Phase 1 + tester burn-down complete). + +**Canonical-per-primitive.** We keep ONE canonical screen per UI primitive as the reference +implementation teammates copy; everything else is documented here and backed out, to be +re-built/finished under its JIRA ticket. + +## Canonical screens (KEPT — copy these) +| Primitive | Canonical screen | Where | +|---|---|---| +| Virtualized editable TABLE | Lexicon Browse/Edit pane | `Src/Common/FwAvalonia/Region/LexicalBrowseView.cs` | +| Composed detail editor (DataTree replacement) | Lexicon Edit entry pane | `Src/xWorks/FullEntryRegionComposer.cs` | +| Tree + multi-selector | ChooserDialog | `Src/Common/FwAvaloniaDialogs/ChooserDialogView.axaml` | +| Tabs | OptionsDialog | `Src/Common/FwAvaloniaDialogs/OptionsDialogView.axaml` | +| Owned-control composite form | InsertEntryDialog | `Src/Common/FwAvaloniaDialogs/InsertEntryDialogView.axaml` | +| Search + list selector | EntryGoDialog | `Src/Common/FwAvaloniaDialogs/EntryGoDialogView.axaml` | + +Also kept (same composer, other record types): **notebookEdit**, **posEdit**. + +## Split into their own follow-up PRs (XL, isolated) +| Surface | OpenSpec change | +|---|---| +| Words interlinear editor (`Analyses`) | `avalonia-interlinear-editor` | +| Grammar rule family (6 tools) | `avalonia-rule-formula-editor` | + +## Deferred screens (DOCUMENTED + backed out → JIRA) +| Screen | Primitive | Doc | JIRA | +|---|---|---|---| +| ConfigureColumns | dual-list reorder | [configure-columns.md](./configure-columns.md) | _TBD_ | +| AddNewSense | owned-control form | [add-new-sense.md](./add-new-sense.md) | _TBD_ | +| MsaCreator | owned-control form | [msa-creator.md](./msa-creator.md) | _TBD_ | +| FeatureChooser | picker | [feature-chooser.md](./feature-chooser.md) | _TBD_ | +| CreateFeature | plain-form | [create-feature.md](./create-feature.md) | _TBD_ | +| DeleteConfirmation | plain-form | [delete-confirmation.md](./delete-confirmation.md) | _TBD_ | +| LexReferenceDetails | plain-form | [lex-reference-details.md](./lex-reference-details.md) | _TBD_ | +| FilterFor | plain-form / radios | [filter-for.md](./filter-for.md) | _TBD_ | +| DateRangeFilter | plain-form + date pickers | [date-range-filter.md](./date-range-filter.md) | _TBD_ | +| FindReplace | plain-form | [find-replace.md](./find-replace.md) | _TBD_ | +| PictureProperties | plain-form + media | [picture-properties.md](./picture-properties.md) | _TBD_ | +| SpecialCharacter | plain-form + list | [special-character.md](./special-character.md) | _TBD_ | +| WritingSystemProperties (core) | plain-form | [writing-system-properties.md](./writing-system-properties.md) | _TBD_ | + +> See [`_TEMPLATE.md`](./_TEMPLATE.md) for the per-screen doc format. PNGs go in `./images/`. diff --git a/Docs/migration/_STUB_TEMPLATE.md b/Docs/migration/_STUB_TEMPLATE.md new file mode 100644 index 0000000000..38b1d14682 --- /dev/null +++ b/Docs/migration/_STUB_TEMPLATE.md @@ -0,0 +1,35 @@ + + +# (``) + +| | | +|---|---| +| **Legacy class** | `` (`Src/…/.cs`) | +| **Area** | Lexicon / Grammar / Texts&Words / Notebook / Lists / Reversal / Shell / App-wide | +| **Type** | dialog / chooser / launcher / tool-screen / list-editor / browse / detail / shell / native-render | +| **Primitive** | plain-form / TABLE / TREE / MULTI-SELECTOR / TABS / owned-control / n/a | +| **State** | legacy / coexist (behind `UIMode=New`) / migrated / retired | +| **Phase** | 1 / 2 | +| **Canonical reference** | the kept canonical screen to copy when migrating (see Docs/migration/README.md) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![before](./images/-before.png) | ![after](./images/-after.png) | + +Same seeded data in both; attach both to the JIRA ticket. `before` = `fieldworks-winapp` capture +(launch-per-tool script for tool screens; `ScreenshotHarnessTests` harness for dialogs). `after` = +the Avalonia visual test for this surface (`fieldworks-semantic-render-parity` lane), added when the +Avalonia surface is built. + +## What it is + + +## Notes / gotchas +- + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (and capture legacy PNGs via the +> `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/_TEMPLATE.md b/Docs/migration/_TEMPLATE.md new file mode 100644 index 0000000000..bad1a2b285 --- /dev/null +++ b/Docs/migration/_TEMPLATE.md @@ -0,0 +1,46 @@ + + +# (legacy ``) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.…` (`Src/…/.cs`) | +| **Area / tool** | e.g. Lexicon › Browse filter bar | +| **Primitive(s)** | plain-form / TABLE / TREE / MULTI-SELECTOR / TABS / owned-control | +| **Canonical reference** | the kept canonical screen to copy (e.g. ChooserDialog for tree+multi-select) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/View.axaml(.cs)` @ git `` (recover from history) | +| **JIRA** | LT-XXXXX | + +## What it is +One or two sentences: what the user does with this screen and when it opens. + +## What it looks like (before / after) +Same seeded data in both, so the comparison is honest. Attach BOTH PNGs to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![ legacy](./images/-before.png) | ![ avalonia](./images/-after.png) | + +- **before** (legacy truth baseline): capture via the `fieldworks-winapp` skill — the launch-per-tool + script (tool screens) or the dialog harness (`ScreenshotHarnessTests`, dialogs). UIMode=Legacy, Sena 3. +- **after** (Avalonia): rendered from the SAME data by the Avalonia visual test for this surface in + `FwAvaloniaDialogs(Tests)` / `FwAvaloniaTests` — the `fieldworks-semantic-render-parity` lane. Added + when the Avalonia surface exists (during this ticket's implementation). + + +## Behaviour to preserve (parity checklist) +- [ ] …each interactive behaviour the legacy screen has +- [ ] validation / OK-gating rules +- [ ] keyboard / focus / accessibility expectations + +## Migration gotchas +- WS/RTL, owned-control hosting, undo-fencing, layout-choice resolution, etc. +- Anything the backed-out stub got wrong or left as `// PARITY`. + +## Wiring +- Legacy call site(s): `.cs:` +- The Avalonia path branched on `UIMode=New` here before back-out: `.cs:` +- Re-wiring target: this launcher/host should re-enter the Avalonia surface behind the flag. diff --git a/Docs/migration/add-new-sense.md b/Docs/migration/add-new-sense.md new file mode 100644 index 0000000000..1df2bf5de0 --- /dev/null +++ b/Docs/migration/add-new-sense.md @@ -0,0 +1,48 @@ +# Add New Sense (legacy `AddNewSenseDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.AddNewSenseDlg` (`Src/LexText/LexTextControls/AddNewSenseDlg.cs`) | +| **Area / tool** | Interlinear / Sandbox › morpheme/sense combo › "Add new sense…" | +| **Primitive(s)** | owned-control form (FwMultiWsTextField gloss + FwMsaGroupBox grammatical-info) | +| **Canonical reference** | InsertEntryDialog (owned-control form hosting a gloss field + FwMsaGroupBox) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/AddNewSenseDialogView.axaml(.cs)` + `AddNewSenseDialogViewModel.cs` @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +Lets the user add a new sense to an existing lexical entry from the interlinear Sandbox morpheme +combo: type a gloss (one row per analysis WS) and set the grammatical info (MSA), creating the sense +in one undoable step. Opens from the Sandbox sense/morph combo's "Add new sense" item. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![add-new-sense legacy](./images/add-new-sense-before.png) | ![add-new-sense avalonia](./images/add-new-sense-after.png) | +## Behaviour to preserve (parity checklist) +- [ ] Read-only citation form / headword shown at top (from the entry). +- [ ] Editable gloss: one `FwMultiWsTextField` row per analysis WS. +- [ ] Owned `FwMsaGroupBox` for grammatical info: POS choosers, slot picker, inflection-class picker, inflection-feature editor. +- [ ] OK is gated when the gloss is empty (legacy `AddNewSenseDlg_Closing` shows `ksFillInGloss` and cancels OK). +- [ ] No gate on the MSA box (always valid). +- [ ] Help button shown only when a help topic is available. + +## Migration gotchas +- WS/RTL: the gloss is multi-WS — each analysis WS gets its own row and must round-trip the right TsString. +- Owned-control hosting: the stub mounts `FwMsaGroupBox` and stages edits into `InMemoryRegionEditContext`. +- The stub header marks this an "MSA-port Stage 5 replacement for the legacy AddNewSenseDlg in New-UI mode". +- Stub markers to honour: `// Stage 3 wires the feature dialogs` and `// §19b Stage 3: wire the inline + create-feature / add-value affordances (replacing the deferred no-op)` — the inline create-feature/add-value + affordances are wired through `LcmInflectionFeatureCreateWiring`, verify they re-attach. + +## Wiring +- Legacy call site(s): `Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs` — the `using (new AddNewSenseDlg(...))` + block in the Legacy branch (below line 2495 in the same method). +- The Avalonia path branched on `UIMode=New` here before back-out: `SandboxBase.ComboHandlers.cs:2492` — + `LcmAddNewSenseDialogLauncher.Show(...)`, inside `if (AvaloniaOptionsDialogLauncher.ShouldUseAvaloniaOptionsDialog(uiMode))` + (the `UIMode` test is at line 2488). Launcher: `LcmAddNewSenseDialogLauncher` + (`Src/LexText/LexTextControls/LcmAddNewSenseDialogLauncher.cs`). +- Re-wiring target: this launcher/host should re-enter the Avalonia surface behind `UIMode=New`; + Legacy keeps `AddNewSenseDlg`. diff --git a/Docs/migration/choosers-launchers/atomic-reference-launcher.md b/Docs/migration/choosers-launchers/atomic-reference-launcher.md new file mode 100644 index 0000000000..6fca2c6db5 --- /dev/null +++ b/Docs/migration/choosers-launchers/atomic-reference-launcher.md @@ -0,0 +1,21 @@ +# Atomic Reference Launcher (`AtomicReferenceLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.AtomicReferenceLauncher` (`Src/Common/Controls/DetailControls/AtomicReferenceLauncher.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwOptionPicker (atomic owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +The in-slice control for an atomic (single-valued) object reference: shows the current target as an embedded view plus a launch button that opens a chooser to set/replace it. + +## Notes / gotchas +- Owned control embedded in a slice; communicates size changes back to the embedding slice. +- Setting the reference is a model write — wrap in the LCM unit-of-work; preserve undo grouping. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/audio-visual-launcher.md b/Docs/migration/choosers-launchers/audio-visual-launcher.md new file mode 100644 index 0000000000..1829d4cbc8 --- /dev/null +++ b/Docs/migration/choosers-launchers/audio-visual-launcher.md @@ -0,0 +1,21 @@ +# Audio/Visual Launcher (`AudioVisualLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.AudioVisualLauncher` (`Src/Common/Controls/DetailControls/AudioVisualSlice.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwOptionPicker (atomic owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +The button + filename view for a media (audio/visual) field; subclasses `ButtonLauncher` and launches the media player along with the embedded filename display. Defined inside `AudioVisualSlice.cs`. + +## Notes / gotchas +- Involves a linked media file path and an external player — porting must handle the LinkedFiles path resolution and the launch of the player, not just a chooser. +- Owned control inside `AudioVisualSlice`; not a standalone dialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/configure-writing-systems-dlg.md b/Docs/migration/choosers-launchers/configure-writing-systems-dlg.md new file mode 100644 index 0000000000..f1a7f5f2f3 --- /dev/null +++ b/Docs/migration/choosers-launchers/configure-writing-systems-dlg.md @@ -0,0 +1,21 @@ +# Configure Writing Systems (`ConfigureWritingSystemsDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.ConfigureWritingSystemsDlg` (`Src/Common/Controls/DetailControls/ConfigureWritingSystemsDlg.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | dialog | +| **Primitive** | MULTI-SELECTOR | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (multi-select checklist) | +| **JIRA** | LT-XXXXX | + +## What it is +Used by `MultiStringSlice` to choose *which* writing systems a multi-string slice displays (a checklist of available WSs). Distinct from `writing-system-properties.md`, which edits a single WS's properties — this one only selects the set of WSs shown. + +## Notes / gotchas +- NOT the WS properties editor; it is a WS visibility selector for a slice. Keep the two migration tickets separate. +- Result feeds back into the slice's displayed-WS list; verify ordering and persistence of the selection. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/entry-sequence-reference-launcher.md b/Docs/migration/choosers-launchers/entry-sequence-reference-launcher.md new file mode 100644 index 0000000000..c57f1aa6d4 --- /dev/null +++ b/Docs/migration/choosers-launchers/entry-sequence-reference-launcher.md @@ -0,0 +1,21 @@ +# Entry Sequence Reference Launcher (`EntrySequenceReferenceLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.EntrySequenceReferenceLauncher` (`Src/LexText/Lexicon/EntrySequenceReferenceLauncher.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control slice launcher for an ordered sequence of entry references (e.g. complex-form components). + +## Notes / gotchas +- Subclass of VectorReferenceLauncher. State=coexist (UIMode-gated). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/gen-date-chooser-dlg.md b/Docs/migration/choosers-launchers/gen-date-chooser-dlg.md new file mode 100644 index 0000000000..3bab4ecd38 --- /dev/null +++ b/Docs/migration/choosers-launchers/gen-date-chooser-dlg.md @@ -0,0 +1,21 @@ +# Generic Date Chooser (`GenDateChooserDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.GenDateChooserDlg` (`Src/Common/Controls/DetailControls/GenDateChooserDlg.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | nearest (small plain-form; OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it is +A chooser dialog for generic dates (`GenDate`) — lets the user pick an approximate/partial date (precision, era, possibly partial year/month/day). Launched by `GenDateLauncher`. + +## Notes / gotchas +- Edits the FieldWorks `GenDate` type (supports imprecise dates: before/about/after, partial fields) — not a plain calendar date. +- Returns the chosen `GenDate` to the launching slice. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/gen-date-launcher.md b/Docs/migration/choosers-launchers/gen-date-launcher.md new file mode 100644 index 0000000000..7320dfbd0d --- /dev/null +++ b/Docs/migration/choosers-launchers/gen-date-launcher.md @@ -0,0 +1,20 @@ +# Generic Date Launcher (`GenDateLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.GenDateLauncher` (`Src/Common/Controls/DetailControls/GenDateLauncher.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwOptionPicker (atomic owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +A button launcher (subclass of `ButtonLauncher`) that opens the generic-date chooser (`GenDateChooserDlg`) to edit a `GenDate` field shown in a slice. + +## Notes / gotchas +- Pairs with `GenDateChooserDlg`; edits the FieldWorks `GenDate` (imprecise/partial date) type, not a plain date. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/ghost-lex-ref-launcher.md b/Docs/migration/choosers-launchers/ghost-lex-ref-launcher.md new file mode 100644 index 0000000000..e7d076fc64 --- /dev/null +++ b/Docs/migration/choosers-launchers/ghost-lex-ref-launcher.md @@ -0,0 +1,21 @@ +# Ghost Lex Ref Launcher (`GhostLexRefLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.GhostLexRefLauncher` (`Src/LexText/Lexicon/GhostLexRefSlice.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control launcher for a ghost (empty-placeholder) lexical-reference slice; creates the relation on first use. + +## Notes / gotchas +- Subclass of ButtonLauncher; defined in GhostLexRefSlice.cs (line 50). State=coexist (UIMode-gated). Lexical-relations slice family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/ghost-reference-vector-launcher.md b/Docs/migration/choosers-launchers/ghost-reference-vector-launcher.md new file mode 100644 index 0000000000..1db0dcd26b --- /dev/null +++ b/Docs/migration/choosers-launchers/ghost-reference-vector-launcher.md @@ -0,0 +1,21 @@ +# Ghost Reference Vector Launcher (`GhostReferenceVectorLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.GhostReferenceVectorLauncher` (`Src/Common/Controls/DetailControls/GhostReferenceVectorSlice.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwReferenceVectorField (vector owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +Vector reference launcher used where a reference-vector slice would appear but the owning object does not yet exist (e.g. the Info tab of Texts/Words on a ghost Notebook record); subclasses `ButtonLauncher`. Inner class of `GhostReferenceVectorSlice`. + +## Notes / gotchas +- "Ghost" semantics: the owning object is NOT created until the user runs the chooser and clicks OK — porting must defer object creation to the OK path (data-loss/empty-object risk if created early). +- Candidate list comes from `ReferenceTargetServices.RnGenericRecReferenceTargetOwner`; currently used only for ghost Notebook-record properties (YAGNI note in source about configurable candidates). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/lex-reference-collection-launcher.md b/Docs/migration/choosers-launchers/lex-reference-collection-launcher.md new file mode 100644 index 0000000000..78f43a443a --- /dev/null +++ b/Docs/migration/choosers-launchers/lex-reference-collection-launcher.md @@ -0,0 +1,21 @@ +# Lex Reference Collection Launcher (`LexReferenceCollectionLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.LexReferenceCollectionLauncher` (`Src/LexText/Lexicon/LexReferenceCollectionLauncher.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control slice launcher for a lexical-relation COLLECTION reference; opens a chooser to add/remove members. + +## Notes / gotchas +- Subclass of VectorReferenceLauncher. State=coexist: file participates in UIMode gating. Part of the lexical-relations slice family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/lex-reference-pair-launcher.md b/Docs/migration/choosers-launchers/lex-reference-pair-launcher.md new file mode 100644 index 0000000000..f93b7831e0 --- /dev/null +++ b/Docs/migration/choosers-launchers/lex-reference-pair-launcher.md @@ -0,0 +1,21 @@ +# Lex Reference Pair Launcher (`LexReferencePairLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.LexReferencePairLauncher` (`Src/LexText/Lexicon/LexReferencePairLauncher.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control slice launcher for a PAIR (asymmetric) lexical-relation reference. + +## Notes / gotchas +- Subclass of AtomicReferenceLauncher. State=coexist (UIMode-gated). Lexical-relations slice family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/lex-reference-sequence-launcher.md b/Docs/migration/choosers-launchers/lex-reference-sequence-launcher.md new file mode 100644 index 0000000000..975a53e91c --- /dev/null +++ b/Docs/migration/choosers-launchers/lex-reference-sequence-launcher.md @@ -0,0 +1,21 @@ +# Lex Reference Sequence Launcher (`LexReferenceSequenceLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.LexReferenceSequenceLauncher` (`Src/LexText/Lexicon/LexReferenceSequenceLauncher.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control slice launcher for a SEQUENCE (ordered) lexical-relation reference. + +## Notes / gotchas +- Subclass of VectorReferenceLauncher. State=coexist (UIMode-gated). Lexical-relations slice family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/lex-reference-tree-branches-launcher.md b/Docs/migration/choosers-launchers/lex-reference-tree-branches-launcher.md new file mode 100644 index 0000000000..f1a7fd427c --- /dev/null +++ b/Docs/migration/choosers-launchers/lex-reference-tree-branches-launcher.md @@ -0,0 +1,21 @@ +# Lex Reference Tree Branches Launcher (`LexReferenceTreeBranchesLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.LexReferenceTreeBranchesLauncher` (`Src/LexText/Lexicon/LexReferenceTreeBranchesLauncher.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control slice launcher for the branches (children) side of a tree lexical relation. + +## Notes / gotchas +- Subclass of VectorReferenceLauncher. State=coexist (UIMode-gated). Lexical-relations slice family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/lex-reference-tree-root-launcher.md b/Docs/migration/choosers-launchers/lex-reference-tree-root-launcher.md new file mode 100644 index 0000000000..d917f358ff --- /dev/null +++ b/Docs/migration/choosers-launchers/lex-reference-tree-root-launcher.md @@ -0,0 +1,21 @@ +# Lex Reference Tree Root Launcher (`LexReferenceTreeRootLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.LexReferenceTreeRootLauncher` (`Src/LexText/Lexicon/LexReferenceTreeRootLauncher.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control slice launcher for the root (parent) side of a tree lexical relation. + +## Notes / gotchas +- Subclass of AtomicReferenceLauncher. State=coexist (UIMode-gated). Lexical-relations slice family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/lex-reference-unidirectional-launcher.md b/Docs/migration/choosers-launchers/lex-reference-unidirectional-launcher.md new file mode 100644 index 0000000000..f511940d5a --- /dev/null +++ b/Docs/migration/choosers-launchers/lex-reference-unidirectional-launcher.md @@ -0,0 +1,21 @@ +# Lex Reference Unidirectional Launcher (`LexReferenceUnidirectionalLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.LexReferenceUnidirectionalLauncher` (`Src/LexText/Lexicon/LexReferenceUnidirectionalLauncher.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control slice launcher for a UNIDIRECTIONAL lexical-relation reference. + +## Notes / gotchas +- Subclass of VectorReferenceLauncher. State=coexist (UIMode-gated). Lexical-relations slice family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/master-category-list-chooser-launcher.md b/Docs/migration/choosers-launchers/master-category-list-chooser-launcher.md new file mode 100644 index 0000000000..ab206a7ac1 --- /dev/null +++ b/Docs/migration/choosers-launchers/master-category-list-chooser-launcher.md @@ -0,0 +1,21 @@ +# Master Category List Chooser Launcher (`MasterCategoryListChooserLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MasterCategoryListChooserLauncher` (`Src/LexText/LexTextControls/MSAPopupTreeManager.cs`) | +| **Area** | Grammar | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Idle-time launcher that opens MasterCategoryListDlg to choose a grammatical category for a sense (added to avoid LT-11548 dispose race in MSAPopupTreeManager). + +## Notes / gotchas +- Defined inside MSAPopupTreeManager.cs (line 489). Opens MasterCategoryListDlg (which has its own Avalonia coexist path via LcmCreatePartOfSpeechLauncher). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/morph-type-atomic-launcher.md b/Docs/migration/choosers-launchers/morph-type-atomic-launcher.md new file mode 100644 index 0000000000..6ab402dcc3 --- /dev/null +++ b/Docs/migration/choosers-launchers/morph-type-atomic-launcher.md @@ -0,0 +1,21 @@ +# Morph Type Atomic Launcher (`MorphTypeAtomicLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.MorphTypeAtomicLauncher` (`Src/Common/Controls/DetailControls/MorphTypeAtomicLauncher.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwOptionPicker (atomic owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +Atomic reference launcher for the morph type of a lexeme/allomorph; subclasses `PossibilityAtomicReferenceLauncher` and opens the `MorphTypeChooser` (with its "Show all types" toggle). + +## Notes / gotchas +- Launches `MorphTypeChooser` rather than the generic list chooser — carry over the "show all types" behaviour. +- Changing morph type can change allomorph class/behaviour in the model; verify the write path and any side effects. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/morph-type-chooser.md b/Docs/migration/choosers-launchers/morph-type-chooser.md new file mode 100644 index 0000000000..a0ca61e38d --- /dev/null +++ b/Docs/migration/choosers-launchers/morph-type-chooser.md @@ -0,0 +1,21 @@ +# Morph Type Chooser (`MorphTypeChooser`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.MorphTypeChooser` (`Src/Common/Controls/DetailControls/MorphTypeChooser.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | chooser | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +A `SimpleListChooser` subclass that picks a morph type, adding a "Show all types" toggle to filter the hierarchical morph-type list. + +## Notes / gotchas +- Hierarchical (extends the tree-based `SimpleListChooser`/`ReallySimpleListChooser`); not flat. +- Adds a `&Show all types` link/button that re-loads the candidate list between the restricted and full type sets. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/msa-dlg-launcher.md b/Docs/migration/choosers-launchers/msa-dlg-launcher.md new file mode 100644 index 0000000000..2ad0868f3a --- /dev/null +++ b/Docs/migration/choosers-launchers/msa-dlg-launcher.md @@ -0,0 +1,21 @@ +# MSA Dialog Launcher (`MSADlgLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.MSADlgLauncher` (`Src/LexText/Lexicon/MSADlgLauncher.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +ButtonLauncher slice that opens the Create Grammatical Info (MSA) dialog from a sense's MSA slice. + +## Notes / gotchas +- State=coexist: in UIMode=New it calls LcmMsaCreatorDialogLauncher.Show seeded from the existing MSA; Legacy keeps WinForms MsaCreatorDlg. The launcher (slice button) is a distinct component from the MsaCreatorDlg it opens. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/phone-env-reference-launcher.md b/Docs/migration/choosers-launchers/phone-env-reference-launcher.md new file mode 100644 index 0000000000..dc38dfc138 --- /dev/null +++ b/Docs/migration/choosers-launchers/phone-env-reference-launcher.md @@ -0,0 +1,21 @@ +# Phonological Environment Reference Launcher (`PhoneEnvReferenceLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.PhoneEnvReferenceLauncher` (`Src/Common/Controls/DetailControls/PhoneEnvReferenceLauncher.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwReferenceVectorField (vector owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +The in-slice control for editing the set of phonological environments on an allomorph; shows the environment strings and a launch button, subclasses `ReferenceLauncher`. + +## Notes / gotchas +- Environment strings have their own parse/validation syntax (slash, underscore, brackets) — porting must preserve environment validation and error display, not just plain text editing. +- Communicates size changes back to the embedding slice. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/possibility-atomic-reference-launcher.md b/Docs/migration/choosers-launchers/possibility-atomic-reference-launcher.md new file mode 100644 index 0000000000..2f9bab711e --- /dev/null +++ b/Docs/migration/choosers-launchers/possibility-atomic-reference-launcher.md @@ -0,0 +1,21 @@ +# Possibility Atomic Reference Launcher (`PossibilityAtomicReferenceLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.PossibilityAtomicReferenceLauncher` (`Src/Common/Controls/DetailControls/PossibilityAtomicReferenceLauncher.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwOptionPicker (atomic owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +Atomic reference launcher specialized for a single possibility-list item (e.g. a single category from a `CmPossibilityList`); subclasses `AtomicReferenceLauncher` and implements `IVwNotifyChange`. + +## Notes / gotchas +- Listens for model changes (`IVwNotifyChange`) to refresh the displayed value — unsubscribe on dispose to avoid leaks. +- Candidate list is a hierarchical possibility list; chooser is the tree-based list chooser. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/possibility-vector-reference-launcher.md b/Docs/migration/choosers-launchers/possibility-vector-reference-launcher.md new file mode 100644 index 0000000000..e364764273 --- /dev/null +++ b/Docs/migration/choosers-launchers/possibility-vector-reference-launcher.md @@ -0,0 +1,21 @@ +# Possibility Vector Reference Launcher (`PossibilityVectorReferenceLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.PossibilityVectorReferenceLauncher` (`Src/Common/Controls/DetailControls/PossibilityVectorReferenceLauncher.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwReferenceVectorField (vector owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +Vector reference launcher specialized for possibility-list items (e.g. multiple categories/domains from a `CmPossibilityList`); subclasses `VectorReferenceLauncher` and implements `IVwNotifyChange`. Base class for `SemanticDomainReferenceLauncher`. + +## Notes / gotchas +- Listens for model changes (`IVwNotifyChange`) to refresh the displayed list — unsubscribe on dispose. +- Multi-select against a hierarchical possibility list; chooser is the tree-based list chooser. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/record-reference-vector-launcher.md b/Docs/migration/choosers-launchers/record-reference-vector-launcher.md new file mode 100644 index 0000000000..e1f8d054af --- /dev/null +++ b/Docs/migration/choosers-launchers/record-reference-vector-launcher.md @@ -0,0 +1,21 @@ +# Record Reference Vector Launcher (`RecordReferenceVectorLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.RecordReferenceVectorLauncher` (`Src/LexText/Lexicon/RecordReferenceVectorLauncher.cs`) | +| **Area** | Notebook | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control slice launcher for a vector reference field on a Data Notebook record. + +## Notes / gotchas +- Subclass of VectorReferenceLauncher. Not in the UIMode grep -> State=legacy. Notebook slice family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/rev-entry-senses-collection-reference-launcher.md b/Docs/migration/choosers-launchers/rev-entry-senses-collection-reference-launcher.md new file mode 100644 index 0000000000..c53ff326e7 --- /dev/null +++ b/Docs/migration/choosers-launchers/rev-entry-senses-collection-reference-launcher.md @@ -0,0 +1,21 @@ +# Rev Entry Senses Collection Reference Launcher (`RevEntrySensesCollectionReferenceLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.RevEntrySensesCollectionReferenceLauncher` (`Src/LexText/Lexicon/RevEntrySensesCollectionReferenceLauncher.cs`) | +| **Area** | Lexicon | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Owned-control slice launcher for the senses collection of a reversal entry. + +## Notes / gotchas +- Subclass of VectorReferenceLauncher. State=coexist (UIMode-gated). Reversal slice. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/choosers-launchers/semantic-domain-reference-launcher.md b/Docs/migration/choosers-launchers/semantic-domain-reference-launcher.md new file mode 100644 index 0000000000..5ddb144ca0 --- /dev/null +++ b/Docs/migration/choosers-launchers/semantic-domain-reference-launcher.md @@ -0,0 +1,21 @@ +# Semantic Domain Reference Launcher (`SemanticDomainReferenceLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.SemanticDomainReferenceLauncher` (`Src/Common/Controls/DetailControls/SemanticDomainReferenceLauncher.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwReferenceVectorField (vector owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +The in-slice control for a sense's semantic domains; subclasses `PossibilityVectorReferenceLauncher` and launches the dedicated `SemanticDomainsChooser` (tree + search) rather than the generic list chooser. + +## Notes / gotchas +- `internal` class; overrides chooser launch to open `SemanticDomainsChooser` (with its search/suggest panel). +- Multi-target edit against the semantic-domain possibility list — wrap writes in the LCM unit-of-work. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/semantic-domains-chooser.md b/Docs/migration/choosers-launchers/semantic-domains-chooser.md new file mode 100644 index 0000000000..08511db7ad --- /dev/null +++ b/Docs/migration/choosers-launchers/semantic-domains-chooser.md @@ -0,0 +1,22 @@ +# Semantic Domains Chooser (`SemanticDomainsChooser`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.SemanticDomainsChooser` (`Src/Common/Controls/DetailControls/SemanticDomainsChooser.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | chooser | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +A standalone `Form` for selecting semantic domains: shows the semantic-domain hierarchy in a TreeView plus a search/suggest ListView, used when assigning semantic domains to a sense. + +## Notes / gotchas +- Multi-select against a hierarchical possibility list (semantic domains); displays both a TreeView and a ListView (see `SemanticDomainSelectionUtility`). +- Has a search/suggest panel separate from the tree — selecting in one must keep the other in sync. +- Custom `DomainNode : LabelNode` for domain display formatting. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/simple-integer-match-dlg.md b/Docs/migration/choosers-launchers/simple-integer-match-dlg.md new file mode 100644 index 0000000000..d7c78b1104 --- /dev/null +++ b/Docs/migration/choosers-launchers/simple-integer-match-dlg.md @@ -0,0 +1,21 @@ +# Simple Integer Match (`SimpleIntegerMatchDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Controls.SimpleIntegerMatchDlg` (`Src/Common/Controls/XMLViews/SimpleIntegerMatchDlg.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | nearest (OptionsDialog; sibling of FilterFor/DateRangeFilter) | +| **JIRA** | LT-XXXXX | + +## What it is +Browse-view integer column filter dialog: pick a comparison (greater than / less than / equal / not equal / <= / >= / between) and one or two integer values; used from a numeric column's "Filter for…" menu. Integer sibling of `SimpleMatchDlg` (FilterFor) and `SimpleDateMatchDlg` (DateRangeFilter). + +## Notes / gotchas +- "Between" mode enables a second `NumericUpDown` and an "and" label; other comparisons use a single value — gate UI on the selected comparison. +- Comparison index order is hard-coded (GreaterThan=0 … Between=6); preserve the mapping when porting. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/user-interface-chooser.md b/Docs/migration/choosers-launchers/user-interface-chooser.md new file mode 100644 index 0000000000..37fde7d083 --- /dev/null +++ b/Docs/migration/choosers-launchers/user-interface-chooser.md @@ -0,0 +1,21 @@ +# User Interface Chooser (`UserInterfaceChooser`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Widgets.UserInterfaceChooser` (`Src/Common/Controls/Widgets/UserInterfaceChooser.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | chooser | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwOptionPicker (owned control on a Tools/Options tab) | +| **JIRA** | LT-XXXXX | + +## What it is +A `ComboBox` subclass for a Tools/Options tab that lists the writing systems into which the program UI has been (at least partially) localized, each language name shown in its own language and script. + +## Notes / gotchas +- It is an owned control (combobox), not a dialog — embeds in the Options dialog tab, so it migrates as a control on the OptionsDialog surface rather than a standalone screen. +- Items are `LanguageDisplayItem`s; display strings are localized UI language names — localization-sensitive. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/choosers-launchers/vector-reference-launcher.md b/Docs/migration/choosers-launchers/vector-reference-launcher.md new file mode 100644 index 0000000000..6527449bb7 --- /dev/null +++ b/Docs/migration/choosers-launchers/vector-reference-launcher.md @@ -0,0 +1,21 @@ +# Vector Reference Launcher (`VectorReferenceLauncher`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Framework.DetailControls.VectorReferenceLauncher` (`Src/Common/Controls/DetailControls/VectorReferenceLauncher.cs`) | +| **Area** | App-wide (these are shared controls) | +| **Type** | launcher | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | FwReferenceVectorField (vector owned control) | +| **JIRA** | LT-XXXXX | + +## What it is +The in-slice control for a vector (multi-valued) object reference: shows the current list of targets as an embedded view plus a launch button that opens a chooser to add/remove/reorder items. + +## Notes / gotchas +- Owned control embedded in a slice; communicates size changes back to the embedding slice. +- Multi-target edits (add/remove/reorder) are model writes — wrap in the LCM unit-of-work; preserve undo grouping. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/configure-columns.md b/Docs/migration/configure-columns.md new file mode 100644 index 0000000000..6276e16c72 --- /dev/null +++ b/Docs/migration/configure-columns.md @@ -0,0 +1,40 @@ +# Configure Columns (legacy `ColumnConfigureDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Controls.ColumnConfigureDialog` (`Src/Common/Controls/XMLViews/ColumnConfigureDialog.cs`) | +| **Area / tool** | Any browse view › column header context menu › "Choose Columns…" / "More Column Choices…" | +| **Primitive(s)** | dual-list reorder (available ↔ shown ListBoxes + Add/Remove/MoveUp/MoveDown) | +| **Canonical reference** | none of the kept canonicals is a dual-list; closest patterns: OptionsDialog (list + buttons), ChooserDialog (multi-select). Build fresh against the MVVM kit. | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/ConfigureColumnsDialogView.axaml(.cs)` + `ConfigureColumnsDialogViewModel.cs` (recover from git history on this branch) | +| **JIRA** | LT-XXXXX | + +## What it is +Lets the user choose which columns appear in a browse table, in what order. Opened from the +browse column-header menu. Returns the ordered set of visible column specs. + +## What it looks like + +![Configure Columns – initial](./images/configure-columns-01.png) + +## Behaviour to preserve (parity checklist) +- [ ] Two lists: "available" (catalog, grouped) and "shown" (ordered). +- [ ] Add / Remove move items between lists; duplicates in "shown" are guarded. +- [ ] Move Up / Move Down reorder the "shown" list; disabled at ends. +- [ ] The last remaining column cannot be removed. +- [ ] OK returns the ordered visible set; Cancel discards. +- [ ] Some columns appear multiple times legitimately (e.g. per-WS) — not deduped by label. + +## Migration gotchas +- The catalog comes from the browse view's column spec XML; ordering and WS-variants matter. +- The backed-out Avalonia stub implemented the dual-list + guards but was wired only for the + `UIMode=New` browse path; verify it preserved multi-instance columns. +- Re-wiring must round-trip the same column-spec objects the legacy `BrowseViewer` consumes. + +## Wiring +- Legacy invocation: browse column-header menu → `BrowseViewer` column-config command. +- Avalonia path before back-out: `Src/xWorks/RecordBrowseView.cs:726` + (`OnConfigureColumnsRequested` instantiated `ConfigureColumnsDialogViewModel`/`View`). +- Re-wiring target: `RecordBrowseView.OnConfigureColumnsRequested` re-enters the Avalonia + dialog behind `UIMode=New`; Legacy keeps `ColumnConfigureDialog`. diff --git a/Docs/migration/create-feature.md b/Docs/migration/create-feature.md new file mode 100644 index 0000000000..03a9bc3892 --- /dev/null +++ b/Docs/migration/create-feature.md @@ -0,0 +1,53 @@ +# Create Feature / Value (legacy `MasterInflectionFeatureListDlg` / `MasterPhonologicalFeatureListDlg` blank-create link) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MasterInflectionFeatureListDlg` (`Src/LexText/LexTextControls/MasterInflectionFeatureListDlg.cs`) and `MasterPhonologicalFeatureListDlg` (`Src/LexText/LexTextControls/MasterPhonologicalFeatureListDlg.cs`) — their blank-create link | +| **Area / tool** | Feature-structure editor › inline "create new feature / value" affordance | +| **Primitive(s)** | plain-form (2 fields: Name + Abbreviation) | +| **Canonical reference** | InsertEntryDialog (closest kept canonical for a small plain-form with text fields) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/CreateFeatureDialogView.axaml(.cs)` + `CreateFeatureDialogViewModel.cs` @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +A small dialog to create a new feature or feature value (Name + optional Abbreviation) inline from the +feature-structure editor, without going through the full master feature list. Invoked from the +`FwFeatureStructureEditor` create-feature / add-value affordances. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![master-phonological-feature-list legacy](./images/master-phonological-feature-list-before.png) | ![master-phonological-feature-list avalonia](./images/master-phonological-feature-list-after.png) | +## Behaviour to preserve (parity checklist) +- [ ] Name text field (required). +- [ ] Abbreviation text field (optional). +- [ ] OK gated on a non-empty Name; an in-dialog error message shows when Name is empty. +- [ ] Labels are parameterised for the feature vs. value flows (`NameLabel` / `AbbreviationLabel`). +- [ ] OK (default) / Cancel buttons. + +## Migration gotchas +- Stub header: "Phase-1 §19b Stage 3 … the LCModel-free collector behind the inline create affordances of + `FwFeatureStructureEditor`. It is the Avalonia replacement for the `MasterInflectionFeatureListDlg` / + `MasterPhonologicalFeatureListDlg` blank-create link". +- PARITY deferral (stub): "the heavy MGA-catalog import path is a documented PARITY deferral — it needs the + MGA assembly + GlossList XML parsing, outside this stage's clean reach." The Avalonia stub only covers the + blank-create link, not the full master-catalog import. +- The stub is LCModel-free (a collector); the launcher/wiring applies the result to the model. + +## Wiring +- Legacy call site(s): the blank-create link inside the WinForms `MasterInflectionFeatureListDlg` / + `MasterPhonologicalFeatureListDlg` (`Src/LexText/LexTextControls/`); the Legacy feature-editor path opens + that master list rather than this small dialog. +- The Avalonia path branched on `UIMode=New` here before back-out: the dialog is invoked via the + `FwMsaGroupBox` / `FwFeatureStructureEditor` create-feature + add-value events, routed through + `LcmInflectionFeatureCreateWiring` (`Src/LexText/LexTextControls/LcmInflectionFeatureCreateWiring.cs`). + Product call sites (CreateFeature / AddValue) of that wiring: + - `Src/LexText/LexTextControls/LcmMsaCreatorDialogLauncher.cs:252` and `:254` + - `Src/LexText/LexTextControls/LcmInsertEntryDialogLauncher.cs:555` and `:557` + - `Src/LexText/LexTextControls/LcmAddNewSenseDialogLauncher.cs:160` and `:162` + - Launcher: `LcmCreateFeatureLauncher` (`Src/LexText/LexTextControls/LcmCreateFeatureLauncher.cs`). +- Re-wiring target: the `LcmInflectionFeatureCreateWiring` create/add affordances re-enter the Avalonia + dialog behind `UIMode=New`; Legacy keeps the master-list blank-create link. diff --git a/Docs/migration/date-range-filter.md b/Docs/migration/date-range-filter.md new file mode 100644 index 0000000000..7e57e678a2 --- /dev/null +++ b/Docs/migration/date-range-filter.md @@ -0,0 +1,45 @@ +# Date Range Filter (legacy `SimpleDateMatchDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Controls.SimpleDateMatchDlg` (`Src/Common/Controls/XMLViews/SimpleDateMatchDlg.cs`) | +| **Area / tool** | Any browse view › date column header filter › "Filter for…" (date) | +| **Primitive(s)** | plain-form (relation combo + CalendarDatePickers) | +| **Canonical reference** | OptionsDialog (closest kept canonical for a small plain-form with controls) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/DateRangeFilterDialogView.axaml(.cs)` + `DateRangeFilterDialogViewModel.cs` (+ `DateRangeFilterPattern.cs`) @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +Lets the user filter a date browse column by a relation (on / not on / on-or-before / on-or-after / +between) and one or two dates. Opens from a date column's filter "Filter for…" command. Returns a +`DateRangeFilterPattern`. + +## What it looks like + +![Date Range Filter – initial](./images/date-range-filter-01.png) + +## Behaviour to preserve (parity checklist) +- [ ] Relation combo with 5 relations: on / not on / on-or-before / on-or-after / between. +- [ ] Start-date picker (required). +- [ ] End-date picker shown only when the relation is "between". +- [ ] Date semantics: start = midnight; end = last instant of the day (23:59:59.9999999) for inclusive matching. +- [ ] OK gated: missing start date blocks OK; for "between", the end day must be on or after the start day. + +## Migration gotchas +- Stub header: "the Avalonia counterpart of the legacy `SimpleDateMatchDlg`"; the header notes "Date + semantics mirror the legacy dialog" with the MIDNIGHT / LAST-INSTANT normalisation and the + "SelectionEnd extends to the very end of the day" rule. +- Legacy uses `DateTimePicker` controls; the Avalonia stub uses `CalendarDatePicker` — verify date round-trip + and the inclusive end-of-day boundary. +- The launcher receives a `handleGenDate` callback (constructor takes `(null, handleGenDate)`); preserve the + GenDate handling on re-wiring. + +## Wiring +- Legacy call site(s): the Legacy branch of the date-filter path in `Src/xWorks/RecordBrowseView.cs` + constructs the WinForms `SimpleDateMatchDlg` (`Src/Common/Controls/XMLViews/SimpleDateMatchDlg.cs`). +- The Avalonia path branched on `UIMode=New` here before back-out (direct, no launcher): + `Src/xWorks/RecordBrowseView.cs:717` — `new FwAvaloniaDialogs.DateRangeFilterDialogViewModel(null, handleGenDate);` + (View at `:718`). +- Re-wiring target: `RecordBrowseView` date-filter path re-enters the Avalonia dialog behind `UIMode=New`; + Legacy keeps `SimpleDateMatchDlg`. diff --git a/Docs/migration/delete-confirmation.md b/Docs/migration/delete-confirmation.md new file mode 100644 index 0000000000..9e79a0d431 --- /dev/null +++ b/Docs/migration/delete-confirmation.md @@ -0,0 +1,48 @@ +# Delete Confirmation (legacy `ConfirmDeleteObjectDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FdoUi.Dialogs.ConfirmDeleteObjectDlg` (`Src/FdoUi/Dialogs/ConfirmDeleteObjectDlg.cs`) | +| **Area / tool** | Lexicon › lexical-reference slice (and other delete-object flows) › "Delete…" confirmation | +| **Primitive(s)** | plain-form (message + gated Delete/Cancel) | +| **Canonical reference** | OptionsDialog (closest kept canonical for a simple message-and-buttons plain form) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/DeleteConfirmationDialogView.axaml(.cs)` + `DeleteConfirmationDialogViewModel.cs` @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +Confirms deletion of an object (e.g. a lexical relation): shows the affected item and asks the user to +continue, with a gated "Delete" button. Opens from delete commands such as the lexical-reference slice. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![confirm-delete-object legacy](./images/confirm-delete-object-before.png) | ![confirm-delete-object avalonia](./images/confirm-delete-object-after.png) | +## Behaviour to preserve (parity checklist) +- [ ] Top message ("You are deleting the following item:"). +- [ ] Summary of the affected object, shown in bold. +- [ ] Optional note line (orphan / relation wording), hidden when empty. +- [ ] Bottom question ("Do you want to continue…?"), hidden when `CanDelete` is false. +- [ ] Warning icon. +- [ ] Affirmative button is labelled "Delete" (not "OK") and is gated on `CanDelete`. +- [ ] When the object cannot be deleted, the dialog becomes an informational "cannot delete" message (Delete disabled, bottom question hidden). + +## Migration gotchas +- Stub header: "Phase-1 §19g". +- PARITY (stub): "the affirmative button is "Delete" (not "OK") and is gated on CanDelete; when the object + cannot be deleted, the bottom question … is hidden and Delete is disabled — the dialog becomes an + informational "cannot delete" message." Preserve both the gating and the informational-mode collapse. +- Undo-fencing: deletion runs inside the caller's undo action (e.g. `ksUndoDeleteRelation`); the dialog only + confirms — the launcher's `Confirm(...)` callback performs the model change. + +## Wiring +- Legacy call site(s): the Legacy delete paths in `Src/LexText/LexTextControls/LexReferenceMultiSlice.cs` + construct the WinForms `ConfirmDeleteObjectDlg` (`Src/FdoUi/Dialogs/ConfirmDeleteObjectDlg.cs`). +- The Avalonia path branched on `UIMode=New` here before back-out: + `Src/LexText/LexTextControls/LexReferenceMultiSlice.cs:908` and `:987` — `LcmDeleteObjectLauncher.Confirm(...)` + (one for `TargetsRS.Remove`, one for `DeleteObj`). Launcher: `LcmDeleteObjectLauncher` + (`Src/LexText/LexTextControls/LcmDeleteObjectLauncher.cs`). +- Re-wiring target: `LexReferenceMultiSlice` delete paths re-enter the Avalonia dialog behind `UIMode=New`; + Legacy keeps `ConfirmDeleteObjectDlg`. diff --git a/Docs/migration/dialogs/add-allomorph-dlg.md b/Docs/migration/dialogs/add-allomorph-dlg.md new file mode 100644 index 0000000000..036b41be3a --- /dev/null +++ b/Docs/migration/dialogs/add-allomorph-dlg.md @@ -0,0 +1,28 @@ +# Add Allomorph (`AddAllomorphDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.AddAllomorphDlg` (`Src/LexText/LexTextControls/AddAllomorphDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | search+list | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![add-allomorph legacy](./images/add-allomorph-before.png) | ![add-allomorph avalonia](./images/add-allomorph-after.png) | +## What it is +Search for an existing entry and add the typed form to it as an allomorph. + +## Notes / gotchas +- Subclass of EntryGoDlg (base skipped; subclass is a distinct shipping dialog). State=coexist: Avalonia path is LcmAddAllomorphDialogLauncher under UIMode=New. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/add-converter.md b/Docs/migration/dialogs/add-converter.md new file mode 100644 index 0000000000..852684d75d --- /dev/null +++ b/Docs/migration/dialogs/add-converter.md @@ -0,0 +1,32 @@ +# Add / Configure Encoding Converter (`AddCnvtrDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.AddCnvtrDlg` (`Src/FwCoreDlgs/AddCnvtrDlg.cs`) | +| **Area** | App-wide (encoding converters) | +| **Type** | dialog | +| **Primitive** | TABS | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | tabs→OptionsDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![add-cnvtr legacy](./images/add-cnvtr-before.png) | ![add-cnvtr avalonia](./images/add-cnvtr-after.png) | + +Tabs (legacy): + +![advanced](./images/add-cnvtr-tab-advanced.png) ![properties](./images/add-cnvtr-tab-properties.png) ![test](./images/add-cnvtr-tab-test.png) +## What it is +The dialog for adding/configuring encoding converters. + +## Notes / gotchas +- Hosts owned sub-controls `CnvtrPropertiesCtrl` (converter properties editor), `AdvancedEncProps` (advanced encoding properties), and `ConverterTester` (a live test pane that is Views-coupled — its `SampleView : SimpleRootSite` renders converted text). Fold these controls into this dialog's migration. +- Views-coupled via the embedded converter test pane. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/add-custom-field.md b/Docs/migration/dialogs/add-custom-field.md new file mode 100644 index 0000000000..754b9f2360 --- /dev/null +++ b/Docs/migration/dialogs/add-custom-field.md @@ -0,0 +1,28 @@ +# Add Custom Field (`AddCustomFieldDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.AddCustomFieldDlg` (`Src/xWorks/AddCustomFieldDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | TABLE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (list/table of fields with add/edit/delete) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![add-custom-field legacy](./images/add-custom-field-before.png) | ![add-custom-field avalonia](./images/add-custom-field-after.png) | +## What it is +Lets the user add and manage custom fields on a class; shows existing custom fields in a `ListView` and edits the selected field's name/type/writing-system properties. + +## Notes / gotchas +- Built entirely in code (no `.Designer.cs`); `ListView` (`m_fieldsListView`) drives a master-detail layout with the field editor controls below. +- Field type/WS controls are interdependent (selecting a type enables/disables WS pickers) — preserve the enable/disable logic. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/add-list.md b/Docs/migration/dialogs/add-list.md new file mode 100644 index 0000000000..60bd5bf6e0 --- /dev/null +++ b/Docs/migration/dialogs/add-list.md @@ -0,0 +1,29 @@ +# Add Custom List (`AddListDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.AddListDlg` (`Src/xWorks/CustomListDlg.cs`) | +| **Area** | Lists | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![custom-list legacy](./images/custom-list-before.png) | ![custom-list avalonia](./images/custom-list-after.png) | +## What it is +Creates a new TopicListEditor-style custom list: collects multilingual name/description plus hierarchy, sort-by, duplicate and display-by options, then creates the list. + +## Notes / gotchas +- Concrete subclass of the shared base `CustomListDlg` (`Src/xWorks/CustomListDlg.cs`); the base hosts the actual controls (multistring name/description via `LabeledMultiStringControl`, WS combo, display-by combo, hierarchy/sort/duplicate checkboxes). Migrate the base once and parameterise for Add vs Configure. +- Newed at `Src/xWorks/XWorksViewBase.cs:812`. +- Uses `LabeledMultiStringControl` (owned multilingual control) — needs an owned-control equivalent. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/add-new-user.md b/Docs/migration/dialogs/add-new-user.md new file mode 100644 index 0000000000..3056d2a496 --- /dev/null +++ b/Docs/migration/dialogs/add-new-user.md @@ -0,0 +1,27 @@ +# Add New User (`AddNewUserDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.AddNewUserDlg` (`Src/FwCoreDlgs/AddNewUserDlg.cs`) | +| **Area** | App-wide (user properties) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![add-new-user legacy](./images/add-new-user-before.png) | ![add-new-user avalonia](./images/add-new-user-after.png) | +## What it is +Used by the User Properties dialog when the Add button is clicked, to add a new user. + +## Notes / gotchas +- Child of `FwUserProperties`, which is itself largely obsolete (see notes there). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/add-new-vern-lang-warning.md b/Docs/migration/dialogs/add-new-vern-lang-warning.md new file mode 100644 index 0000000000..5ccce36b1a --- /dev/null +++ b/Docs/migration/dialogs/add-new-vern-lang-warning.md @@ -0,0 +1,27 @@ +# Add New Vernacular Language Warning (`AddNewVernLangWarningDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.AddNewVernLangWarningDlg` (`Src/FwCoreDlgs/AddNewVernLangWarningDlg.cs`) | +| **Area** | App-wide (writing-system management) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![add-new-vern-lang-warning legacy](./images/add-new-vern-lang-warning-before.png) | ![add-new-vern-lang-warning avalonia](./images/add-new-vern-lang-warning-after.png) | +## What it is +A warning to dissuade users from adding multiple vernacular languages when they usually want multiple writing systems of the same vernacular language. + +## Notes / gotchas +- Confirmation/warning dialog; modal. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/advanced-mt-dialog.md b/Docs/migration/dialogs/advanced-mt-dialog.md new file mode 100644 index 0000000000..238b9b43c5 --- /dev/null +++ b/Docs/migration/dialogs/advanced-mt-dialog.md @@ -0,0 +1,21 @@ +# Advanced Mark Pre/Postposed (`AdvancedMTDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Discourse.AdvancedMTDialog` (`Src/LexText/Discourse/AdvancedMTDialog.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Marks SOME of the text in a constituent-chart cell as pre/postposed from the same or different rows of the chart; the logic lives in a separate class. + +## Notes / gotchas +- Discourse constituent-chart feature; owned move-target selection control. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/anthro-field-mapping-dlg.md b/Docs/migration/dialogs/anthro-field-mapping-dlg.md new file mode 100644 index 0000000000..83e710b04d --- /dev/null +++ b/Docs/migration/dialogs/anthro-field-mapping-dlg.md @@ -0,0 +1,28 @@ +# Anthro Field Mapping (`AnthroFieldMappingDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.DataNotebook.AnthroFieldMappingDlg` (`Src/LexText/LexTextControls/DataNotebook/AnthroFieldMappingDlg.cs`) | +| **Area** | Notebook | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![anthro-field-mapping legacy](./images/anthro-field-mapping-before.png) | ![anthro-field-mapping avalonia](./images/anthro-field-mapping-after.png) | +## What it is +Maps an imported field to an anthropology-category destination during Data Notebook import. + +## Notes / gotchas +- Part of the Data Notebook SFM-import wizard mapping family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/apply-style.md b/Docs/migration/dialogs/apply-style.md new file mode 100644 index 0000000000..56342fb8a8 --- /dev/null +++ b/Docs/migration/dialogs/apply-style.md @@ -0,0 +1,28 @@ +# Apply Style (`FwApplyStyleDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwApplyStyleDlg` (`Src/FwCoreDlgs/FwApplyStyleDlg.cs`) | +| **Area** | App-wide (styles) | +| **Type** | dialog | +| **Primitive** | plain-form (style list) | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | search+list→EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Lets the user apply an existing paragraph/character style to the current selection (a lighter sibling of the full Styles dialog). + +## What it looks like (before / after) +**Live-capture / on-pickup.** The Format → Apply Style command is **disabled unless there is a live text +selection** to apply a style to (verified live: greyed out in the lexicon tool). The live-UIA menu-capture +harness (`Capture-MenuDialogs.ps1`) skips disabled leaves, so this dialog is captured when its ticket is +worked, from a text-editing context where the command is enabled. Its sibling Styles dialog (FwStylesDlg) +is captured — see [styles.md](./styles.md). + +## Notes / gotchas +- Views-coupled (references `IVwRootSite`/selection to apply styles to the active view). +- Shares the style-list helper infrastructure with the full Styles dialog (FwStylesDlg). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/archive-with-ramp.md b/Docs/migration/dialogs/archive-with-ramp.md new file mode 100644 index 0000000000..aa43a81e7f --- /dev/null +++ b/Docs/migration/dialogs/archive-with-ramp.md @@ -0,0 +1,20 @@ +# Archive With RAMP (`ArchiveWithRamp`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.ArchiveWithRamp` (`Src/FwCoreDlgs/ArchiveWithRamp.cs`) | +| **Area** | App-wide (archiving / Send-Receive) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it is +Dialog supporting archiving the project with RAMP (the SIL archiving tool). + +## Notes / gotchas +- Integrates with an external RAMP tool; modal. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/backup-project.md b/Docs/migration/dialogs/backup-project.md new file mode 100644 index 0000000000..ee60755227 --- /dev/null +++ b/Docs/migration/dialogs/backup-project.md @@ -0,0 +1,28 @@ +# Backup Project (`BackupProjectDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.BackupRestore.BackupProjectDlg` (`Src/FwCoreDlgs/BackupRestore/BackupProjectDlg.cs`) | +| **Area** | App-wide (backup / restore) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![backup-project legacy](./images/backup-project-before.png) | ![backup-project avalonia](./images/backup-project-after.png) | +## What it is +The Backup Project dialog — configures and runs a backup of the current project (implements `IBackupProjectView`). + +## Notes / gotchas +- View/presenter split (`IBackupProjectView` / `BackupProjectPresenter`). +- May launch `ChangeDefaultBackupDir` for the backup-directory option. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/basic-find.md b/Docs/migration/dialogs/basic-find.md new file mode 100644 index 0000000000..9d74318741 --- /dev/null +++ b/Docs/migration/dialogs/basic-find.md @@ -0,0 +1,27 @@ +# Basic Find (`BasicFindDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.BasicFindDialog` (`Src/FwCoreDlgs/BasicFindDialog.cs`) | +| **Area** | App-wide | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![basic-find legacy](./images/basic-find-before.png) | ![basic-find avalonia](./images/basic-find-after.png) | +## What it is +A no-frills find dialog; all work is done by the caller via events fired by the dialog (implements `IBasicFindView`). + +## Notes / gotchas +- View/presenter split (`IBasicFindView`); caller owns the search logic. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/cant-restore-linked-files-to-original-location.md b/Docs/migration/dialogs/cant-restore-linked-files-to-original-location.md new file mode 100644 index 0000000000..5a64d4475a --- /dev/null +++ b/Docs/migration/dialogs/cant-restore-linked-files-to-original-location.md @@ -0,0 +1,20 @@ +# Cannot Restore Linked Files to Original Location (`CantRestoreLinkedFilesToOriginalLocation`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FdoUi.Dialogs.CantRestoreLinkedFilesToOriginalLocation` (`Src/FdoUi/Dialogs/CantRestoreLinkedFilesToOriginalLocation.cs`) | +| **Area** | App-wide | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it is +Restore-linked-files prompt shown when linked files cannot be restored to their original location; offers the user an alternative location choice. + +## Notes / gotchas +- Part of the linked-files restore prompt family (with `FilesToRestoreAreOlder` and `RestoreLinkedFilesToProjectsFolder`) — migrate together; likely radio-option + OK/Cancel. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/change-default-backup-dir.md b/Docs/migration/dialogs/change-default-backup-dir.md new file mode 100644 index 0000000000..4e96a65c7e --- /dev/null +++ b/Docs/migration/dialogs/change-default-backup-dir.md @@ -0,0 +1,20 @@ +# Change Default Backup Directory (`ChangeDefaultBackupDir`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.BackupRestore.ChangeDefaultBackupDir` (`Src/FwCoreDlgs/BackupRestore/ChangeDefaultBackupDir.cs`) | +| **Area** | App-wide (backup / restore) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it is +TBD — lets the user change the default directory where project backups are stored (infer from class name; fill on pickup). + +## Notes / gotchas +- Small folder-selection dialog launched from `BackupProjectDlg`. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/choose-lang-project.md b/Docs/migration/dialogs/choose-lang-project.md new file mode 100644 index 0000000000..4257aa60e5 --- /dev/null +++ b/Docs/migration/dialogs/choose-lang-project.md @@ -0,0 +1,27 @@ +# Choose Language Project (`ChooseLangProjectDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.ChooseLangProjectDialog` (`Src/FwCoreDlgs/ChooseLangProjectDialog.cs`) | +| **Area** | App-wide (project open / Send-Receive) | +| **Type** | dialog | +| **Primitive** | plain-form (search + list) | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | search+list→EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![choose-lang-project legacy](./images/choose-lang-project-before.png) | ![choose-lang-project avalonia](./images/choose-lang-project-after.png) | +## What it is +Lets the user pick a language project to open — local projects, networked projects, or projects obtained via Send/Receive. Presents a ListBox of available projects. + +## Notes / gotchas +- ListBox-of-projects picker (`m_lstLanguageProjects`); no tabs. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/choose-text-writing-system-dlg.md b/Docs/migration/dialogs/choose-text-writing-system-dlg.md new file mode 100644 index 0000000000..89d0871fd6 --- /dev/null +++ b/Docs/migration/dialogs/choose-text-writing-system-dlg.md @@ -0,0 +1,21 @@ +# Choose Text Writing System (`ChooseTextWritingSystemDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.ChooseTextWritingSystemDlg` (`Src/LexText/Interlinear/ChooseTextWritingSystemDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Lets the user pick the writing system for a text. + +## Notes / gotchas +- Small picker form. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/chooser.md b/Docs/migration/dialogs/chooser.md new file mode 100644 index 0000000000..a60261f8cc --- /dev/null +++ b/Docs/migration/dialogs/chooser.md @@ -0,0 +1,28 @@ +# Chooser (`FwChooserDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwChooserDlg` (`Src/FwCoreDlgs/FwChooserDlg.cs`) | +| **Area** | App-wide (possibility/list chooser) | +| **Type** | dialog | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | tree/multi-select/picker→ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![fw-chooser legacy](./images/fw-chooser-before.png) | ![fw-chooser avalonia](./images/fw-chooser-after.png) | +## What it is +A general-purpose chooser for `ICmPossibility` items (implements `ISettings`, `ICmPossibilitySupplier`). + +## Notes / gotchas +- Hosts the owned `ChooserTreeView` (a `TriStateTreeView`). Fold into this dialog's migration. +- Tri-state tree selection of possibility-list items. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/combine-import-dlg.md b/Docs/migration/dialogs/combine-import-dlg.md new file mode 100644 index 0000000000..ad0cb5474a --- /dev/null +++ b/Docs/migration/dialogs/combine-import-dlg.md @@ -0,0 +1,28 @@ +# Combine (FLEx) Import (`CombineImportDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.CombineImportDlg` (`Src/LexText/LexTextControls/CombineImportDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![combine-import legacy](./images/combine-import-before.png) | ![combine-import avalonia](./images/combine-import-after.png) | +## What it is +Imports a project sent from The Combine (web word-collection tool). + +## Notes / gotchas +- Implements IFwExtension. Uses Ionic.Zip + FileDialog wrappers. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/complex-conc-morph-dlg.md b/Docs/migration/dialogs/complex-conc-morph-dlg.md new file mode 100644 index 0000000000..f28d64aa9a --- /dev/null +++ b/Docs/migration/dialogs/complex-conc-morph-dlg.md @@ -0,0 +1,21 @@ +# Complex Concordance Morpheme (`ComplexConcMorphDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.ComplexConcMorphDlg` (`Src/LexText/Interlinear/ComplexConcMorphDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Edits a morpheme node in the Complex Concordance query builder. + +## Notes / gotchas +- Owned FW writing-system-aware edit controls (SIL.FieldWorks.Common.Widgets); part of the complex-concordance query UI. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/complex-conc-tag-dlg.md b/Docs/migration/dialogs/complex-conc-tag-dlg.md new file mode 100644 index 0000000000..5aa7d5a95b --- /dev/null +++ b/Docs/migration/dialogs/complex-conc-tag-dlg.md @@ -0,0 +1,21 @@ +# Complex Concordance Tag (`ComplexConcTagDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.ComplexConcTagDlg` (`Src/LexText/Interlinear/ComplexConcTagDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Edits a tag node in the Complex Concordance query builder. + +## Notes / gotchas +- Part of the complex-concordance query UI. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/complex-conc-word-dlg.md b/Docs/migration/dialogs/complex-conc-word-dlg.md new file mode 100644 index 0000000000..dfa694fb84 --- /dev/null +++ b/Docs/migration/dialogs/complex-conc-word-dlg.md @@ -0,0 +1,21 @@ +# Complex Concordance Word (`ComplexConcWordDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.ComplexConcWordDlg` (`Src/LexText/Interlinear/ComplexConcWordDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Edits a word node in the Complex Concordance query builder. + +## Notes / gotchas +- Owned FW widgets; part of the complex-concordance query UI. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/concordance-dlg.md b/Docs/migration/dialogs/concordance-dlg.md new file mode 100644 index 0000000000..d2ec9f3fa8 --- /dev/null +++ b/Docs/migration/dialogs/concordance-dlg.md @@ -0,0 +1,21 @@ +# Concordance (`ConcordanceDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.MorphologyEditor.ConcordanceDlg` (`Src/LexText/Morphology/ConcordanceDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Shows text occurrences (concordance) of a selected wordform, excluding instances assigned to a child analysis/gloss. + +## Notes / gotchas +- Implements IFwGuiControl. Hosts a concordance browse view (Views-coupled). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/configure-interlin-dialog.md b/Docs/migration/dialogs/configure-interlin-dialog.md new file mode 100644 index 0000000000..bb8c9dc5d1 --- /dev/null +++ b/Docs/migration/dialogs/configure-interlin-dialog.md @@ -0,0 +1,28 @@ +# Configure Interlinear (`ConfigureInterlinDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.ConfigureInterlinDialog` (`Src/LexText/Interlinear/ConfigureInterlinDialog.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![configure-interlin legacy](./images/configure-interlin-before.png) | ![configure-interlin avalonia](./images/configure-interlin-after.png) | +## What it is +Configures which interlinear lines/writing systems are shown and their order. + +## Notes / gotchas +- Owned line-ordering control (multi-line picker). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/configure-list.md b/Docs/migration/dialogs/configure-list.md new file mode 100644 index 0000000000..399d5615f1 --- /dev/null +++ b/Docs/migration/dialogs/configure-list.md @@ -0,0 +1,22 @@ +# Configure Custom List (`ConfigureListDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.ConfigureListDlg` (`Src/xWorks/CustomListDlg.cs`) | +| **Area** | Lists | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it is +Edits the properties (name, description, hierarchy/sort/duplicate/display-by options) of an existing custom list and reports whether changes were made. + +## Notes / gotchas +- Concrete subclass of the shared base `CustomListDlg` (`Src/xWorks/CustomListDlg.cs`); shares all controls with `AddListDlg` — migrate the base once. +- Newed at `Src/xWorks/XWorksViewBase.cs:776`. Tracks a "changed existing list" flag for caller refresh. +- Uses `LabeledMultiStringControl` (owned multilingual control). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/conflicting-save.md b/Docs/migration/dialogs/conflicting-save.md new file mode 100644 index 0000000000..ff52859812 --- /dev/null +++ b/Docs/migration/dialogs/conflicting-save.md @@ -0,0 +1,27 @@ +# Conflicting Save (`ConflictingSaveDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FdoUi.Dialogs.ConflictingSaveDlg` (`Src/FdoUi/Dialogs/ConflictingSaveDlg.cs`) | +| **Area** | App-wide | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![conflicting-save legacy](./images/conflicting-save-before.png) | ![conflicting-save avalonia](./images/conflicting-save-after.png) | +## What it is +Message-box-like dialog warning of a conflicting save, offering OK and "Refresh Now"; clicking Refresh Now yields `DialogResult.Yes`. + +## Notes / gotchas +- Trivial two-button message form; the non-standard `DialogResult.Yes` for "Refresh Now" must be preserved for callers. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/create-allomorph-type-mismatch-dlg.md b/Docs/migration/dialogs/create-allomorph-type-mismatch-dlg.md new file mode 100644 index 0000000000..9579c99aca --- /dev/null +++ b/Docs/migration/dialogs/create-allomorph-type-mismatch-dlg.md @@ -0,0 +1,21 @@ +# Create Allomorph Type Mismatch (`CreateAllomorphTypeMismatchDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.CreateAllomorphTypeMismatchDlg` (`Src/LexText/Interlinear/CreateAllomorphTypeMismatchDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Warns/prompts when a created allomorph's type does not match the morpheme-break markers. + +## Notes / gotchas +- MorphType-mismatch prompt; data-loss-adjacent confirmation - preserve exact prompt semantics. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/delete-project.md b/Docs/migration/dialogs/delete-project.md new file mode 100644 index 0000000000..297d0d1d1a --- /dev/null +++ b/Docs/migration/dialogs/delete-project.md @@ -0,0 +1,27 @@ +# Delete Project (`FwDeleteProjectDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwDeleteProjectDlg` (`Src/FwCoreDlgs/FwDeleteProjectDlg.cs`) | +| **Area** | App-wide (project management) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![fw-delete-project legacy](./images/fw-delete-project-before.png) | ![fw-delete-project avalonia](./images/fw-delete-project-after.png) | +## What it is +TBD — confirm/delete a FieldWorks project (infer from class name; fill on pickup). + +## Notes / gotchas +- Confirmation-style project deletion dialog; modal. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/delete-writing-system-warning.md b/Docs/migration/dialogs/delete-writing-system-warning.md new file mode 100644 index 0000000000..f6861ed832 --- /dev/null +++ b/Docs/migration/dialogs/delete-writing-system-warning.md @@ -0,0 +1,27 @@ +# Delete Writing System Warning (`DeleteWritingSystemWarningDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.DeleteWritingSystemWarningDialog` (`Src/FwCoreDlgs/DeleteWritingSystemWarningDialog.cs`) | +| **Area** | App-wide (writing-system management) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![delete-writing-system-warning legacy](./images/delete-writing-system-warning-before.png) | ![delete-writing-system-warning avalonia](./images/delete-writing-system-warning-after.png) | +## What it is +A warning dialog used in place of a plain MessageBox because custom text is required for the "Yes" button when deleting a writing system. + +## Notes / gotchas +- Confirmation dialog; modal. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/dictionary-config-mgr.md b/Docs/migration/dialogs/dictionary-config-mgr.md new file mode 100644 index 0000000000..86304750b8 --- /dev/null +++ b/Docs/migration/dialogs/dictionary-config-mgr.md @@ -0,0 +1,26 @@ +# Dictionary Config Manager (legacy XML) (`DictionaryConfigMgrDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.DictionaryConfigMgrDlg` (`Src/xWorks/DictionaryConfigMgrDlg.cs`) | +| **Area** | Dictionary-config | +| **Type** | dialog | +| **Primitive** | TABLE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (list of named configurations with rename/copy/delete) | +| **JIRA** | LT-XXXXX | + +## What it is +MVP "view" for managing stored dictionary configurations (rename/copy/delete/select). Displays the configurations in a `ListView` and reports user actions to `DictionaryConfigManager`. Implements `IDictConfigViewer`. + +## What it looks like (before / after) +**Live-capture / on-pickup.** Not captured: the dialog NRE'd in the headless harness +(`LoadDataFromInventory` needs a specific inventory node) and has no standalone menu entry (it opens only +from the legacy XML configure path). Capture live from that flow on pickup. + +## Notes / gotchas +- This is the OLDER manager, tied to the legacy XML configure path: newed only from `Src/xWorks/XmlDocConfigureDlg.cs:4368`. The newer equivalent is DictionaryConfigurationManagerDlg (`dictionary-configuration-manager.md`). Confirm on pickup which one survives; this one may retire with XmlDocConfigureDlg. +- `ListView` items are a custom `VisibleListItem : ListViewItem`. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/dictionary-configuration-import.md b/Docs/migration/dialogs/dictionary-configuration-import.md new file mode 100644 index 0000000000..44739cbbfe --- /dev/null +++ b/Docs/migration/dialogs/dictionary-configuration-import.md @@ -0,0 +1,28 @@ +# Dictionary Configuration Import (`DictionaryConfigurationImportDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.DictionaryConfigurationImportDlg` (`Src/xWorks/DictionaryConfigurationImportDlg.cs`) | +| **Area** | Dictionary-config | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![dictionary-configuration-import legacy](./images/dictionary-configuration-import-before.png) | ![dictionary-configuration-import avalonia](./images/dictionary-configuration-import-after.png) | +## What it is +Imports a dictionary configuration from a file: file-path text box + Browse, overwrite/don't-overwrite radio options, an explanation pane, and Import/Cancel/Help. + +## Notes / gotchas +- Plain-form (TextBox + Browse button + RadioButtons + explanation TextBox); logic in `DictionaryConfigurationImportController`. +- Overwrite vs new-name radio options drive the import behaviour — preserve the gating/explanation text. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/dictionary-configuration-manager.md b/Docs/migration/dialogs/dictionary-configuration-manager.md new file mode 100644 index 0000000000..da6277c519 --- /dev/null +++ b/Docs/migration/dialogs/dictionary-configuration-manager.md @@ -0,0 +1,31 @@ +# Dictionary Configuration Manager (`DictionaryConfigurationManagerDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.DictionaryConfigurationManagerDlg` (`Src/xWorks/DictionaryConfigurationManagerDlg.cs`) | +| **Area** | Dictionary-config | +| **Type** | dialog | +| **Primitive** | TABLE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (list of configurations with rename/copy/delete/import/export) | +| **JIRA** | LT-XXXXX | + +## What it is +Manages the set of dictionary configurations (rename, copy, delete, import, export) shown in a `ListView` (`configurationsListView`). The "Manage Configurations" sub-dialog launched from the Configure Dictionary dialog. + +## What it looks like (before / after) +Legacy "before" captured live (Capture-MenuDialogs.ps1, option 2b) by opening Configure Dictionary +(Tools→Configure) then clicking the **Manage Layouts…** button — the in-modal `postClicks` path. Avalonia +"after" comes from the surface's FwAvaloniaDialogs(Tests) visual test; attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![dictionary-configuration-manager legacy](./images/dictionary-configuration-manager-before.png) | ![dictionary-configuration-manager avalonia](./images/dictionary-configuration-manager-after.png) | + +## Notes / gotchas +- The CURRENT manager (newer than `DictionaryConfigMgrDlg`); newed from `Src/xWorks/DictionaryConfigurationController.cs:367`. Logic lives in `DictionaryConfigurationManagerController`. +- Manipulates per-item fonts (bold = current) and focus handlers on the `ListView`. +- Import/export flows reach `DictionaryConfigurationImportDlg` and controller export code. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/dictionary-configuration-node-rename.md b/Docs/migration/dialogs/dictionary-configuration-node-rename.md new file mode 100644 index 0000000000..1e088af333 --- /dev/null +++ b/Docs/migration/dialogs/dictionary-configuration-node-rename.md @@ -0,0 +1,27 @@ +# Dictionary Configuration Node Rename (`DictionaryConfigurationNodeRenameDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.DictionaryConfigurationNodeRenameDlg` (`Src/xWorks/DictionaryConfigurationNodeRenameDlg.cs`) | +| **Area** | Dictionary-config | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![dictionary-configuration-node-rename legacy](./images/dictionary-configuration-node-rename-before.png) | ![dictionary-configuration-node-rename avalonia](./images/dictionary-configuration-node-rename-after.png) | +## What it is +Small single-field dialog to rename a node (duplicate label) in the dictionary configuration tree; inserts the node value into the description text. + +## Notes / gotchas +- Trivial plain-form (label + textbox + OK/Cancel) launched from the configuration tree context. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/dictionary-configuration.md b/Docs/migration/dialogs/dictionary-configuration.md new file mode 100644 index 0000000000..00b26f1975 --- /dev/null +++ b/Docs/migration/dialogs/dictionary-configuration.md @@ -0,0 +1,30 @@ +# Dictionary Configuration (`DictionaryConfigurationDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.DictionaryConfigurationDlg` (`Src/xWorks/DictionaryConfigurationDlg.cs`) | +| **Area** | Dictionary-config | +| **Type** | dialog | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (tree + detail), but this is a large bespoke screen — see gotchas | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![dictionary-configuration legacy](./images/dictionary-configuration-before.png) | ![dictionary-configuration avalonia](./images/dictionary-configuration-after.png) | +## What it is +The main Dictionary Configuration editor: a configuration tree (`DictionaryConfigurationTreeControl`) on the left, a per-node details pane (`DictionaryDetailsView` family) on the right, and a live HTML preview, with OK/Apply/Cancel and a "Manage Views" entry point. Implements `IDictionaryConfigurationView` (MVC view, driven by `DictionaryConfigurationController`). + +## Notes / gotchas +- LARGE, COMPLEX screen — not a simple plain-form. Composes three regions: tree control, swappable details panels (`DetailsView`, `ListOptionsView`, `PictureOptionsView`, `GroupingOptionsView`, `*OverPanel`), and a preview. +- HARD dependency on **GeckoWebBrowser**: the preview (`m_preview.NativeBrowser`) is asserted to be a `GeckoWebBrowser`; node highlighting walks `GeckoElement`s in the rendered HTML. Migration must reproduce or replace the HTML preview + element-highlight feature. +- Details panels are owned WinForms UserControls hosted in this dialog (covered by directory scope, not separately stubbed). +- MVC: behaviour lives in `DictionaryConfigurationController`; the dialog is mostly the view surface. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/discourse-export-dialog.md b/Docs/migration/dialogs/discourse-export-dialog.md new file mode 100644 index 0000000000..f2a89b3b52 --- /dev/null +++ b/Docs/migration/dialogs/discourse-export-dialog.md @@ -0,0 +1,22 @@ +# Discourse Export (`DiscourseExportDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Discourse.DiscourseExportDialog` (`Src/LexText/Discourse/DiscourseExportDialog.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Exports the discourse constituent chart; subclass of ExportDialog. + +## Notes / gotchas +- Subclasses shared ExportDialog (in FwControls, outside this tree) - migrate alongside the InterlinearExportDialog/ExportDialog family. +- Source comment notes considerable overlap with InterlinearExportDialog; common code could move down to ExportDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/edit-morph-breaks-dlg.md b/Docs/migration/dialogs/edit-morph-breaks-dlg.md new file mode 100644 index 0000000000..3cfdd356f5 --- /dev/null +++ b/Docs/migration/dialogs/edit-morph-breaks-dlg.md @@ -0,0 +1,21 @@ +# Edit Morph Breaks (`EditMorphBreaksDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.EditMorphBreaksDlg` (`Src/LexText/Interlinear/EditMorphBreaksDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Large edit box for editing the morph breaks in a word, with helper info to assist marking morpheme types. + +## Notes / gotchas +- Bigger edit box than the inline combobox; displays morpheme-type guidance. Tied to interlinear morph-break editing (see split-out Analyses Sandbox editor for the inline path). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/entry-go.md b/Docs/migration/dialogs/entry-go.md new file mode 100644 index 0000000000..3a813cc149 --- /dev/null +++ b/Docs/migration/dialogs/entry-go.md @@ -0,0 +1,31 @@ +# Go To Entry (`EntryGoDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.EntryGoDlg` (`Src/LexText/LexTextControls/EntryGoDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog (search + matching-entries list) | +| **Primitive** | search + list selector | +| **State** | coexist — the Avalonia **EntryGoDialog** is a KEPT canonical screen (see [README](../README.md)) | +| **JIRA** | LT-XXXXX (canonical reference — not a deferred port) | + +Legacy "before" baseline for the kept-canonical Avalonia `EntryGoDialog`. Harness captures the dialog +CHROME (ctor-only); the live matching-entries search-browse populates only in the running app. + +## Notes / gotchas +- Base of the GoDlg family (the abstract BaseGoDlg): the matching-entries browser is a live XMLView/SearchEngine. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![entry-go legacy](./images/entry-go-before.png) | ![entry-go avalonia](./images/entry-go-after.png) | +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![entry-go legacy](./images/entry-go-before.png) | ![entry-go avalonia](./images/entry-go-after.png) | diff --git a/Docs/migration/dialogs/export-dialog.md b/Docs/migration/dialogs/export-dialog.md new file mode 100644 index 0000000000..4c1686b8ff --- /dev/null +++ b/Docs/migration/dialogs/export-dialog.md @@ -0,0 +1,30 @@ +# Export (`ExportDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.ExportDialog` (`Src/xWorks/ExportDialog.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | TABLE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (list of export formats + Export action) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![export-dialog legacy](./images/export-dialog-before.png) | ![export-dialog avalonia](./images/export-dialog-after.png) | +## What it is +XML-configurable export chooser: lists available export formats/targets in a `ListView` (`m_exportList`, columns data/format/extension/filter/description), the user picks one and runs the export (default is the main lexicon export). Subclassed for other areas (see NotebookExportDialog). + +## Notes / gotchas +- Base class is instantiated directly (main lexicon export) AND subclassed — migrate as a parameterised/overridable surface, not a one-off. +- Driven by an XML configuration file (`ConfigurationFilePath`); export options are data-driven, not hardcoded — preserve the config-file contract. +- `ListView` master list with column sorting; uses `Common.RootSites` (review for any Views coupling on the export path). +- Standard FXT export pipeline by default; subclasses override `ConfigureItem`/the export process. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/export-semantic-domains.md b/Docs/migration/dialogs/export-semantic-domains.md new file mode 100644 index 0000000000..2ba8e50da4 --- /dev/null +++ b/Docs/migration/dialogs/export-semantic-domains.md @@ -0,0 +1,27 @@ +# Export Semantic Domains (`ExportSemanticDomainsDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.ExportSemanticDomainsDlg` (`Src/xWorks/ExportSemanticDomainsDlg.cs`) | +| **Area** | Lists | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![export-semantic-domains legacy](./images/export-semantic-domains-before.png) | ![export-semantic-domains avalonia](./images/export-semantic-domains-after.png) | +## What it is +Options dialog for exporting the semantic-domains list: choose the writing system and whether to show missing translations with English shown in red. + +## Notes / gotchas +- Small options form; the "English in red" checkbox enablement depends on the chosen WS (`m_EnglishInRedCheckBox.Enabled`) — preserve the gating. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/export-translated-lists.md b/Docs/migration/dialogs/export-translated-lists.md new file mode 100644 index 0000000000..bff3c0bce3 --- /dev/null +++ b/Docs/migration/dialogs/export-translated-lists.md @@ -0,0 +1,27 @@ +# Export Translated Lists (`ExportTranslatedListsDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.ExportTranslatedListsDlg` (`Src/xWorks/ExportTranslatedListsDlg.cs`) | +| **Area** | Lists | +| **Type** | dialog | +| **Primitive** | MULTI-SELECTOR | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (checked multi-select of lists + writing systems) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![export-translated-lists legacy](./images/export-translated-lists-before.png) | ![export-translated-lists avalonia](./images/export-translated-lists-after.png) | +## What it is +Lets the user select specific lists to export and the specific writing systems to include, via two checkbox `ListView`s (`m_lvLists`, `m_lvWritingSystems`). + +## Notes / gotchas +- Two parallel checkbox lists (lists + writing systems) using `ListView.CheckedItems`; column widths are computed from list width at load — preserve checked-item collection semantics. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/files-to-restore-are-older.md b/Docs/migration/dialogs/files-to-restore-are-older.md new file mode 100644 index 0000000000..3fdadd035c --- /dev/null +++ b/Docs/migration/dialogs/files-to-restore-are-older.md @@ -0,0 +1,20 @@ +# Files to Restore Are Older (`FilesToRestoreAreOlder`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FdoUi.Dialogs.FilesToRestoreAreOlder` (`Src/FdoUi/Dialogs/FilesToRestoreAreOlder.cs`) | +| **Area** | App-wide | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it is +Restore-linked-files prompt shown when the files being restored are older than existing files; asks the user how to proceed (overwrite / keep). + +## Notes / gotchas +- Part of the linked-files restore prompt family (with `CantRestoreLinkedFilesToOriginalLocation` and `RestoreLinkedFilesToProjectsFolder`) — migrate together; likely radio-option + OK/Cancel. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/filter-texts-dialog.md b/Docs/migration/dialogs/filter-texts-dialog.md new file mode 100644 index 0000000000..a759817981 --- /dev/null +++ b/Docs/migration/dialogs/filter-texts-dialog.md @@ -0,0 +1,21 @@ +# Filter Texts (`FilterTextsDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.FilterTextsDialog` (`Src/LexText/Interlinear/FilterTextsDialog.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Bundles ordinary and Scripture texts into a checkable tree so the user can filter which texts are shown. + +## Notes / gotchas +- Subclass of abstract FilterAllTextsDialog (base excluded). Checkable text tree; Scripture vs ordinary text bundling. Source TODO suggests merging the base in. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/find-example-sentence-dlg.md b/Docs/migration/dialogs/find-example-sentence-dlg.md new file mode 100644 index 0000000000..e19fd66393 --- /dev/null +++ b/Docs/migration/dialogs/find-example-sentence-dlg.md @@ -0,0 +1,21 @@ +# Find Example Sentence (`FindExampleSentenceDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.FindExampleSentenceDlg` (`Src/LexText/Lexicon/FindExampleSentenceDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Finds corpus example sentences for a sense and lets the user attach one as an example. + +## Notes / gotchas +- Implements IFwGuiControl. Couples to interlinear/concordance views to display matches. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/flexbridge-first-send-receive-instructions-dlg.md b/Docs/migration/dialogs/flexbridge-first-send-receive-instructions-dlg.md new file mode 100644 index 0000000000..35d2aa4944 --- /dev/null +++ b/Docs/migration/dialogs/flexbridge-first-send-receive-instructions-dlg.md @@ -0,0 +1,21 @@ +# FLExBridge First Send/Receive Instructions (`FLExBridgeFirstSendReceiveInstructionsDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.FLExBridgeFirstSendReceiveInstructionsDlg` (`Src/LexText/Lexicon/FLExBridgeFirstSendReceiveInstructionsDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Shows first-time instructions before the initial FLExBridge Send/Receive. + +## Notes / gotchas +- Informational/instructional dialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/font.md b/Docs/migration/dialogs/font.md new file mode 100644 index 0000000000..b3b93526ee --- /dev/null +++ b/Docs/migration/dialogs/font.md @@ -0,0 +1,27 @@ +# Font (`FwFontDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwFontDialog` (`Src/FwCoreDlgs/FwFontDialog.cs`) | +| **Area** | App-wide (styles / formatting) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![fw-font legacy](./images/fw-font-before.png) | ![fw-font avalonia](./images/fw-font-after.png) | +## What it is +A FieldWorks font picker dialog (implements `IFontDialog`) — choose font family, size, and attributes. + +## Notes / gotchas +- Implements `IFontDialog`; hosts owned controls `FwFontAttributes` and `FontFeaturesButton`. Fold those into this dialog's migration. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/hc-max-compound-rules-dlg.md b/Docs/migration/dialogs/hc-max-compound-rules-dlg.md new file mode 100644 index 0000000000..8eb34d59b2 --- /dev/null +++ b/Docs/migration/dialogs/hc-max-compound-rules-dlg.md @@ -0,0 +1,21 @@ +# HC Max Compound Rules (`HCMaxCompoundRulesDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.HCMaxCompoundRulesDlg` (`Src/LexText/ParserUI/HCMaxCompoundRulesDlg.cs`) | +| **Area** | Grammar | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | OptionsDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Sets the maximum number of compound-rule applications for the Hermit Crab parser. + +## Notes / gotchas +- Subclass of ParserParametersBase (base excluded). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/headword-numbers.md b/Docs/migration/dialogs/headword-numbers.md new file mode 100644 index 0000000000..aec55e322f --- /dev/null +++ b/Docs/migration/dialogs/headword-numbers.md @@ -0,0 +1,28 @@ +# Headword Numbers (`HeadwordNumbersDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.HeadwordNumbersDlg` (`Src/xWorks/HeadWordNumbersDlg.cs`) | +| **Area** | Dictionary-config | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![headword-numbers legacy](./images/headword-numbers-before.png) | ![headword-numbers avalonia](./images/headword-numbers-after.png) | +## What it is +Configures the display and manipulation of homograph/headword numbers (style, before/after text, subentry options). Implements `IHeadwordNumbersView` (MVC view). + +## Notes / gotchas +- Has a "Styles..." button that opens the style chooser; raises an event passing the style `ComboBox` as sender so the controller can update it — preserve the styles round-trip. +- Behaviour lives in the controller; the dialog is the view surface. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/help-about.md b/Docs/migration/dialogs/help-about.md new file mode 100644 index 0000000000..47ceb1765a --- /dev/null +++ b/Docs/migration/dialogs/help-about.md @@ -0,0 +1,27 @@ +# Help About (`FwHelpAbout`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwHelpAbout` (`Src/FwCoreDlgs/FwHelpAbout.cs`) | +| **Area** | App-wide | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![help-about legacy](./images/help-about-before.png) | ![help-about avalonia](./images/help-about-after.png) | +## What it is +The FieldWorks Help > About dialog (previously HelpAboutDlg in AfDialog.cpp) — shows version/build/credits. + +## Notes / gotchas +- Read-only informational dialog; modal. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/images/add-allomorph-before.png b/Docs/migration/dialogs/images/add-allomorph-before.png new file mode 100644 index 0000000000..5b50fd3882 Binary files /dev/null and b/Docs/migration/dialogs/images/add-allomorph-before.png differ diff --git a/Docs/migration/dialogs/images/add-cnvtr-before.png b/Docs/migration/dialogs/images/add-cnvtr-before.png new file mode 100644 index 0000000000..6801be23dc Binary files /dev/null and b/Docs/migration/dialogs/images/add-cnvtr-before.png differ diff --git a/Docs/migration/dialogs/images/add-cnvtr-tab-advanced.png b/Docs/migration/dialogs/images/add-cnvtr-tab-advanced.png new file mode 100644 index 0000000000..53b576360c Binary files /dev/null and b/Docs/migration/dialogs/images/add-cnvtr-tab-advanced.png differ diff --git a/Docs/migration/dialogs/images/add-cnvtr-tab-properties.png b/Docs/migration/dialogs/images/add-cnvtr-tab-properties.png new file mode 100644 index 0000000000..6801be23dc Binary files /dev/null and b/Docs/migration/dialogs/images/add-cnvtr-tab-properties.png differ diff --git a/Docs/migration/dialogs/images/add-cnvtr-tab-test.png b/Docs/migration/dialogs/images/add-cnvtr-tab-test.png new file mode 100644 index 0000000000..3aecdd0327 Binary files /dev/null and b/Docs/migration/dialogs/images/add-cnvtr-tab-test.png differ diff --git a/Docs/migration/dialogs/images/add-custom-field-before.png b/Docs/migration/dialogs/images/add-custom-field-before.png new file mode 100644 index 0000000000..df2c996836 Binary files /dev/null and b/Docs/migration/dialogs/images/add-custom-field-before.png differ diff --git a/Docs/migration/dialogs/images/add-new-user-before.png b/Docs/migration/dialogs/images/add-new-user-before.png new file mode 100644 index 0000000000..eaacc47e5d Binary files /dev/null and b/Docs/migration/dialogs/images/add-new-user-before.png differ diff --git a/Docs/migration/dialogs/images/add-new-vern-lang-warning-before.png b/Docs/migration/dialogs/images/add-new-vern-lang-warning-before.png new file mode 100644 index 0000000000..ea14ad8361 Binary files /dev/null and b/Docs/migration/dialogs/images/add-new-vern-lang-warning-before.png differ diff --git a/Docs/migration/dialogs/images/anthro-field-mapping-before.png b/Docs/migration/dialogs/images/anthro-field-mapping-before.png new file mode 100644 index 0000000000..1efc36c7e7 Binary files /dev/null and b/Docs/migration/dialogs/images/anthro-field-mapping-before.png differ diff --git a/Docs/migration/dialogs/images/backup-project-before.png b/Docs/migration/dialogs/images/backup-project-before.png new file mode 100644 index 0000000000..3cf462a094 Binary files /dev/null and b/Docs/migration/dialogs/images/backup-project-before.png differ diff --git a/Docs/migration/dialogs/images/basic-find-before.png b/Docs/migration/dialogs/images/basic-find-before.png new file mode 100644 index 0000000000..57b3b63511 Binary files /dev/null and b/Docs/migration/dialogs/images/basic-find-before.png differ diff --git a/Docs/migration/dialogs/images/choose-lang-project-before.png b/Docs/migration/dialogs/images/choose-lang-project-before.png new file mode 100644 index 0000000000..81849588fb Binary files /dev/null and b/Docs/migration/dialogs/images/choose-lang-project-before.png differ diff --git a/Docs/migration/dialogs/images/combine-import-before.png b/Docs/migration/dialogs/images/combine-import-before.png new file mode 100644 index 0000000000..114d86c526 Binary files /dev/null and b/Docs/migration/dialogs/images/combine-import-before.png differ diff --git a/Docs/migration/dialogs/images/configure-interlin-before.png b/Docs/migration/dialogs/images/configure-interlin-before.png new file mode 100644 index 0000000000..054b7d6613 Binary files /dev/null and b/Docs/migration/dialogs/images/configure-interlin-before.png differ diff --git a/Docs/migration/dialogs/images/conflicting-save-before.png b/Docs/migration/dialogs/images/conflicting-save-before.png new file mode 100644 index 0000000000..213bc5cc6d Binary files /dev/null and b/Docs/migration/dialogs/images/conflicting-save-before.png differ diff --git a/Docs/migration/dialogs/images/custom-list-before.png b/Docs/migration/dialogs/images/custom-list-before.png new file mode 100644 index 0000000000..5ff59a9ce8 Binary files /dev/null and b/Docs/migration/dialogs/images/custom-list-before.png differ diff --git a/Docs/migration/dialogs/images/delete-writing-system-warning-before.png b/Docs/migration/dialogs/images/delete-writing-system-warning-before.png new file mode 100644 index 0000000000..c0b2869a1a Binary files /dev/null and b/Docs/migration/dialogs/images/delete-writing-system-warning-before.png differ diff --git a/Docs/migration/dialogs/images/dictionary-configuration-before.png b/Docs/migration/dialogs/images/dictionary-configuration-before.png new file mode 100644 index 0000000000..8e8828f2e5 Binary files /dev/null and b/Docs/migration/dialogs/images/dictionary-configuration-before.png differ diff --git a/Docs/migration/dialogs/images/dictionary-configuration-import-before.png b/Docs/migration/dialogs/images/dictionary-configuration-import-before.png new file mode 100644 index 0000000000..b1a9983062 Binary files /dev/null and b/Docs/migration/dialogs/images/dictionary-configuration-import-before.png differ diff --git a/Docs/migration/dialogs/images/dictionary-configuration-manager-before.png b/Docs/migration/dialogs/images/dictionary-configuration-manager-before.png new file mode 100644 index 0000000000..ee4fcd2b25 Binary files /dev/null and b/Docs/migration/dialogs/images/dictionary-configuration-manager-before.png differ diff --git a/Docs/migration/dialogs/images/dictionary-configuration-node-rename-before.png b/Docs/migration/dialogs/images/dictionary-configuration-node-rename-before.png new file mode 100644 index 0000000000..baad79f236 Binary files /dev/null and b/Docs/migration/dialogs/images/dictionary-configuration-node-rename-before.png differ diff --git a/Docs/migration/dialogs/images/entry-go-before.png b/Docs/migration/dialogs/images/entry-go-before.png new file mode 100644 index 0000000000..48d8711c77 Binary files /dev/null and b/Docs/migration/dialogs/images/entry-go-before.png differ diff --git a/Docs/migration/dialogs/images/export-before.png b/Docs/migration/dialogs/images/export-before.png new file mode 100644 index 0000000000..3397fbf4d0 Binary files /dev/null and b/Docs/migration/dialogs/images/export-before.png differ diff --git a/Docs/migration/dialogs/images/export-dialog-before.png b/Docs/migration/dialogs/images/export-dialog-before.png new file mode 100644 index 0000000000..e607f0b2f6 Binary files /dev/null and b/Docs/migration/dialogs/images/export-dialog-before.png differ diff --git a/Docs/migration/dialogs/images/export-semantic-domains-before.png b/Docs/migration/dialogs/images/export-semantic-domains-before.png new file mode 100644 index 0000000000..c511ee340a Binary files /dev/null and b/Docs/migration/dialogs/images/export-semantic-domains-before.png differ diff --git a/Docs/migration/dialogs/images/export-translated-lists-before.png b/Docs/migration/dialogs/images/export-translated-lists-before.png new file mode 100644 index 0000000000..c8ce95c800 Binary files /dev/null and b/Docs/migration/dialogs/images/export-translated-lists-before.png differ diff --git a/Docs/migration/dialogs/images/fw-chooser-before.png b/Docs/migration/dialogs/images/fw-chooser-before.png new file mode 100644 index 0000000000..22602ca675 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-chooser-before.png differ diff --git a/Docs/migration/dialogs/images/fw-delete-project-before.png b/Docs/migration/dialogs/images/fw-delete-project-before.png new file mode 100644 index 0000000000..3218b278e6 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-delete-project-before.png differ diff --git a/Docs/migration/dialogs/images/fw-font-before.png b/Docs/migration/dialogs/images/fw-font-before.png new file mode 100644 index 0000000000..c02415897f Binary files /dev/null and b/Docs/migration/dialogs/images/fw-font-before.png differ diff --git a/Docs/migration/dialogs/images/fw-proj-properties-before.png b/Docs/migration/dialogs/images/fw-proj-properties-before.png new file mode 100644 index 0000000000..f5c5706335 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-proj-properties-before.png differ diff --git a/Docs/migration/dialogs/images/fw-proj-properties-tab-general.png b/Docs/migration/dialogs/images/fw-proj-properties-tab-general.png new file mode 100644 index 0000000000..f5c5706335 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-proj-properties-tab-general.png differ diff --git a/Docs/migration/dialogs/images/fw-proj-properties-tab-linked-files.png b/Docs/migration/dialogs/images/fw-proj-properties-tab-linked-files.png new file mode 100644 index 0000000000..2cb50db940 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-proj-properties-tab-linked-files.png differ diff --git a/Docs/migration/dialogs/images/fw-proj-properties-tab-sharing.png b/Docs/migration/dialogs/images/fw-proj-properties-tab-sharing.png new file mode 100644 index 0000000000..d05d759f0f Binary files /dev/null and b/Docs/migration/dialogs/images/fw-proj-properties-tab-sharing.png differ diff --git a/Docs/migration/dialogs/images/fw-styles-before.png b/Docs/migration/dialogs/images/fw-styles-before.png new file mode 100644 index 0000000000..cfb43aaf34 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-styles-before.png differ diff --git a/Docs/migration/dialogs/images/fw-styles-modified-before.png b/Docs/migration/dialogs/images/fw-styles-modified-before.png new file mode 100644 index 0000000000..7f9e3f42da Binary files /dev/null and b/Docs/migration/dialogs/images/fw-styles-modified-before.png differ diff --git a/Docs/migration/dialogs/images/fw-styles-tab-border.png b/Docs/migration/dialogs/images/fw-styles-tab-border.png new file mode 100644 index 0000000000..0647311dd5 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-styles-tab-border.png differ diff --git a/Docs/migration/dialogs/images/fw-styles-tab-bullets.png b/Docs/migration/dialogs/images/fw-styles-tab-bullets.png new file mode 100644 index 0000000000..da1a87ae63 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-styles-tab-bullets.png differ diff --git a/Docs/migration/dialogs/images/fw-styles-tab-font.png b/Docs/migration/dialogs/images/fw-styles-tab-font.png new file mode 100644 index 0000000000..b052e700c6 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-styles-tab-font.png differ diff --git a/Docs/migration/dialogs/images/fw-styles-tab-general.png b/Docs/migration/dialogs/images/fw-styles-tab-general.png new file mode 100644 index 0000000000..03f00a9a35 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-styles-tab-general.png differ diff --git a/Docs/migration/dialogs/images/fw-styles-tab-paragraph.png b/Docs/migration/dialogs/images/fw-styles-tab-paragraph.png new file mode 100644 index 0000000000..99a33e963c Binary files /dev/null and b/Docs/migration/dialogs/images/fw-styles-tab-paragraph.png differ diff --git a/Docs/migration/dialogs/images/fw-update-report-before.png b/Docs/migration/dialogs/images/fw-update-report-before.png new file mode 100644 index 0000000000..c85fe0e202 Binary files /dev/null and b/Docs/migration/dialogs/images/fw-update-report-before.png differ diff --git a/Docs/migration/dialogs/images/headword-numbers-before.png b/Docs/migration/dialogs/images/headword-numbers-before.png new file mode 100644 index 0000000000..53c715ed75 Binary files /dev/null and b/Docs/migration/dialogs/images/headword-numbers-before.png differ diff --git a/Docs/migration/dialogs/images/help-about-before.png b/Docs/migration/dialogs/images/help-about-before.png new file mode 100644 index 0000000000..f45afec444 Binary files /dev/null and b/Docs/migration/dialogs/images/help-about-before.png differ diff --git a/Docs/migration/dialogs/images/import-char-mapping-before.png b/Docs/migration/dialogs/images/import-char-mapping-before.png new file mode 100644 index 0000000000..9fa35d4d0f Binary files /dev/null and b/Docs/migration/dialogs/images/import-char-mapping-before.png differ diff --git a/Docs/migration/dialogs/images/import-date-format-before.png b/Docs/migration/dialogs/images/import-date-format-before.png new file mode 100644 index 0000000000..ffb3af4e0c Binary files /dev/null and b/Docs/migration/dialogs/images/import-date-format-before.png differ diff --git a/Docs/migration/dialogs/images/import-enc-cvtr-before.png b/Docs/migration/dialogs/images/import-enc-cvtr-before.png new file mode 100644 index 0000000000..87e2bc4075 Binary files /dev/null and b/Docs/migration/dialogs/images/import-enc-cvtr-before.png differ diff --git a/Docs/migration/dialogs/images/import-match-replace-before.png b/Docs/migration/dialogs/images/import-match-replace-before.png new file mode 100644 index 0000000000..015e943d5f Binary files /dev/null and b/Docs/migration/dialogs/images/import-match-replace-before.png differ diff --git a/Docs/migration/dialogs/images/insert-entry-before.png b/Docs/migration/dialogs/images/insert-entry-before.png new file mode 100644 index 0000000000..ee19b64c30 Binary files /dev/null and b/Docs/migration/dialogs/images/insert-entry-before.png differ diff --git a/Docs/migration/dialogs/images/insert-record-before.png b/Docs/migration/dialogs/images/insert-record-before.png new file mode 100644 index 0000000000..5137732aca Binary files /dev/null and b/Docs/migration/dialogs/images/insert-record-before.png differ diff --git a/Docs/migration/dialogs/images/insert-variant-before.png b/Docs/migration/dialogs/images/insert-variant-before.png new file mode 100644 index 0000000000..29ebb3d93a Binary files /dev/null and b/Docs/migration/dialogs/images/insert-variant-before.png differ diff --git a/Docs/migration/dialogs/images/interlinear-export-before.png b/Docs/migration/dialogs/images/interlinear-export-before.png new file mode 100644 index 0000000000..e1248b281f Binary files /dev/null and b/Docs/migration/dialogs/images/interlinear-export-before.png differ diff --git a/Docs/migration/dialogs/images/interlinear-import-before.png b/Docs/migration/dialogs/images/interlinear-import-before.png new file mode 100644 index 0000000000..46bf55043e Binary files /dev/null and b/Docs/migration/dialogs/images/interlinear-import-before.png differ diff --git a/Docs/migration/dialogs/images/lex-import-wizard-char-marker-before.png b/Docs/migration/dialogs/images/lex-import-wizard-char-marker-before.png new file mode 100644 index 0000000000..9e90e0995f Binary files /dev/null and b/Docs/migration/dialogs/images/lex-import-wizard-char-marker-before.png differ diff --git a/Docs/migration/dialogs/images/lex-options-before.png b/Docs/migration/dialogs/images/lex-options-before.png new file mode 100644 index 0000000000..7b0f6541b1 Binary files /dev/null and b/Docs/migration/dialogs/images/lex-options-before.png differ diff --git a/Docs/migration/dialogs/images/lex-options-tab-general.png b/Docs/migration/dialogs/images/lex-options-tab-general.png new file mode 100644 index 0000000000..7b0f6541b1 Binary files /dev/null and b/Docs/migration/dialogs/images/lex-options-tab-general.png differ diff --git a/Docs/migration/dialogs/images/lex-options-tab-plugins.png b/Docs/migration/dialogs/images/lex-options-tab-plugins.png new file mode 100644 index 0000000000..3053f91f03 Binary files /dev/null and b/Docs/migration/dialogs/images/lex-options-tab-plugins.png differ diff --git a/Docs/migration/dialogs/images/lex-options-tab-privacy.png b/Docs/migration/dialogs/images/lex-options-tab-privacy.png new file mode 100644 index 0000000000..8faf4acbab Binary files /dev/null and b/Docs/migration/dialogs/images/lex-options-tab-privacy.png differ diff --git a/Docs/migration/dialogs/images/lex-options-tab-updates.png b/Docs/migration/dialogs/images/lex-options-tab-updates.png new file mode 100644 index 0000000000..55ab886851 Binary files /dev/null and b/Docs/migration/dialogs/images/lex-options-tab-updates.png differ diff --git a/Docs/migration/dialogs/images/lift-export-message-before.png b/Docs/migration/dialogs/images/lift-export-message-before.png new file mode 100644 index 0000000000..d5e337d28e Binary files /dev/null and b/Docs/migration/dialogs/images/lift-export-message-before.png differ diff --git a/Docs/migration/dialogs/images/lift-import-before.png b/Docs/migration/dialogs/images/lift-import-before.png new file mode 100644 index 0000000000..0a339f27e0 Binary files /dev/null and b/Docs/migration/dialogs/images/lift-import-before.png differ diff --git a/Docs/migration/dialogs/images/link-allomorph-before.png b/Docs/migration/dialogs/images/link-allomorph-before.png new file mode 100644 index 0000000000..a193af5aeb Binary files /dev/null and b/Docs/migration/dialogs/images/link-allomorph-before.png differ diff --git a/Docs/migration/dialogs/images/link-entry-or-sense-before.png b/Docs/migration/dialogs/images/link-entry-or-sense-before.png new file mode 100644 index 0000000000..aa90146c7e Binary files /dev/null and b/Docs/migration/dialogs/images/link-entry-or-sense-before.png differ diff --git a/Docs/migration/dialogs/images/link-msa-before.png b/Docs/migration/dialogs/images/link-msa-before.png new file mode 100644 index 0000000000..57a04fcfe8 Binary files /dev/null and b/Docs/migration/dialogs/images/link-msa-before.png differ diff --git a/Docs/migration/dialogs/images/master-category-list-before.png b/Docs/migration/dialogs/images/master-category-list-before.png new file mode 100644 index 0000000000..fb35b38500 Binary files /dev/null and b/Docs/migration/dialogs/images/master-category-list-before.png differ diff --git a/Docs/migration/dialogs/images/merge-entry-before.png b/Docs/migration/dialogs/images/merge-entry-before.png new file mode 100644 index 0000000000..b271e67943 Binary files /dev/null and b/Docs/migration/dialogs/images/merge-entry-before.png differ diff --git a/Docs/migration/dialogs/images/merge-object-before.png b/Docs/migration/dialogs/images/merge-object-before.png new file mode 100644 index 0000000000..619c0dd370 Binary files /dev/null and b/Docs/migration/dialogs/images/merge-object-before.png differ diff --git a/Docs/migration/dialogs/images/move-or-copy-files-before.png b/Docs/migration/dialogs/images/move-or-copy-files-before.png new file mode 100644 index 0000000000..e8924b2782 Binary files /dev/null and b/Docs/migration/dialogs/images/move-or-copy-files-before.png differ diff --git a/Docs/migration/dialogs/images/notebook-export-before.png b/Docs/migration/dialogs/images/notebook-export-before.png new file mode 100644 index 0000000000..e62131033c Binary files /dev/null and b/Docs/migration/dialogs/images/notebook-export-before.png differ diff --git a/Docs/migration/dialogs/images/occurrence-before.png b/Docs/migration/dialogs/images/occurrence-before.png new file mode 100644 index 0000000000..a4ba99a00a Binary files /dev/null and b/Docs/migration/dialogs/images/occurrence-before.png differ diff --git a/Docs/migration/dialogs/images/parser-parameters-before.png b/Docs/migration/dialogs/images/parser-parameters-before.png new file mode 100644 index 0000000000..93ca38f775 Binary files /dev/null and b/Docs/migration/dialogs/images/parser-parameters-before.png differ diff --git a/Docs/migration/dialogs/images/project-location-before.png b/Docs/migration/dialogs/images/project-location-before.png new file mode 100644 index 0000000000..cfe8edddc1 Binary files /dev/null and b/Docs/migration/dialogs/images/project-location-before.png differ diff --git a/Docs/migration/dialogs/images/record-go-before.png b/Docs/migration/dialogs/images/record-go-before.png new file mode 100644 index 0000000000..53d305d3da Binary files /dev/null and b/Docs/migration/dialogs/images/record-go-before.png differ diff --git a/Docs/migration/dialogs/images/restore-defaults-before.png b/Docs/migration/dialogs/images/restore-defaults-before.png new file mode 100644 index 0000000000..787a6e194b Binary files /dev/null and b/Docs/migration/dialogs/images/restore-defaults-before.png differ diff --git a/Docs/migration/dialogs/images/restore-project-before.png b/Docs/migration/dialogs/images/restore-project-before.png new file mode 100644 index 0000000000..a088c062e4 Binary files /dev/null and b/Docs/migration/dialogs/images/restore-project-before.png differ diff --git a/Docs/migration/dialogs/images/sfm-to-texts-and-words-mapping-before.png b/Docs/migration/dialogs/images/sfm-to-texts-and-words-mapping-before.png new file mode 100644 index 0000000000..f296dff1f2 Binary files /dev/null and b/Docs/migration/dialogs/images/sfm-to-texts-and-words-mapping-before.png differ diff --git a/Docs/migration/dialogs/images/try-a-word-before.png b/Docs/migration/dialogs/images/try-a-word-before.png new file mode 100644 index 0000000000..e62d38e4ed Binary files /dev/null and b/Docs/migration/dialogs/images/try-a-word-before.png differ diff --git a/Docs/migration/dialogs/images/upload-to-webonary-before.png b/Docs/migration/dialogs/images/upload-to-webonary-before.png new file mode 100644 index 0000000000..94b144d482 Binary files /dev/null and b/Docs/migration/dialogs/images/upload-to-webonary-before.png differ diff --git a/Docs/migration/dialogs/images/utility-before.png b/Docs/migration/dialogs/images/utility-before.png new file mode 100644 index 0000000000..38f6948522 Binary files /dev/null and b/Docs/migration/dialogs/images/utility-before.png differ diff --git a/Docs/migration/dialogs/images/view-hidden-writing-systems-before.png b/Docs/migration/dialogs/images/view-hidden-writing-systems-before.png new file mode 100644 index 0000000000..21f1c68bca Binary files /dev/null and b/Docs/migration/dialogs/images/view-hidden-writing-systems-before.png differ diff --git a/Docs/migration/dialogs/images/xml-doc-configure-before.png b/Docs/migration/dialogs/images/xml-doc-configure-before.png new file mode 100644 index 0000000000..0c4c005ccf Binary files /dev/null and b/Docs/migration/dialogs/images/xml-doc-configure-before.png differ diff --git a/Docs/migration/dialogs/import-char-mapping-dlg.md b/Docs/migration/dialogs/import-char-mapping-dlg.md new file mode 100644 index 0000000000..c152154a03 --- /dev/null +++ b/Docs/migration/dialogs/import-char-mapping-dlg.md @@ -0,0 +1,28 @@ +# Import Char Mapping (`ImportCharMappingDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.DataNotebook.ImportCharMappingDlg` (`Src/LexText/LexTextControls/DataNotebook/ImportCharMappingDlg.cs`) | +| **Area** | Notebook | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![import-char-mapping legacy](./images/import-char-mapping-before.png) | ![import-char-mapping avalonia](./images/import-char-mapping-after.png) | +## What it is +Defines a character/inline-marker mapping for Data Notebook SFM import. + +## Notes / gotchas +- Part of the Data Notebook import-mapping family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/import-date-format-dlg.md b/Docs/migration/dialogs/import-date-format-dlg.md new file mode 100644 index 0000000000..9f49b7c810 --- /dev/null +++ b/Docs/migration/dialogs/import-date-format-dlg.md @@ -0,0 +1,28 @@ +# Import Date Format (`ImportDateFormatDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.DataNotebook.ImportDateFormatDlg` (`Src/LexText/LexTextControls/DataNotebook/ImportDateFormatDlg.cs`) | +| **Area** | Notebook | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![import-date-format legacy](./images/import-date-format-before.png) | ![import-date-format avalonia](./images/import-date-format-after.png) | +## What it is +Specifies the date format for an imported date field (Data Notebook import). + +## Notes / gotchas +- Part of the Data Notebook import-mapping family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/import-enc-cvtr-dlg.md b/Docs/migration/dialogs/import-enc-cvtr-dlg.md new file mode 100644 index 0000000000..3943729235 --- /dev/null +++ b/Docs/migration/dialogs/import-enc-cvtr-dlg.md @@ -0,0 +1,28 @@ +# Import Encoding Converter (`ImportEncCvtrDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.DataNotebook.ImportEncCvtrDlg` (`Src/LexText/LexTextControls/DataNotebook/ImportEncCvtrDlg.cs`) | +| **Area** | Notebook | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![import-enc-cvtr legacy](./images/import-enc-cvtr-before.png) | ![import-enc-cvtr avalonia](./images/import-enc-cvtr-after.png) | +## What it is +Chooses an encoding converter for a given writing system during import. + +## Notes / gotchas +- Wraps an encoding-converter chooser (SilEncConverters40). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/import-match-replace-dlg.md b/Docs/migration/dialogs/import-match-replace-dlg.md new file mode 100644 index 0000000000..1cae5f7711 --- /dev/null +++ b/Docs/migration/dialogs/import-match-replace-dlg.md @@ -0,0 +1,28 @@ +# Import Match/Replace (`ImportMatchReplaceDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.DataNotebook.ImportMatchReplaceDlg` (`Src/LexText/LexTextControls/DataNotebook/ImportMatchReplaceDlg.cs`) | +| **Area** | Notebook | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![import-match-replace legacy](./images/import-match-replace-before.png) | ![import-match-replace avalonia](./images/import-match-replace-after.png) | +## What it is +Defines a match/replace transformation applied to an imported field. + +## Notes / gotchas +- Part of the Data Notebook import-mapping family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/import-word-set-dlg.md b/Docs/migration/dialogs/import-word-set-dlg.md new file mode 100644 index 0000000000..501e411011 --- /dev/null +++ b/Docs/migration/dialogs/import-word-set-dlg.md @@ -0,0 +1,21 @@ +# Import Word Set (`ImportWordSetDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.ImportWordSetDlg` (`Src/LexText/ParserUI/ImportWordSetDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Imports a word set (list of words) for the parser to try. + +## Notes / gotchas +- Parser tool dialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/insert-entry.md b/Docs/migration/dialogs/insert-entry.md new file mode 100644 index 0000000000..5bf552855e --- /dev/null +++ b/Docs/migration/dialogs/insert-entry.md @@ -0,0 +1,33 @@ +# Insert Entry (`InsertEntryDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.InsertEntryDlg` (`Src/LexText/LexTextControls/InsertEntryDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | owned-control form (multi-WS lexeme/gloss + morph-type picker + MSA group box + matching-entries list) | +| **State** | coexist — the Avalonia **InsertEntryDialog** is a KEPT canonical screen (see [README](../README.md)) | +| **JIRA** | LT-XXXXX (canonical reference — not a deferred port) | + +This is the legacy "before" baseline for the kept-canonical Avalonia `InsertEntryDialog`. The harness +captures the dialog CHROME (ctor-only); its live "matching entries" search-browse populates only in the +running app, so the populated results list is captured live / on pickup. + +## Notes / gotchas +- Owned controls: `FwMultiWsTextField` (lexeme form + gloss), `FwOptionPicker` (morph type), `FwMsaGroupBox`. +- The matching-entries list is the live search-browse (XMLView) — see the matching-entries note in `capture-ledger.md`. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![insert-entry legacy](./images/insert-entry-before.png) | ![insert-entry avalonia](./images/insert-entry-after.png) | +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![insert-entry legacy](./images/insert-entry-before.png) | ![insert-entry avalonia](./images/insert-entry-after.png) | diff --git a/Docs/migration/dialogs/insert-record-dlg.md b/Docs/migration/dialogs/insert-record-dlg.md new file mode 100644 index 0000000000..5dca6062d5 --- /dev/null +++ b/Docs/migration/dialogs/insert-record-dlg.md @@ -0,0 +1,29 @@ +# Insert Record (`InsertRecordDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.InsertRecordDlg` (`Src/LexText/LexTextControls/InsertRecordDlg.cs`) | +| **Area** | Notebook | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![insert-record legacy](./images/insert-record-before.png) | ![insert-record avalonia](./images/insert-record-after.png) | +## What it is +Creates a new Data Notebook record (Notebook counterpart of InsertEntryDlg). + +## Notes / gotchas +- Owned FW writing-system-aware title control; writes through LcmModel.Infrastructure (UnitOfWork). +- Closest sibling to the already-migrated InsertEntryDialog - copy that pattern. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/insert-variant-dlg.md b/Docs/migration/dialogs/insert-variant-dlg.md new file mode 100644 index 0000000000..5d906709e3 --- /dev/null +++ b/Docs/migration/dialogs/insert-variant-dlg.md @@ -0,0 +1,28 @@ +# Insert Variant (`InsertVariantDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.InsertVariantDlg` (`Src/LexText/LexTextControls/LinkVariantToEntryOrSense.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | search+list | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![insert-variant legacy](./images/insert-variant-before.png) | ![insert-variant avalonia](./images/insert-variant-after.png) | +## What it is +Insert a new variant, reusing LinkVariantToEntryOrSense logic to detect an already-inserted selected variant. + +## Notes / gotchas +- Subclass of LinkVariantToEntryOrSense (same file, line 427). Source TODO flags refactor of m_fBackRefToVariant logic. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/interlinear-export-dialog.md b/Docs/migration/dialogs/interlinear-export-dialog.md new file mode 100644 index 0000000000..50ae779da5 --- /dev/null +++ b/Docs/migration/dialogs/interlinear-export-dialog.md @@ -0,0 +1,29 @@ +# Interlinear Export (`InterlinearExportDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.InterlinearExportDialog` (`Src/LexText/Interlinear/InterlinearExportDialog.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![interlinear-export legacy](./images/interlinear-export-before.png) | ![interlinear-export avalonia](./images/interlinear-export-after.png) | +## What it is +Exports interlinear text; subclass of ExportDialog. + +## Notes / gotchas +- Subclasses shared ExportDialog (outside this tree); migrate with the ExportDialog family. +- Couples to RootSites (SIL.FieldWorks.Common.RootSites) for view rendering. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/interlinear-import-dlg.md b/Docs/migration/dialogs/interlinear-import-dlg.md new file mode 100644 index 0000000000..5da56816ce --- /dev/null +++ b/Docs/migration/dialogs/interlinear-import-dlg.md @@ -0,0 +1,29 @@ +# Interlinear Import (`InterlinearImportDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.InterlinearImportDlg` (`Src/LexText/Interlinear/InterlinearImportDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![interlinear-import legacy](./images/interlinear-import-before.png) | ![interlinear-import avalonia](./images/interlinear-import-after.png) | +## What it is +Imports interlinear text (FlexText) into the project. + +## Notes / gotchas +- Implements IFwExtension (loaded as a tool extension). +- Uses FileDialog wrappers (SIL.FieldWorks.Common.Controls.FileDialog). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/lex-import-wizard-char-marker-dlg.md b/Docs/migration/dialogs/lex-import-wizard-char-marker-dlg.md new file mode 100644 index 0000000000..fef07d2d2d --- /dev/null +++ b/Docs/migration/dialogs/lex-import-wizard-char-marker-dlg.md @@ -0,0 +1,28 @@ +# Lex Import Wizard - Char Marker (`LexImportWizardCharMarkerDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.LexImportWizardCharMarkerDlg` (`Src/LexText/LexTextControls/LexImportWizardCharMarkerDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![lex-import-wizard-char-marker legacy](./images/lex-import-wizard-char-marker-before.png) | ![lex-import-wizard-char-marker avalonia](./images/lex-import-wizard-char-marker-after.png) | +## What it is +Edits an inline character/begin-end marker mapping in the standard-format lexicon import wizard. + +## Notes / gotchas +- Part of the LexImport SFM wizard step family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/lex-import-wizard-language.md b/Docs/migration/dialogs/lex-import-wizard-language.md new file mode 100644 index 0000000000..740edb997e --- /dev/null +++ b/Docs/migration/dialogs/lex-import-wizard-language.md @@ -0,0 +1,21 @@ +# Lex Import Wizard - Language (`LexImportWizardLanguage`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.LexImportWizardLanguage` (`Src/LexText/LexTextControls/LexImportWizardLanguage.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Defines a language/writing-system + encoding-converter for a marker in the lexicon import wizard. + +## Notes / gotchas +- Part of the LexImport SFM wizard step family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/lex-import-wizard-marker.md b/Docs/migration/dialogs/lex-import-wizard-marker.md new file mode 100644 index 0000000000..d654465a90 --- /dev/null +++ b/Docs/migration/dialogs/lex-import-wizard-marker.md @@ -0,0 +1,21 @@ +# Lex Import Wizard - Marker (`LexImportWizardMarker`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.LexImportWizardMarker` (`Src/LexText/LexTextControls/LexImportWizardMarker.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Edits the destination mapping for an SFM marker in the lexicon import wizard. + +## Notes / gotchas +- Part of the LexImport SFM wizard step family. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/lex-options.md b/Docs/migration/dialogs/lex-options.md new file mode 100644 index 0000000000..d884abcae5 --- /dev/null +++ b/Docs/migration/dialogs/lex-options.md @@ -0,0 +1,39 @@ +# Tools → Options (`LexOptionsDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexText.LexOptionsDlg` (`Src/LexText/LexTextControls/LexOptionsDlg.cs`) | +| **Area** | App-wide | +| **Type** | dialog (TABS) | +| **Primitive** | tabs | +| **State** | coexist — the Avalonia **OptionsDialog** is a KEPT canonical screen (see [README](../README.md)) | +| **JIRA** | LT-XXXXX (canonical reference — not a deferred port) | + +Legacy "before" baseline for the kept-canonical Avalonia `OptionsDialog`. Captured with all tabs +(General / Plugins / Privacy / Updates). + +## Notes / gotchas +- Canonical TABS example; the Avalonia `OptionsDialog` replaces it (live UI-mode apply, no restart). + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![lex-options legacy](./images/lex-options-before.png) | ![lex-options avalonia](./images/lex-options-after.png) | + +Tabs (legacy): + +![general](./images/lex-options-tab-general.png) ![plugins](./images/lex-options-tab-plugins.png) ![privacy](./images/lex-options-tab-privacy.png) ![updates](./images/lex-options-tab-updates.png) +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![lex-options legacy](./images/lex-options-before.png) | ![lex-options avalonia](./images/lex-options-after.png) | + +Tabs (legacy): + +![general](./images/lex-options-tab-general.png) ![plugins](./images/lex-options-tab-plugins.png) ![privacy](./images/lex-options-tab-privacy.png) ![updates](./images/lex-options-tab-updates.png) diff --git a/Docs/migration/dialogs/lift-export-message.md b/Docs/migration/dialogs/lift-export-message.md new file mode 100644 index 0000000000..7df3d18e4b --- /dev/null +++ b/Docs/migration/dialogs/lift-export-message.md @@ -0,0 +1,27 @@ +# LIFT Export Message (`LiftExportMessageDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LiftExportMessageDlg` (`Src/xWorks/LiftExportMessageDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![lift-export-message legacy](./images/lift-export-message-before.png) | ![lift-export-message avalonia](./images/lift-export-message-after.png) | +## What it is +Informational message dialog shown after a LIFT export. + +## Notes / gotchas +- Simple message-and-button plain-form; TBD — confirm exact message/buttons on pickup. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/lift-import-dlg.md b/Docs/migration/dialogs/lift-import-dlg.md new file mode 100644 index 0000000000..dbc7ad59ad --- /dev/null +++ b/Docs/migration/dialogs/lift-import-dlg.md @@ -0,0 +1,28 @@ +# LIFT Import (`LiftImportDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.LiftImportDlg` (`Src/LexText/LexTextControls/LiftImportDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![lift-import legacy](./images/lift-import-before.png) | ![lift-import avalonia](./images/lift-import-after.png) | +## What it is +Imports a LIFT lexicon file into the project. + +## Notes / gotchas +- Implements IFwExtension. Uses SIL.Lift validation. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/lingua-links-import-dlg.md b/Docs/migration/dialogs/lingua-links-import-dlg.md new file mode 100644 index 0000000000..f68a0a454e --- /dev/null +++ b/Docs/migration/dialogs/lingua-links-import-dlg.md @@ -0,0 +1,21 @@ +# LinguaLinks Import (`LinguaLinksImportDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.IText.LinguaLinksImportDlg` (`Src/LexText/Interlinear/LinguaLinksImportDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Imports data from a legacy LinguaLinks export. + +## Notes / gotchas +- Implements IFwExtension. Multi-step import flow. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/link-allomorph-dlg.md b/Docs/migration/dialogs/link-allomorph-dlg.md new file mode 100644 index 0000000000..d806d2285a --- /dev/null +++ b/Docs/migration/dialogs/link-allomorph-dlg.md @@ -0,0 +1,28 @@ +# Link Allomorph (`LinkAllomorphDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.LinkAllomorphDlg` (`Src/LexText/LexTextControls/LinkAllomorphDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | search+list | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![link-allomorph legacy](./images/link-allomorph-before.png) | ![link-allomorph avalonia](./images/link-allomorph-after.png) | +## What it is +Search for and select an existing allomorph to link to. + +## Notes / gotchas +- Subclass of EntryGoDlg. State=coexist: Avalonia path is LcmLinkAllomorphDialogLauncher under UIMode=New. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/link-entry-or-sense-dlg.md b/Docs/migration/dialogs/link-entry-or-sense-dlg.md new file mode 100644 index 0000000000..7e4c1da2bf --- /dev/null +++ b/Docs/migration/dialogs/link-entry-or-sense-dlg.md @@ -0,0 +1,28 @@ +# Link Entry or Sense (`LinkEntryOrSenseDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.LinkEntryOrSenseDlg` (`Src/LexText/LexTextControls/LinkEntryOrSenseDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | search+list | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![link-entry-or-sense legacy](./images/link-entry-or-sense-before.png) | ![link-entry-or-sense avalonia](./images/link-entry-or-sense-after.png) | +## What it is +Search for and select an existing lexical entry or sense to link to. + +## Notes / gotchas +- Subclass of EntryGoDlg. State=coexist: Avalonia path is LcmLinkEntryOrSenseDialogLauncher under UIMode=New. Base class for LinkVariantToEntryOrSense. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/link-msa-dlg.md b/Docs/migration/dialogs/link-msa-dlg.md new file mode 100644 index 0000000000..207d70a4a9 --- /dev/null +++ b/Docs/migration/dialogs/link-msa-dlg.md @@ -0,0 +1,28 @@ +# Link MSA (`LinkMSADlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.LinkMSADlg` (`Src/LexText/LexTextControls/LinkMSADlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | search+list | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![link-msa legacy](./images/link-msa-before.png) | ![link-msa avalonia](./images/link-msa-after.png) | +## What it is +Search for and select an existing entry/MSA (grammatical info) to link to. + +## Notes / gotchas +- Subclass of EntryGoDlg. State=coexist: Avalonia path is LcmLinkMsaDialogLauncher under UIMode=New. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/link-variant-to-entry-or-sense.md b/Docs/migration/dialogs/link-variant-to-entry-or-sense.md new file mode 100644 index 0000000000..0e4d2f3f28 --- /dev/null +++ b/Docs/migration/dialogs/link-variant-to-entry-or-sense.md @@ -0,0 +1,21 @@ +# Link Variant to Entry or Sense (`LinkVariantToEntryOrSense`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.LinkVariantToEntryOrSense` (`Src/LexText/LexTextControls/LinkVariantToEntryOrSense.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | search+list | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Search for and link a variant to its main entry/sense, with extra variant-back-reference logic. + +## Notes / gotchas +- Subclass of LinkEntryOrSenseDlg; instantiated directly (e.g. from the interlinear Sandbox combo handlers) AND subclassed by InsertVariantDlg. State=coexist via the LinkEntryOrSenseDlg/LcmLinkEntryOrSenseDialogLauncher path. Adds m_fBackRefToVariant logic. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/master-category-list-dlg.md b/Docs/migration/dialogs/master-category-list-dlg.md new file mode 100644 index 0000000000..9ea78f154d --- /dev/null +++ b/Docs/migration/dialogs/master-category-list-dlg.md @@ -0,0 +1,29 @@ +# Master Category List (`MasterCategoryListDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MasterCategoryListDlg` (`Src/LexText/LexTextControls/MasterCategoryListDlg.cs`) | +| **Area** | Grammar | +| **Type** | dialog | +| **Primitive** | TREE | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![master-category-list legacy](./images/master-category-list-before.png) | ![master-category-list avalonia](./images/master-category-list-after.png) | +## What it is +Pick a grammatical category (part of speech) from the GOLD master catalog tree and add it to the project. + +## Notes / gotchas +- State=coexist: the Avalonia replacement is LcmCreatePartOfSpeechLauncher (UIMode=New), which mirrors this dialog's catalog-load + create-in-project logic exactly. Also reached via MasterCategoryListChooserLauncher and POSPopupTreeManager. +- Hierarchical single-select tree of master categories loaded from the template directory. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/master-inflection-feature-list-dlg.md b/Docs/migration/dialogs/master-inflection-feature-list-dlg.md new file mode 100644 index 0000000000..9c140a40b7 --- /dev/null +++ b/Docs/migration/dialogs/master-inflection-feature-list-dlg.md @@ -0,0 +1,21 @@ +# Master Inflection Feature List (`MasterInflectionFeatureListDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MasterInflectionFeatureListDlg` (`Src/LexText/LexTextControls/MasterInflectionFeatureListDlg.cs`) | +| **Area** | Grammar | +| **Type** | dialog | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Pick an inflection feature from the master feature catalog tree and add it to the project. + +## Notes / gotchas +- Subclass of MasterListDlg (base excluded). Sibling of the feature-chooser family but distinct (this picks from the GOLD master catalog, not the project feature structure). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/master-phonological-feature-list-dlg.md b/Docs/migration/dialogs/master-phonological-feature-list-dlg.md new file mode 100644 index 0000000000..78e91485ed --- /dev/null +++ b/Docs/migration/dialogs/master-phonological-feature-list-dlg.md @@ -0,0 +1,21 @@ +# Master Phonological Feature List (`MasterPhonologicalFeatureListDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MasterPhonologicalFeatureListDlg` (`Src/LexText/LexTextControls/MasterPhonologicalFeatureListDlg.cs`) | +| **Area** | Grammar | +| **Type** | dialog | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Pick a phonological feature from the master feature catalog tree and add it to the project. + +## Notes / gotchas +- Subclass of MasterListDlg (base excluded). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/merge-entry-dlg.md b/Docs/migration/dialogs/merge-entry-dlg.md new file mode 100644 index 0000000000..7217824969 --- /dev/null +++ b/Docs/migration/dialogs/merge-entry-dlg.md @@ -0,0 +1,28 @@ +# Merge Entry (`MergeEntryDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MergeEntryDlg` (`Src/LexText/LexTextControls/MergeEntryDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | search+list | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![merge-entry legacy](./images/merge-entry-before.png) | ![merge-entry avalonia](./images/merge-entry-after.png) | +## What it is +Search for and select the entry to merge the current entry into. + +## Notes / gotchas +- Subclass of EntryGoDlg. State=coexist: Avalonia path is LcmMergeEntryDialogLauncher under UIMode=New. Merge is destructive - preserve confirmation/undo semantics. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/merge-object.md b/Docs/migration/dialogs/merge-object.md new file mode 100644 index 0000000000..ec78276e74 --- /dev/null +++ b/Docs/migration/dialogs/merge-object.md @@ -0,0 +1,28 @@ +# Merge Object (`MergeObjectDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FdoUi.Dialogs.MergeObjectDlg` (`Src/FdoUi/Dialogs/MergeObjectDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (pick a merge target from a list) with an owned preview control | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![merge-object legacy](./images/merge-object-before.png) | ![merge-object avalonia](./images/merge-object-after.png) | +## What it is +Lets the user merge the current object into another object of the same kind: pick the target to merge into, with a preview/summary of the candidate. + +## Notes / gotchas +- Uses an owned `FwTextBox` (`m_fwTextBoxBottomMsg`, `SIL.FieldWorks.Common.Widgets.FwTextBox`) for the bottom message — needs an owned-control / styled-text equivalent. +- Destructive operation (merge); confirm undo/transaction semantics on pickup. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/merge-writing-system.md b/Docs/migration/dialogs/merge-writing-system.md new file mode 100644 index 0000000000..462bd6afa5 --- /dev/null +++ b/Docs/migration/dialogs/merge-writing-system.md @@ -0,0 +1,20 @@ +# Merge Writing System (`MergeWritingSystemDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.MergeWritingSystemDlg` (`Src/FwCoreDlgs/MergeWritingSystemDlg.cs`) | +| **Area** | App-wide (writing-system management) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it is +The merge writing systems dialog — merges one writing system's data into another. + +## Notes / gotchas +- Modal; data-mutating operation (confirm semantics on pickup). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/mga-dialog.md b/Docs/migration/dialogs/mga-dialog.md new file mode 100644 index 0000000000..b4a8d2eb8d --- /dev/null +++ b/Docs/migration/dialogs/mga-dialog.md @@ -0,0 +1,21 @@ +# MGA (Gloss Assistant) (`MGADialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MGA.MGADialog` (`Src/LexText/Morphology/MGA/MGADialog.cs`) | +| **Area** | Grammar | +| **Type** | dialog | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Morphological Glossing Assistant: build a gloss/grammatical-info string by walking a feature tree. Usable standalone (no HTML help). + +## Notes / gotchas +- Base for MGAHtmlHelpDialog but also used standalone, so it is a real user-facing dialog. Feature tree + generated gloss string. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/mga-html-help-dialog.md b/Docs/migration/dialogs/mga-html-help-dialog.md new file mode 100644 index 0000000000..1100b54bb0 --- /dev/null +++ b/Docs/migration/dialogs/mga-html-help-dialog.md @@ -0,0 +1,21 @@ +# MGA (with HTML Help) (`MGAHtmlHelpDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MGA.MGAHtmlHelpDialog` (`Src/LexText/Morphology/MGA/MGAHtmlHelpDialog.cs`) | +| **Area** | Grammar | +| **Type** | dialog | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +MGADialog plus an embedded HTML help pane describing the currently selected feature node. + +## Notes / gotchas +- Subclass of MGADialog adding an HTML help browser pane. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/missing-old-fieldworks.md b/Docs/migration/dialogs/missing-old-fieldworks.md new file mode 100644 index 0000000000..99b64d9791 --- /dev/null +++ b/Docs/migration/dialogs/missing-old-fieldworks.md @@ -0,0 +1,20 @@ +# Missing Old FieldWorks (`MissingOldFieldWorksDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.MissingOldFieldWorksDlg` (`Src/FwCoreDlgs/MissingOldFieldWorksDlg.cs`) | +| **Area** | App-wide (restore / migration) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it is +Pops up when the user tries to restore/migrate an old project but the old version of FieldWorks (or its special SQL Server instance) is not installed. + +## Notes / gotchas +- Informational/blocking dialog; modal. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/move-or-copy-files.md b/Docs/migration/dialogs/move-or-copy-files.md new file mode 100644 index 0000000000..2ec126b260 --- /dev/null +++ b/Docs/migration/dialogs/move-or-copy-files.md @@ -0,0 +1,27 @@ +# Move or Copy Files (`MoveOrCopyFilesDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.MoveOrCopyFilesDlg` (`Src/FwCoreDlgs/MoveOrCopyFilesDlg.cs`) | +| **Area** | App-wide (linked files) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![move-or-copy-files legacy](./images/move-or-copy-files-before.png) | ![move-or-copy-files avalonia](./images/move-or-copy-files-after.png) | +## What it is +Asks what to do with files when `LangProject.LinkedFilesRootDir` changes, or when linking a file from outside that root — move, copy into the linked-files root, or leave in place. + +## Notes / gotchas +- Driven by `MoveOrCopyFilesController` (folds in); choice/radio-button style decision dialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/new-lang-project.md b/Docs/migration/dialogs/new-lang-project.md new file mode 100644 index 0000000000..48898c9487 --- /dev/null +++ b/Docs/migration/dialogs/new-lang-project.md @@ -0,0 +1,21 @@ +# New Language Project (`FwNewLangProject`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwNewLangProject` (`Src/FwCoreDlgs/FwNewLangProject.cs`) | +| **Area** | App-wide (project creation) | +| **Type** | dialog | +| **Primitive** | owned-control (wizard steps) | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | owned-control form→InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +The New Language Project dialog — collects project name and initial writing systems to create a new FLEx project. + +## Notes / gotchas +- Hosts owned sub-controls: `FwNewProjectProjectNameControl`, `FwNewLangProjWritingSystemsControl`, `FwNewLangProjMoreWsControl`, `FwChooseAnthroListCtrl` (anthropology-list picker, backed by `FwChooseAnthroListModel`), and `WizardStep` (driven by `FwNewLangProjectModel` / `NewLangProjStep`). Fold these into this dialog's migration. +- Wizard-style multi-step flow. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/notebook-export.md b/Docs/migration/dialogs/notebook-export.md new file mode 100644 index 0000000000..0e5edbf2f1 --- /dev/null +++ b/Docs/migration/dialogs/notebook-export.md @@ -0,0 +1,27 @@ +# Notebook Export (`NotebookExportDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.NotebookExportDialog` (`Src/xWorks/NotebookExportDialog.cs`) | +| **Area** | App-wide (Data Notebook) | +| **Type** | dialog | +| **Primitive** | TABLE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (list of export formats + Export action) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![notebook-export legacy](./images/notebook-export-before.png) | ![notebook-export avalonia](./images/notebook-export-after.png) | +## What it is +Subclass of `ExportDialog` that overrides export behaviour to export from the Data Notebook section of Language Explorer. + +## Notes / gotchas +- Inherits the `ExportDialog` `ListView`/config-file surface (`export-dialog.md`); only `ConfigureItem` and the export process differ. Migrate together with `ExportDialog` as a configured variant. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/occurrence-dlg.md b/Docs/migration/dialogs/occurrence-dlg.md new file mode 100644 index 0000000000..f513868d82 --- /dev/null +++ b/Docs/migration/dialogs/occurrence-dlg.md @@ -0,0 +1,28 @@ +# Occurrence (`OccurrenceDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.OccurrenceDlg` (`Src/LexText/LexTextControls/OccurrenceDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![occurrence legacy](./images/occurrence-before.png) | ![occurrence avalonia](./images/occurrence-after.png) | +## What it is +TBD - fill on pickup (prompts for an occurrence count/spec). + +## Notes / gotchas +- No class summary in source. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/overwrite-existing-project.md b/Docs/migration/dialogs/overwrite-existing-project.md new file mode 100644 index 0000000000..43f973a5ee --- /dev/null +++ b/Docs/migration/dialogs/overwrite-existing-project.md @@ -0,0 +1,26 @@ +# Overwrite Existing Project (`OverwriteExistingProject`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.BackupRestore.OverwriteExistingProject` (`Src/FwCoreDlgs/BackupRestore/OverwriteExistingProject.cs`) | +| **Area** | App-wide (backup / restore) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it is +TBD — confirmation shown during restore when the target project already exists, to confirm overwriting (infer from class name; fill on pickup). + +## What it looks like (before / after) +**Live-capture / on-pickup.** This confirmation only appears *mid-restore* (when the target project already +exists), so it can't be opened unattended without actually performing a restore (which would modify a +project — out of scope for read-only capture). Its parent RestoreProjectDlg is captured — see +[restore-project.md](./restore-project.md). + +## Notes / gotchas +- Confirmation dialog launched from the Restore-a-Project dialog (RestoreProjectDlg); modal. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/parser-parameters-dlg.md b/Docs/migration/dialogs/parser-parameters-dlg.md new file mode 100644 index 0000000000..e0933ba330 --- /dev/null +++ b/Docs/migration/dialogs/parser-parameters-dlg.md @@ -0,0 +1,28 @@ +# Parser Parameters (`ParserParametersDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.ParserParametersDlg` (`Src/LexText/ParserUI/ParserParametersDlg.cs`) | +| **Area** | Grammar | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | OptionsDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![parser-parameters legacy](./images/parser-parameters-before.png) | ![parser-parameters avalonia](./images/parser-parameters-after.png) | +## What it is +Edits the active parser's parameters (XML-backed settings). + +## Notes / gotchas +- Subclass of ParserParametersBase (base excluded - never instantiated directly). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/project-location.md b/Docs/migration/dialogs/project-location.md new file mode 100644 index 0000000000..69ffad99eb --- /dev/null +++ b/Docs/migration/dialogs/project-location.md @@ -0,0 +1,27 @@ +# Project Location (`ProjectLocationDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.ProjectLocationDlg` (`Src/FwCoreDlgs/ProjectLocationDlg.cs`) | +| **Area** | App-wide (project management) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![project-location legacy](./images/project-location-before.png) | ![project-location avalonia](./images/project-location-after.png) | +## What it is +Supports controlling the location and sharing of the project folder. + +## Notes / gotchas +- Modal; folder/path selection plus sharing options. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/project-properties.md b/Docs/migration/dialogs/project-properties.md new file mode 100644 index 0000000000..6e5adc52d3 --- /dev/null +++ b/Docs/migration/dialogs/project-properties.md @@ -0,0 +1,32 @@ +# Project Properties (`FwProjPropertiesDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwProjPropertiesDlg` (`Src/FwCoreDlgs/FwProjPropertiesDlg.cs`) | +| **Area** | App-wide (project settings) | +| **Type** | dialog | +| **Primitive** | TABS | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | tabs→OptionsDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![fw-proj-properties legacy](./images/fw-proj-properties-before.png) | ![fw-proj-properties avalonia](./images/fw-proj-properties-after.png) | + +Tabs (legacy): + +![general](./images/fw-proj-properties-tab-general.png) ![linked-files](./images/fw-proj-properties-tab-linked-files.png) ![sharing](./images/fw-proj-properties-tab-sharing.png) +## What it is +The Project Properties dialog — edits project-level settings (name, description, writing systems, linked-files location, etc.). + +## Notes / gotchas +- Views-coupled (references `IVwRootSite`/selection-based rendering for some fields). +- Multi-tab settings surface. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/record-go-dlg.md b/Docs/migration/dialogs/record-go-dlg.md new file mode 100644 index 0000000000..6b39633bf6 --- /dev/null +++ b/Docs/migration/dialogs/record-go-dlg.md @@ -0,0 +1,28 @@ +# Record Go (Notebook) (`RecordGoDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.RecordGoDlg` (`Src/LexText/LexTextControls/RecordGoDlg.cs`) | +| **Area** | Notebook | +| **Type** | dialog | +| **Primitive** | search+list | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![record-go legacy](./images/record-go-before.png) | ![record-go avalonia](./images/record-go-after.png) | +## What it is +Go-to / find dialog for Data Notebook records (Notebook analog of GotoEntryDlg). + +## Notes / gotchas +- Subclass of BaseGoDlg (base excluded). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/related-words.md b/Docs/migration/dialogs/related-words.md new file mode 100644 index 0000000000..0e661e1747 --- /dev/null +++ b/Docs/migration/dialogs/related-words.md @@ -0,0 +1,21 @@ +# Related Words (`RelatedWords`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FdoUi.Dialogs.RelatedWords` (`Src/FdoUi/Dialogs/RelatedWords.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog (owned native-render control inside a dialog) | +| **JIRA** | LT-XXXXX | + +## What it is +Shows words semantically/lexically related to a selected word (or a "not in dictionary" message), letting the user navigate to a related entry. + +## Notes / gotchas +- HARD Views coupling: hosts an embedded `RelatedWordsView : SimpleRootSite` (`Src/FdoUi/Dialogs/RelatedWords.cs:553`) with its own `IVwRootBox`. Migration needs a native-render owned control or a re-implemented related-words view. +- `using SIL.FieldWorks.Common.RootSites`. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/respeller-dlg.md b/Docs/migration/dialogs/respeller-dlg.md new file mode 100644 index 0000000000..7f3dc36f05 --- /dev/null +++ b/Docs/migration/dialogs/respeller-dlg.md @@ -0,0 +1,21 @@ +# Respeller (`RespellerDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.MorphologyEditor.RespellerDlg` (`Src/LexText/Morphology/RespellerDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | TABLE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Bulk-corrects the spelling of a wordform across all its occurrences, with per-occurrence include/exclude. + +## Notes / gotchas +- Owned occurrences browse view (Views-coupled); applies a respelling across many text instances - preserve undo + per-occurrence selection. Resources/Drawing coupling. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/restore-defaults-dlg.md b/Docs/migration/dialogs/restore-defaults-dlg.md new file mode 100644 index 0000000000..97c527cf7c --- /dev/null +++ b/Docs/migration/dialogs/restore-defaults-dlg.md @@ -0,0 +1,28 @@ +# Restore Defaults (`RestoreDefaultsDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexText.RestoreDefaultsDlg` (`Src/LexText/LexTextDll/RestoreDefaultsDlg.cs`) | +| **Area** | App-wide | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![restore-defaults legacy](./images/restore-defaults-before.png) | ![restore-defaults avalonia](./images/restore-defaults-after.png) | +## What it is +TBD - fill on pickup (confirms restoring default settings/layouts). + +## Notes / gotchas +- No class summary in source. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/restore-linked-files-to-projects-folder.md b/Docs/migration/dialogs/restore-linked-files-to-projects-folder.md new file mode 100644 index 0000000000..37c074bbe4 --- /dev/null +++ b/Docs/migration/dialogs/restore-linked-files-to-projects-folder.md @@ -0,0 +1,20 @@ +# Restore Linked Files to Projects Folder (`RestoreLinkedFilesToProjectsFolder`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FdoUi.Dialogs.RestoreLinkedFilesToProjectsFolder` (`Src/FdoUi/Dialogs/RestoreLinkedFilesToProjectsFolder.cs`) | +| **Area** | App-wide | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it is +Restore-linked-files prompt offering to restore linked files into the project's folder (vs another location). + +## Notes / gotchas +- Part of the linked-files restore prompt family (with `CantRestoreLinkedFilesToOriginalLocation` and `FilesToRestoreAreOlder`) — migrate together; likely radio-option + OK/Cancel. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/restore-project.md b/Docs/migration/dialogs/restore-project.md new file mode 100644 index 0000000000..a864c26d8f --- /dev/null +++ b/Docs/migration/dialogs/restore-project.md @@ -0,0 +1,28 @@ +# Restore Project (`RestoreProjectDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.BackupRestore.RestoreProjectDlg` (`Src/FwCoreDlgs/BackupRestore/RestoreProjectDlg.cs`) | +| **Area** | App-wide (backup / restore) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![restore-project legacy](./images/restore-project-before.png) | ![restore-project avalonia](./images/restore-project-after.png) | +## What it is +The Restore Project dialog — picks a backup and restores it into a project. + +## Notes / gotchas +- Driven by `RestoreProjectPresenter`. +- May surface `OverwriteExistingProject` when the restore target already exists. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/reversal-entry-go-dlg.md b/Docs/migration/dialogs/reversal-entry-go-dlg.md new file mode 100644 index 0000000000..768e6921e4 --- /dev/null +++ b/Docs/migration/dialogs/reversal-entry-go-dlg.md @@ -0,0 +1,21 @@ +# Reversal Entry Go (`ReversalEntryGoDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.ReversalEntryGoDlg` (`Src/LexText/Lexicon/ReversalEntryGoDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | search+list | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Go-to / find dialog for reversal-index entries. + +## Notes / gotchas +- Subclass of BaseGoDlg (base excluded). Reversal-index variant of GotoEntryDlg. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/select-clauses-dialog.md b/Docs/migration/dialogs/select-clauses-dialog.md new file mode 100644 index 0000000000..a4d83630bd --- /dev/null +++ b/Docs/migration/dialogs/select-clauses-dialog.md @@ -0,0 +1,21 @@ +# Select Clauses (`SelectClausesDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Discourse.SelectClausesDialog` (`Src/LexText/Discourse/SelectClausesDialog.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +TBD - fill on pickup (constituent-chart clause selection). + +## Notes / gotchas +- No class summary in source. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/sfm-to-texts-and-words-mapping-dlg.md b/Docs/migration/dialogs/sfm-to-texts-and-words-mapping-dlg.md new file mode 100644 index 0000000000..5ce6601bb1 --- /dev/null +++ b/Docs/migration/dialogs/sfm-to-texts-and-words-mapping-dlg.md @@ -0,0 +1,28 @@ +# SFM -> Texts & Words Mapping (`SfmToTextsAndWordsMappingDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.SfmToTextsAndWordsMappingDlg` (`Src/LexText/LexTextControls/SfmToTextsAndWordsMappingBaseDlg.cs`) | +| **Area** | Texts&Words | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![sfm-to-texts-and-words-mapping legacy](./images/sfm-to-texts-and-words-mapping-before.png) | ![sfm-to-texts-and-words-mapping avalonia](./images/sfm-to-texts-and-words-mapping-after.png) | +## What it is +Maps an SFM marker to a Texts & Words destination during interlinear SFM import. + +## Notes / gotchas +- Uses encoding-converter (SilEncConverters40) + writing-system selection. Sibling of the DataNotebook import-mapping dialogs. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/splash-screen.md b/Docs/migration/dialogs/splash-screen.md new file mode 100644 index 0000000000..4f76f12560 --- /dev/null +++ b/Docs/migration/dialogs/splash-screen.md @@ -0,0 +1,21 @@ +# Splash Screen (`RealSplashScreen`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.RealSplashScreen` (`Src/FwCoreDlgs/RealSplashScreen.cs`) | +| **Area** | App-wide (startup) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it is +The startup splash screen the user sees while FieldWorks loads. Created and managed by `FwSplashScreen` (a non-Form `IThreadedProgress` wrapper) and runs on a separate thread; implements `IProgress`. + +## Notes / gotchas +- This is a splash window, not a true modal/modeless dialog — borderless, no user input, runs on its own thread. Migration treatment differs from ordinary dialogs. +- `internal class` (not public). `FwSplashScreen` is the public wrapper/owner and is not itself a Form. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/styles-modified.md b/Docs/migration/dialogs/styles-modified.md new file mode 100644 index 0000000000..118971a880 --- /dev/null +++ b/Docs/migration/dialogs/styles-modified.md @@ -0,0 +1,27 @@ +# Styles Modified (`FwStylesModifiedDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwStylesModifiedDlg` (`Src/FwCoreDlgs/FwStylesModifiedDlg.cs`) | +| **Area** | App-wide (styles) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![fw-styles-modified legacy](./images/fw-styles-modified-before.png) | ![fw-styles-modified avalonia](./images/fw-styles-modified-after.png) | +## What it is +A message box displayed when the stylesheet has been modified, to notify the user that they may want to check their styles. + +## Notes / gotchas +- Subclass of FwUpdateReportDlg (report-style dialog). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/styles.md b/Docs/migration/dialogs/styles.md new file mode 100644 index 0000000000..f784ae55c6 --- /dev/null +++ b/Docs/migration/dialogs/styles.md @@ -0,0 +1,33 @@ +# Styles (`FwStylesDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwStylesDlg` (`Src/FwCoreDlgs/FwStylesDlg.cs`) | +| **Area** | App-wide (styles) | +| **Type** | dialog | +| **Primitive** | TABS | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | tabs→OptionsDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![fw-styles legacy](./images/fw-styles-before.png) | ![fw-styles avalonia](./images/fw-styles-after.png) | + +Tabs (legacy): + +![border](./images/fw-styles-tab-border.png) ![bullets](./images/fw-styles-tab-bullets.png) ![font](./images/fw-styles-tab-font.png) ![general](./images/fw-styles-tab-general.png) ![paragraph](./images/fw-styles-tab-paragraph.png) +## What it is +The (new) Styles dialog — create, edit, and organize paragraph/character styles for the project. + +## Notes / gotchas +- Views-coupled (hosts a live style preview / `IVwRootSite`). +- Hosts the owned `IStylesTab` tab controls (`FwGeneralTab`, `FwFontTab`, `FwParagraphTab`, `FwBulletsTab`, `FwBorderTab`) plus style-list helpers and `StyleInfo`. Fold these into this dialog's migration. +- Includes an `UndoStyleChangesAction` (undo integration). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/summary-dialog-form.md b/Docs/migration/dialogs/summary-dialog-form.md new file mode 100644 index 0000000000..481aedd5ea --- /dev/null +++ b/Docs/migration/dialogs/summary-dialog-form.md @@ -0,0 +1,22 @@ +# Summary Dialog Find in Lexicon (`SummaryDialogForm`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FdoUi.Dialogs.SummaryDialogForm` (`Src/FdoUi/Dialogs/SummaryDialogForm.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog (owned native-render summary control inside a dialog) | +| **JIRA** | LT-XXXXX | + +## What it is +Launched from TE's "Find In Lexicon" when a matching lexical entry exists: displays a summary of the entry with buttons to find other similar entries or to open Flex on the relevant entry. + +## Notes / gotchas +- `internal` class but a real user-facing dialog (not a sub-control). +- `using SIL.FieldWorks.Common.RootSites` -- review for a native-render summary view (owned control). +- UNUSUAL modal pattern: after `ShowDialog`, the caller must test `ShouldLink` and call `LinkToLexicon()` only AFTER the dialog fully closes (see LT-3461) -- closing first prevents TE jumping in front of Flex. Also test `OtherButtonClicked`. Preserve this close-then-link ordering. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/swap-lexeme-with-allomorph-dlg.md b/Docs/migration/dialogs/swap-lexeme-with-allomorph-dlg.md new file mode 100644 index 0000000000..8efff4181b --- /dev/null +++ b/Docs/migration/dialogs/swap-lexeme-with-allomorph-dlg.md @@ -0,0 +1,21 @@ +# Swap Lexeme With Allomorph (`SwapLexemeWithAllomorphDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.LexEd.SwapLexemeWithAllomorphDlg` (`Src/LexText/Lexicon/SwapLexemeWithAllomorphDlg.cs`) | +| **Area** | Lexicon | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it is +TBD - fill on pickup (swaps the lexeme form with one of its allomorphs). + +## Notes / gotchas +- No descriptive class summary in source. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/try-a-word-dlg.md b/Docs/migration/dialogs/try-a-word-dlg.md new file mode 100644 index 0000000000..1e653f2996 --- /dev/null +++ b/Docs/migration/dialogs/try-a-word-dlg.md @@ -0,0 +1,28 @@ +# Try A Word (`TryAWordDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.TryAWordDlg` (`Src/LexText/ParserUI/TryAWordDlg.cs`) | +| **Area** | Grammar | +| **Type** | dialog | +| **Primitive** | owned-control | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | InsertEntryDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![try-a-word legacy](./images/try-a-word-before.png) | ![try-a-word avalonia](./images/try-a-word-after.png) | +## What it is +Runs the parser on a single word and shows the trace/results. + +## Notes / gotchas +- Implements IMediatorProvider + IPropertyTableProvider (hosts its own mediator). Hosts a trace/results view (Views-coupled). Modeless-style parser tool. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. + diff --git a/Docs/migration/dialogs/update-report.md b/Docs/migration/dialogs/update-report.md new file mode 100644 index 0000000000..552dfa0282 --- /dev/null +++ b/Docs/migration/dialogs/update-report.md @@ -0,0 +1,27 @@ +# Update Report (`FwUpdateReportDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwUpdateReportDlg` (`Src/FwCoreDlgs/FwUpdateReportDlg.cs`) | +| **Area** | App-wide | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it is +Displays somewhat technical information reporting changes that were applied automatically to a project but which the user might want to review. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![fw-update-report legacy](./images/fw-update-report-before.png) | ![fw-update-report avalonia](./images/fw-update-report-after.png) | +## Notes / gotchas +- Intended as an abstract base but kept concrete so the Designer works in derived classes; subclassed by FwStylesModifiedDlg. Override `HelpTopicKey` per use. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/upload-to-webonary.md b/Docs/migration/dialogs/upload-to-webonary.md new file mode 100644 index 0000000000..712ec7ee79 --- /dev/null +++ b/Docs/migration/dialogs/upload-to-webonary.md @@ -0,0 +1,29 @@ +# Upload to Webonary (`UploadToWebonaryDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.UploadToWebonaryDlg` (`Src/xWorks/UploadToWebonaryDlg.cs`) | +| **Area** | Dictionary-config | +| **Type** | dialog | +| **Primitive** | MULTI-SELECTOR | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (credentials + checked multi-select of reversals/publications) | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![upload-to-webonary legacy](./images/upload-to-webonary-before.png) | ![upload-to-webonary avalonia](./images/upload-to-webonary-after.png) | +## What it is +Dialog for publishing dictionary data to the Webonary web site: site/credentials, publication and configuration choice, and a checkbox list of reversals (`reversalsCheckedListBox`) to include. Implements `IUploadToWebonaryView` (MVC view). + +## Notes / gotchas +- `CheckedListBox` of reversals (checked-item state must round-trip to/from saved project settings). +- Performs network upload; needs `HelpTopicProvider` and saves project-specific settings. Logic in the Webonary controller/model (`UploadToWebonaryModel`, `WebonaryClient`). +- Pairs with `WebonaryLogViewer` for results. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/user-properties.md b/Docs/migration/dialogs/user-properties.md new file mode 100644 index 0000000000..dbc875203b --- /dev/null +++ b/Docs/migration/dialogs/user-properties.md @@ -0,0 +1,21 @@ +# User Properties (`FwUserProperties`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwUserProperties` (`Src/FwCoreDlgs/FwUserProperties.cs`) | +| **Area** | App-wide (user management) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it is +User Properties / logons dialog. Per the class comment, this is reached only by a menu command not currently configured in any XML, and the feature was never fully implemented — likely obsolete. + +## Notes / gotchas +- Largely dead code; verify whether it is still reachable before investing in a migration. +- Parent of `AddNewUserDlg` (Add button). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/utility.md b/Docs/migration/dialogs/utility.md new file mode 100644 index 0000000000..544a393914 --- /dev/null +++ b/Docs/migration/dialogs/utility.md @@ -0,0 +1,27 @@ +# Utilities (`UtilityDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.UtilityDlg` (`Src/FwCoreDlgs/UtilityDlg.cs`) | +| **Area** | App-wide (tools / utilities) | +| **Type** | dialog | +| **Primitive** | plain-form (list + description panes) | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | search+list→EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![utility legacy](./images/utility-before.png) | ![utility avalonia](./images/utility-after.png) | +## What it is +Presents the list of utilities defined in `Language Explorer\Configuration\UtilityCatalogInclude.xml`. Each utility implements `IUtility` and can set explanatory labels describing when it is needed and what it does. + +## Notes / gotchas +- Plugin-style: items come from `IUtility` implementations enumerated from XML config. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/valid-characters.md b/Docs/migration/dialogs/valid-characters.md new file mode 100644 index 0000000000..5d482396f0 --- /dev/null +++ b/Docs/migration/dialogs/valid-characters.md @@ -0,0 +1,21 @@ +# Valid Characters (`ValidCharactersDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.ValidCharactersDlg` (`Src/FwCoreDlgs/ValidCharactersDlg.cs`) | +| **Area** | App-wide (writing-system management) | +| **Type** | dialog | +| **Primitive** | TABS | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | tabs→OptionsDialog | +| **JIRA** | LT-XXXXX | + +## What it is +Dialog for specifying the valid characters for a FieldWorks writing system. + +## Notes / gotchas +- Views-coupled (hosts `IVwRootSite`-based rendering). +- Hosts the owned `CharContextCtrl` (a `UserControl` with a `ContextGrid : DataGridView` and Views coupling) and uses `FwCharacterCategorizer`. Fold these into this dialog's migration. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/view-hidden-writing-systems.md b/Docs/migration/dialogs/view-hidden-writing-systems.md new file mode 100644 index 0000000000..aee2e9e849 --- /dev/null +++ b/Docs/migration/dialogs/view-hidden-writing-systems.md @@ -0,0 +1,27 @@ +# View Hidden Writing Systems (`ViewHiddenWritingSystemsDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.ViewHiddenWritingSystemsDlg` (`Src/FwCoreDlgs/ViewHiddenWritingSystemsDlg.cs`) | +| **Area** | App-wide (writing-system management) | +| **Type** | dialog | +| **Primitive** | plain-form (list) | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | search+list→EntryGoDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![view-hidden-writing-systems legacy](./images/view-hidden-writing-systems-before.png) | ![view-hidden-writing-systems avalonia](./images/view-hidden-writing-systems-after.png) | +## What it is +Lists writing systems that have text in the project but are not in the current type's list (may be in the opposite list); lets the user add a writing system or delete all text in one. Backed by `ViewHiddenWritingSystemsModel`. + +## Notes / gotchas +- View/model split (`ViewHiddenWritingSystemsModel`). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/warning-not-using-default-linked-files-location.md b/Docs/migration/dialogs/warning-not-using-default-linked-files-location.md new file mode 100644 index 0000000000..4c1d3da174 --- /dev/null +++ b/Docs/migration/dialogs/warning-not-using-default-linked-files-location.md @@ -0,0 +1,20 @@ +# Warning: Not Using Default Linked Files Location (`WarningNotUsingDefaultLinkedFilesLocation`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.WarningNotUsingDefaultLinkedFilesLocation` (`Src/FwCoreDlgs/WarningNotUsingDefaultLinkedFilesLocation.cs`) | +| **Area** | App-wide (linked files / Send-Receive) | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form→nearest | +| **JIRA** | LT-XXXXX | + +## What it is +Warns the user that when they choose a custom location for linked files, Send/Receive will not send those files. + +## Notes / gotchas +- Warning dialog; modal. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/webonary-log-viewer.md b/Docs/migration/dialogs/webonary-log-viewer.md new file mode 100644 index 0000000000..f21f1faa59 --- /dev/null +++ b/Docs/migration/dialogs/webonary-log-viewer.md @@ -0,0 +1,21 @@ +# Webonary Log Viewer (`WebonaryLogViewer`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.WebonaryLogViewer` (`Src/xWorks/WebonaryLogViewer.cs`) | +| **Area** | Dictionary-config | +| **Type** | dialog | +| **Primitive** | TABLE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (filterable log grid) | +| **JIRA** | LT-XXXXX | + +## What it is +Displays the Webonary upload log in a `DataGridView`, with a status-level filter combo (Full Log / errors / warnings) backed by `WebonaryStatusCondition`. + +## Notes / gotchas +- `DataGridView` (not `ListView`) loaded from a log file; filter uses a custom `ComboBoxItem` and a checked-combo (`SIL.Windows.Forms.CheckedComboBox`). +- Read-only viewer; pairs with `UploadToWebonaryDlg`. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/xml-diagnostics.md b/Docs/migration/dialogs/xml-diagnostics.md new file mode 100644 index 0000000000..08f2244f09 --- /dev/null +++ b/Docs/migration/dialogs/xml-diagnostics.md @@ -0,0 +1,20 @@ +# XML Diagnostics (`XmlDiagnosticsDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.XmlDiagnosticsDlg` (`Src/xWorks/XmlDiagnosticsDlg.cs`) | +| **Area** | App-wide | +| **Type** | dialog | +| **Primitive** | plain-form | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | plain-form (nearest: OptionsDialog) | +| **JIRA** | LT-XXXXX | + +## What it is +Developer/diagnostics dialog that displays XML diagnostics information. + +## Notes / gotchas +- Diagnostics/utility surface (likely text display); low user exposure. Confirm exact content/controls on pickup. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/dialogs/xml-doc-configure.md b/Docs/migration/dialogs/xml-doc-configure.md new file mode 100644 index 0000000000..2856443e3b --- /dev/null +++ b/Docs/migration/dialogs/xml-doc-configure.md @@ -0,0 +1,30 @@ +# XML Document Configure (legacy) (`XmlDocConfigureDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.XWorks.XmlDocConfigureDlg` (`Src/xWorks/XmlDocConfigureDlg.cs`) | +| **Area** | Dictionary-config | +| **Type** | dialog | +| **Primitive** | TREE | +| **State** | legacy | +| **Phase** | 1 | +| **Canonical reference** | ChooserDialog (tree + detail), but this is a large bespoke screen — see gotchas | +| **JIRA** | LT-XXXXX | + +## What it is +The older jtview-layout configuration editor (the predecessor to `DictionaryConfigurationDlg`): builds a checkable `TreeView` (`m_tvParts`) from XML ``/`` nodes and lets the user configure the Dictionary view. Implements `ILayoutConverter`. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test; attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![xml-doc-configure legacy](./images/xml-doc-configure-before.png) | ![xml-doc-configure avalonia](./images/xml-doc-configure-after.png) | + +## Notes / gotchas +- LARGE, COMPLEX legacy screen with a checkable parts `TreeView`, content `ListView` (writing systems / relation / complex-form / variant / minor-entry types), and per-node detail editing. +- Tightly coupled to the XML jtview layout model (`` traversal, `hideConfig` attributes) and `Common.RootSites`. +- Launches the legacy `DictionaryConfigMgrDlg` (`dictionary-config-mgr.md`) from its Manage button (`:4368`). Likely to RETIRE alongside the legacy config path — confirm which configure dialog survives before investing. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/feature-chooser.md b/Docs/migration/feature-chooser.md new file mode 100644 index 0000000000..65881c3234 --- /dev/null +++ b/Docs/migration/feature-chooser.md @@ -0,0 +1,53 @@ +# Feature-Value Chooser (legacy `MsaInflectionFeatureListDlg` / `PhonologicalFeatureChooserDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MsaInflectionFeatureListDlg` (`Src/LexText/LexTextControls/MsaInflectionFeatureListDlg.cs`) and `PhonologicalFeatureChooserDlg` (`Src/LexText/LexTextControls/PhonologicalFeatureChooserDlg.cs`) | +| **Area / tool** | Lexicon / Grammar › inflection-feature slice and phonological-feature slice › feature-value chooser | +| **Primitive(s)** | picker (owned `FwFeatureStructureEditor` feature-value tree) | +| **Canonical reference** | ChooserDialog (tree / multi-select picker) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/FeatureChooserDialogView.axaml(.cs)` + `FeatureChooserDialogViewModel.cs` @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +A standalone feature-structure chooser: assigns feature values to an MSA's `IFsFeatStruc` (inflection +features) or to the phonological feature system. Opens from the inflection-feature slice and the +phonological-feature slice. **PARTIAL completeness** — see gotchas. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![phonological-feature-chooser legacy](./images/phonological-feature-chooser-before.png) | ![phonological-feature-chooser avalonia](./images/phonological-feature-chooser-after.png) | +## Behaviour to preserve (parity checklist) +- [ ] Optional instruction prompt at top (may be empty). +- [ ] Owned `FwFeatureStructureEditor`: hierarchical feature system with per-feature value assignment. +- [ ] No OK gate — an empty assignment set (every feature "") is the valid "delete the FS / unspecified" outcome (matches both legacy dialogs). +- [ ] Help button shown only when a help topic is available. + +## Migration gotchas +- The single Avalonia chooser stands in for **two** legacy dialogs (inflection + phonological), driven by + two launchers (`LcmInflectionFeatureChooserLauncher`, `LcmPhonologicalFeatureChooserLauncher`). +- Stub header: "Phase-1 §19b Stage 3 … the Avalonia replacement for the WinForms MsaInflectionFeatureListDlg + … and PhonologicalFeatureChooserDlg". +- PARTIAL — PARITY (from `LcmPhonologicalFeatureChooserLauncher.cs`): "the legacy dialog can also drive + phonological-RULE feature CONSTRAINTS (agree/disagree polarity over IPhFeatureConstraint), used only from + the rule-formula control. That polarity surface is NOT ported here." Re-wiring the rule-formula constraint + path is out of scope for the bounded port. +- Inline create-feature / add-value affordances are wired through `LcmInflectionFeatureCreateWiring`. + +## Wiring +- Legacy call site(s): `Src/LexText/LexTextControls/MsaInflectionFeatureListDlgLauncher.cs` and + `PhonologicalFeatureListDlgLauncher.cs` — the Legacy branches construct the WinForms + `MsaInflectionFeatureListDlg` / `PhonologicalFeatureChooserDlg`. +- The Avalonia path branched on `UIMode=New` here before back-out: + - inflection: `Src/LexText/LexTextControls/MsaInflectionFeatureListDlgLauncher.cs:176` — + `LcmInflectionFeatureChooserLauncher.ShowForOwner(...)`. + - phonological: `Src/LexText/LexTextControls/PhonologicalFeatureListDlgLauncher.cs:124` — + `LcmPhonologicalFeatureChooserLauncher.Show(...)`. + - Launchers: `LcmInflectionFeatureChooserLauncher` / `LcmPhonologicalFeatureChooserLauncher` + (`Src/LexText/LexTextControls/`). +- Re-wiring target: both launchers re-enter the Avalonia chooser behind `UIMode=New`; Legacy keeps + `MsaInflectionFeatureListDlg` / `PhonologicalFeatureChooserDlg`. diff --git a/Docs/migration/filter-for.md b/Docs/migration/filter-for.md new file mode 100644 index 0000000000..b4b0e6e5a9 --- /dev/null +++ b/Docs/migration/filter-for.md @@ -0,0 +1,43 @@ +# Filter For (legacy `SimpleMatchDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.Common.Controls.SimpleMatchDlg` (`Src/Common/Controls/XMLViews/SimpleMatchDlg.cs`) | +| **Area / tool** | Any browse view › column header filter › "Filter for…" | +| **Primitive(s)** | plain-form (text + position radios + regex/case checkboxes) | +| **Canonical reference** | OptionsDialog (closest kept canonical for a small plain-form with controls) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/FilterForDialogView.axaml(.cs)` + `FilterForDialogViewModel.cs` (+ `FilterForPattern.cs`) @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +Lets the user enter a text match for a browse-view column filter: the match string plus where it must +match (anywhere / start / end / whole item), with regex and case options. Opens from a browse column's +"Filter for…" command. Returns a `FilterForPattern`. + +## What it looks like + +![Filter For – initial](./images/filter-for-01.png) + +## Behaviour to preserve (parity checklist) +- [ ] Match-text field (required). +- [ ] Position radios (mutually exclusive): anywhere (default) / at start / at end / whole item. +- [ ] "Use regular expressions" checkbox: disables the position radios when on. +- [ ] "Match case" checkbox. +- [ ] OK gated: empty match text blocks OK; in regex mode an invalid regex blocks OK. + +## Migration gotchas +- Stub header: "the Avalonia counterpart of the legacy `SimpleMatchDlg`"; its Behaviors section documents + the position-radio disabling under regex and the OK-gating rules. +- Note: the legacy `SimpleMatchDlg` also exposes a "Match diacritics" option; verify whether the Avalonia + `FilterForPattern` carries it (the stub's documented radios are anywhere/start/end/whole + regex + case). +- WS/RTL: the match text is plain text against a single column's display string. + +## Wiring +- Legacy call site(s): the Legacy branch of the filter-for path in `Src/xWorks/RecordBrowseView.cs` + constructs the WinForms `SimpleMatchDlg` (`Src/Common/Controls/XMLViews/SimpleMatchDlg.cs`). +- The Avalonia path branched on `UIMode=New` here before back-out (direct, no launcher): + `Src/xWorks/RecordBrowseView.cs:662` — `new FwAvaloniaDialogs.FilterForDialogViewModel();` + (View constructed at `:663`). +- Re-wiring target: `RecordBrowseView` filter-for path re-enters the Avalonia dialog behind `UIMode=New`; + Legacy keeps `SimpleMatchDlg`. diff --git a/Docs/migration/find-replace.md b/Docs/migration/find-replace.md new file mode 100644 index 0000000000..fc700669ab --- /dev/null +++ b/Docs/migration/find-replace.md @@ -0,0 +1,53 @@ +# Find / Replace — bulk-replace subset (legacy `FwFindReplaceDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwFindReplaceDlg` (`Src/FwCoreDlgs/FwFindReplaceDlg.cs`) — app-wide modeless find/replace; this is a Phase-1 **subset** for bulk replace only | +| **Area / tool** | Browse view › bulk edit › Find/Replace pattern setup | +| **Primitive(s)** | plain-form (find/replace text + match-option checkboxes) | +| **Canonical reference** | OptionsDialog (closest kept canonical for a small plain-form with controls) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/FindReplaceDialogView.axaml(.cs)` + `FindReplaceDialogViewModel.cs` (+ `FindReplacePattern.cs`) @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +A spec-only modal that lets the user author a Find/Replace pattern for a bulk replace over a browse +column. OK snapshots the edited fields into a `FindReplacePattern`. There is NO find engine and NO +modeless app-wide find/replace here — that is deferred to Phase 2. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![fw-find-replace legacy](./images/fw-find-replace-before.png) | ![fw-find-replace avalonia](./images/fw-find-replace-after.png) | + +Tabs (legacy): + +![find](./images/fw-find-replace-tab-find.png) +## Behaviour to preserve (parity checklist) +- [ ] Find-text field (required) and Replace-text field. +- [ ] "Use regular expressions" checkbox: disables/clears the literal-only options (match case, whole word). +- [ ] "Match case" and "Match whole word" checkboxes (literal mode only; disabled when regex is on). +- [ ] "Match diacritics" and "Match writing system" checkboxes are present but grayed (P1 no-ops). +- [ ] OK gated: empty find text blocks OK; in regex mode an invalid regex blocks OK. + +## Migration gotchas +- DEFERRED P2 (stub `FindReplaceDialogViewModel.cs`): "There is NO find engine and NO modeless app-wide + Find/Replace here (deferred P2); OK simply snapshots the edited fields into" the pattern. +- P1 SCOPE (stub `FindReplacePattern.cs`): the producer applies the pattern over single-WS plain-text cells + in managed code (System.Text.RegularExpressions, else literal case/whole-word replace). +- P1 NO-OPS (stub): "`MatchDiacritics` and `MatchWritingSystem` are CARRIED but are P1 no-ops (a faithful + diacritic/WS-collation match needs the full `IVwPattern` + TsString round-trip, deferred to P2)"; the + dialog grays them "so the user is not misled." +- View comment: "Spec-only modal (OK snapshots the FindReplacePattern into Result) — there is NO find engine + and NO modeless app-wide dialog here (deferred P2)." + +## Wiring +- Legacy call site(s): the legacy bulk-replace path opens the WinForms find/replace surface + (`FwFindReplaceDlg`, `Src/FwCoreDlgs/FwFindReplaceDlg.cs`); the modeless app-wide dialog stays on this path. +- The Avalonia path branched on `UIMode=New` here before back-out (direct, no launcher): + `Src/xWorks/RecordBrowseView.cs:1641` — `new FwAvaloniaDialogs.FindReplaceDialogViewModel(pattern);` + (View at `:1642`). +- Re-wiring target: `RecordBrowseView` bulk find/replace path re-enters the Avalonia dialog behind + `UIMode=New`; the modeless app-wide `FwFindReplaceDlg` remains on the legacy path and is the Phase-2 target. diff --git a/Docs/migration/images/add-new-sense-before.png b/Docs/migration/images/add-new-sense-before.png new file mode 100644 index 0000000000..ed3ffa25e1 Binary files /dev/null and b/Docs/migration/images/add-new-sense-before.png differ diff --git a/Docs/migration/images/confirm-delete-object-before.png b/Docs/migration/images/confirm-delete-object-before.png new file mode 100644 index 0000000000..a925eac808 Binary files /dev/null and b/Docs/migration/images/confirm-delete-object-before.png differ diff --git a/Docs/migration/images/feature-chooser-01.png b/Docs/migration/images/feature-chooser-01.png new file mode 100644 index 0000000000..8440f7c6f0 Binary files /dev/null and b/Docs/migration/images/feature-chooser-01.png differ diff --git a/Docs/migration/images/feature-chooser-before.png b/Docs/migration/images/feature-chooser-before.png new file mode 100644 index 0000000000..c06aacd41f Binary files /dev/null and b/Docs/migration/images/feature-chooser-before.png differ diff --git a/Docs/migration/images/fw-find-replace-before.png b/Docs/migration/images/fw-find-replace-before.png new file mode 100644 index 0000000000..972a7f9468 Binary files /dev/null and b/Docs/migration/images/fw-find-replace-before.png differ diff --git a/Docs/migration/images/fw-find-replace-tab-find.png b/Docs/migration/images/fw-find-replace-tab-find.png new file mode 100644 index 0000000000..972a7f9468 Binary files /dev/null and b/Docs/migration/images/fw-find-replace-tab-find.png differ diff --git a/Docs/migration/images/fw-writing-system-setup-before.png b/Docs/migration/images/fw-writing-system-setup-before.png new file mode 100644 index 0000000000..9751ce6ade Binary files /dev/null and b/Docs/migration/images/fw-writing-system-setup-before.png differ diff --git a/Docs/migration/images/fw-writing-system-setup-tab-characters.png b/Docs/migration/images/fw-writing-system-setup-tab-characters.png new file mode 100644 index 0000000000..04d8aada62 Binary files /dev/null and b/Docs/migration/images/fw-writing-system-setup-tab-characters.png differ diff --git a/Docs/migration/images/fw-writing-system-setup-tab-converters.png b/Docs/migration/images/fw-writing-system-setup-tab-converters.png new file mode 100644 index 0000000000..e0213e55ca Binary files /dev/null and b/Docs/migration/images/fw-writing-system-setup-tab-converters.png differ diff --git a/Docs/migration/images/fw-writing-system-setup-tab-font.png b/Docs/migration/images/fw-writing-system-setup-tab-font.png new file mode 100644 index 0000000000..d93c787290 Binary files /dev/null and b/Docs/migration/images/fw-writing-system-setup-tab-font.png differ diff --git a/Docs/migration/images/fw-writing-system-setup-tab-general.png b/Docs/migration/images/fw-writing-system-setup-tab-general.png new file mode 100644 index 0000000000..9751ce6ade Binary files /dev/null and b/Docs/migration/images/fw-writing-system-setup-tab-general.png differ diff --git a/Docs/migration/images/fw-writing-system-setup-tab-keyboard.png b/Docs/migration/images/fw-writing-system-setup-tab-keyboard.png new file mode 100644 index 0000000000..7792bcd38d Binary files /dev/null and b/Docs/migration/images/fw-writing-system-setup-tab-keyboard.png differ diff --git a/Docs/migration/images/fw-writing-system-setup-tab-numbers.png b/Docs/migration/images/fw-writing-system-setup-tab-numbers.png new file mode 100644 index 0000000000..5e89ba444d Binary files /dev/null and b/Docs/migration/images/fw-writing-system-setup-tab-numbers.png differ diff --git a/Docs/migration/images/fw-writing-system-setup-tab-sorting.png b/Docs/migration/images/fw-writing-system-setup-tab-sorting.png new file mode 100644 index 0000000000..1f898848a6 Binary files /dev/null and b/Docs/migration/images/fw-writing-system-setup-tab-sorting.png differ diff --git a/Docs/migration/images/lex-reference-details-before.png b/Docs/migration/images/lex-reference-details-before.png new file mode 100644 index 0000000000..7d13a276a4 Binary files /dev/null and b/Docs/migration/images/lex-reference-details-before.png differ diff --git a/Docs/migration/images/master-inflection-feature-list-before.png b/Docs/migration/images/master-inflection-feature-list-before.png new file mode 100644 index 0000000000..f1e77af322 Binary files /dev/null and b/Docs/migration/images/master-inflection-feature-list-before.png differ diff --git a/Docs/migration/images/master-phonological-feature-list-before.png b/Docs/migration/images/master-phonological-feature-list-before.png new file mode 100644 index 0000000000..27ac6a40c3 Binary files /dev/null and b/Docs/migration/images/master-phonological-feature-list-before.png differ diff --git a/Docs/migration/images/msa-creator-before.png b/Docs/migration/images/msa-creator-before.png new file mode 100644 index 0000000000..56f88e45ec Binary files /dev/null and b/Docs/migration/images/msa-creator-before.png differ diff --git a/Docs/migration/images/msa-inflection-feature-list-before.png b/Docs/migration/images/msa-inflection-feature-list-before.png new file mode 100644 index 0000000000..d5779d7b53 Binary files /dev/null and b/Docs/migration/images/msa-inflection-feature-list-before.png differ diff --git a/Docs/migration/images/phonological-feature-chooser-before.png b/Docs/migration/images/phonological-feature-chooser-before.png new file mode 100644 index 0000000000..a5f6d737a1 Binary files /dev/null and b/Docs/migration/images/phonological-feature-chooser-before.png differ diff --git a/Docs/migration/lex-reference-details.md b/Docs/migration/lex-reference-details.md new file mode 100644 index 0000000000..5abe85852b --- /dev/null +++ b/Docs/migration/lex-reference-details.md @@ -0,0 +1,46 @@ +# Lexical Reference Details (legacy `LexReferenceDetailsDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.LexReferenceDetailsDlg` (`Src/LexText/LexTextControls/LexReferenceDetailsDlg.cs`) | +| **Area / tool** | Lexicon › lexical-reference slice › "Edit Reference Set Details…" | +| **Primitive(s)** | plain-form (2 fields: Name + multi-line Comment) | +| **Canonical reference** | InsertEntryDialog (closest kept canonical for a small plain-form with text fields) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/LexReferenceDetailsDialogView.axaml(.cs)` + `LexReferenceDetailsDialogViewModel.cs` @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +Lets the user edit a lexical reference set's name (its display label / "type" for this set) and an optional +comment/note. Opens from the lexical-reference slice's "edit details" command. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![lex-reference-details legacy](./images/lex-reference-details-before.png) | ![lex-reference-details avalonia](./images/lex-reference-details-after.png) | +## Behaviour to preserve (parity checklist) +- [ ] Explanation / instructional text at top. +- [ ] Name text field (single-line) with its label. +- [ ] Comment text field (multi-line, accepts returns / wraps) with its label. +- [ ] OK is intentionally NOT gated — a reference set may legitimately carry an empty name or note. +- [ ] Name and Comment are trimmed before the launcher reads them. +- [ ] OK / Cancel buttons. + +## Migration gotchas +- Stub header: "Phase-1 §19g". +- PARITY (stub): "OK is intentionally NOT gated — parity with the legacy dialog, where a reference set may + legitimately carry an empty name or note (the slice display falls back to the reference type's own name)." + Do not add an OK gate on Name. +- Undo-fencing: the edit runs inside the caller's undo action (`ksUndoEditRefSetDetails` / + `ksRedoEditRefSetDetails`). + +## Wiring +- Legacy call site(s): the Legacy edit-details path in `Src/LexText/LexTextControls/LexReferenceMultiSlice.cs` + constructs the WinForms `LexReferenceDetailsDlg` (`Src/LexText/LexTextControls/LexReferenceDetailsDlg.cs`). +- The Avalonia path branched on `UIMode=New` here before back-out: + `Src/LexText/LexTextControls/LexReferenceMultiSlice.cs:1041` — `LcmLexReferenceDetailsLauncher.Edit(...)`. + Launcher: `LcmLexReferenceDetailsLauncher` (`Src/LexText/LexTextControls/LcmLexReferenceDetailsLauncher.cs`). +- Re-wiring target: `LexReferenceMultiSlice` edit-details path re-enters the Avalonia dialog behind + `UIMode=New`; Legacy keeps `LexReferenceDetailsDlg`. diff --git a/Docs/migration/lists/affix-category-edit.md b/Docs/migration/lists/affix-category-edit.md new file mode 100644 index 0000000000..c784a10762 --- /dev/null +++ b/Docs/migration/lists/affix-category-edit.md @@ -0,0 +1,25 @@ +# Affix Categories (`affixCategoryEdit`) + +| | | +|---|---| +| **Tool id** | `affixCategoryEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Affix Categories (`affixCategoryEdit`) (Sena 3, Legacy)](./images/affix-category-edit-01.png) + +## What it is +Edit the 'Affix Categories' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/annotation-def-edit.md b/Docs/migration/lists/annotation-def-edit.md new file mode 100644 index 0000000000..57e5bc18da --- /dev/null +++ b/Docs/migration/lists/annotation-def-edit.md @@ -0,0 +1,25 @@ +# Annotation Definitions (`annotationDefEdit`) + +| | | +|---|---| +| **Tool id** | `annotationDefEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Annotation Definitions (`annotationDefEdit`) (Sena 3, Legacy)](./images/annotation-def-edit-01.png) + +## What it is +Edit the 'Annotation Definitions' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/anthro-edit.md b/Docs/migration/lists/anthro-edit.md new file mode 100644 index 0000000000..327d2f876e --- /dev/null +++ b/Docs/migration/lists/anthro-edit.md @@ -0,0 +1,25 @@ +# Anthropology Categories (`anthroEdit`) + +| | | +|---|---| +| **Tool id** | `anthroEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Anthropology Categories (`anthroEdit`) (Sena 3, Legacy)](./images/anthro-edit-01.png) + +## What it is +Edit the 'Anthropology Categories' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/chartmark-edit.md b/Docs/migration/lists/chartmark-edit.md new file mode 100644 index 0000000000..a827fc6696 --- /dev/null +++ b/Docs/migration/lists/chartmark-edit.md @@ -0,0 +1,25 @@ +# Text Chart Markers (`chartmarkEdit`) + +| | | +|---|---| +| **Tool id** | `chartmarkEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Text Chart Markers (`chartmarkEdit`) (Sena 3, Legacy)](./images/chartmark-edit-01.png) + +## What it is +Edit the 'Text Chart Markers' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/charttemp-edit.md b/Docs/migration/lists/charttemp-edit.md new file mode 100644 index 0000000000..ca54f404bf --- /dev/null +++ b/Docs/migration/lists/charttemp-edit.md @@ -0,0 +1,25 @@ +# Text Constituent Chart Templates (`charttempEdit`) + +| | | +|---|---| +| **Tool id** | `charttempEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Text Constituent Chart Templates (`charttempEdit`) (Sena 3, Legacy)](./images/charttemp-edit-01.png) + +## What it is +Edit the 'Text Constituent Chart Templates' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/complex-entry-type-edit.md b/Docs/migration/lists/complex-entry-type-edit.md new file mode 100644 index 0000000000..96019dc234 --- /dev/null +++ b/Docs/migration/lists/complex-entry-type-edit.md @@ -0,0 +1,25 @@ +# Complex Form Types (`complexEntryTypeEdit`) + +| | | +|---|---| +| **Tool id** | `complexEntryTypeEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Complex Form Types (`complexEntryTypeEdit`) (Sena 3, Legacy)](./images/complex-entry-type-edit-01.png) + +## What it is +Edit the 'Complex Form Types' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/confidence-edit.md b/Docs/migration/lists/confidence-edit.md new file mode 100644 index 0000000000..1a1a8c90cf --- /dev/null +++ b/Docs/migration/lists/confidence-edit.md @@ -0,0 +1,25 @@ +# Confidence Levels (`confidenceEdit`) + +| | | +|---|---| +| **Tool id** | `confidenceEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Confidence Levels (`confidenceEdit`) (Sena 3, Legacy)](./images/confidence-edit-01.png) + +## What it is +Edit the 'Confidence Levels' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/dialects-list-edit.md b/Docs/migration/lists/dialects-list-edit.md new file mode 100644 index 0000000000..89e6e9ecf0 --- /dev/null +++ b/Docs/migration/lists/dialects-list-edit.md @@ -0,0 +1,25 @@ +# Dialect Labels (`dialectsListEdit`) + +| | | +|---|---| +| **Tool id** | `dialectsListEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Dialect Labels (`dialectsListEdit`) (Sena 3, Legacy)](./images/dialects-list-edit-01.png) + +## What it is +Edit the 'Dialect Labels' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/domain-type-edit.md b/Docs/migration/lists/domain-type-edit.md new file mode 100644 index 0000000000..3e02b30a4e --- /dev/null +++ b/Docs/migration/lists/domain-type-edit.md @@ -0,0 +1,25 @@ +# Academic Domains (`domainTypeEdit`) + +| | | +|---|---| +| **Tool id** | `domainTypeEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Academic Domains (`domainTypeEdit`) (Sena 3, Legacy)](./images/domain-type-edit-01.png) + +## What it is +Edit the 'Academic Domains' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/education-edit.md b/Docs/migration/lists/education-edit.md new file mode 100644 index 0000000000..ff6f163ae0 --- /dev/null +++ b/Docs/migration/lists/education-edit.md @@ -0,0 +1,25 @@ +# Education Levels (`educationEdit`) + +| | | +|---|---| +| **Tool id** | `educationEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Education Levels (`educationEdit`) (Sena 3, Legacy)](./images/education-edit-01.png) + +## What it is +Edit the 'Education Levels' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/ext-note-type-edit.md b/Docs/migration/lists/ext-note-type-edit.md new file mode 100644 index 0000000000..75abc0e971 --- /dev/null +++ b/Docs/migration/lists/ext-note-type-edit.md @@ -0,0 +1,25 @@ +# Extended Note Types (`extNoteTypeEdit`) + +| | | +|---|---| +| **Tool id** | `extNoteTypeEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Extended Note Types (`extNoteTypeEdit`) (Sena 3, Legacy)](./images/ext-note-type-edit-01.png) + +## What it is +Edit the 'Extended Note Types' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/feature-types-advanced-edit.md b/Docs/migration/lists/feature-types-advanced-edit.md new file mode 100644 index 0000000000..43081e1cf4 --- /dev/null +++ b/Docs/migration/lists/feature-types-advanced-edit.md @@ -0,0 +1,25 @@ +# Feature Types (`featureTypesAdvancedEdit`) + +| | | +|---|---| +| **Tool id** | `featureTypesAdvancedEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Feature Types (`featureTypesAdvancedEdit`) (Sena 3, Legacy)](./images/feature-types-advanced-edit-01.png) + +## What it is +Edit the 'Feature Types' CmPossibility list. + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView detail over the FsFeatStrucType list. +- CmPossibility-style list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/genres-edit.md b/Docs/migration/lists/genres-edit.md new file mode 100644 index 0000000000..56c3b9d59c --- /dev/null +++ b/Docs/migration/lists/genres-edit.md @@ -0,0 +1,25 @@ +# Genres (`genresEdit`) + +| | | +|---|---| +| **Tool id** | `genresEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Genres (`genresEdit`) (Sena 3, Legacy)](./images/genres-edit-01.png) + +## What it is +Edit the 'Genres' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/images/affix-category-edit-01.png b/Docs/migration/lists/images/affix-category-edit-01.png new file mode 100644 index 0000000000..fbb77f0276 Binary files /dev/null and b/Docs/migration/lists/images/affix-category-edit-01.png differ diff --git a/Docs/migration/lists/images/annotation-def-edit-01.png b/Docs/migration/lists/images/annotation-def-edit-01.png new file mode 100644 index 0000000000..fbb77f0276 Binary files /dev/null and b/Docs/migration/lists/images/annotation-def-edit-01.png differ diff --git a/Docs/migration/lists/images/anthro-edit-01.png b/Docs/migration/lists/images/anthro-edit-01.png new file mode 100644 index 0000000000..f6868f74e8 Binary files /dev/null and b/Docs/migration/lists/images/anthro-edit-01.png differ diff --git a/Docs/migration/lists/images/chartmark-edit-01.png b/Docs/migration/lists/images/chartmark-edit-01.png new file mode 100644 index 0000000000..94a7c10568 Binary files /dev/null and b/Docs/migration/lists/images/chartmark-edit-01.png differ diff --git a/Docs/migration/lists/images/charttemp-edit-01.png b/Docs/migration/lists/images/charttemp-edit-01.png new file mode 100644 index 0000000000..d19cc916cd Binary files /dev/null and b/Docs/migration/lists/images/charttemp-edit-01.png differ diff --git a/Docs/migration/lists/images/complex-entry-type-edit-01.png b/Docs/migration/lists/images/complex-entry-type-edit-01.png new file mode 100644 index 0000000000..45d30d7e93 Binary files /dev/null and b/Docs/migration/lists/images/complex-entry-type-edit-01.png differ diff --git a/Docs/migration/lists/images/confidence-edit-01.png b/Docs/migration/lists/images/confidence-edit-01.png new file mode 100644 index 0000000000..caad767cd6 Binary files /dev/null and b/Docs/migration/lists/images/confidence-edit-01.png differ diff --git a/Docs/migration/lists/images/dialects-list-edit-01.png b/Docs/migration/lists/images/dialects-list-edit-01.png new file mode 100644 index 0000000000..6d2b8912da Binary files /dev/null and b/Docs/migration/lists/images/dialects-list-edit-01.png differ diff --git a/Docs/migration/lists/images/domain-type-edit-01.png b/Docs/migration/lists/images/domain-type-edit-01.png new file mode 100644 index 0000000000..e04e26b73b Binary files /dev/null and b/Docs/migration/lists/images/domain-type-edit-01.png differ diff --git a/Docs/migration/lists/images/education-edit-01.png b/Docs/migration/lists/images/education-edit-01.png new file mode 100644 index 0000000000..7482d0c566 Binary files /dev/null and b/Docs/migration/lists/images/education-edit-01.png differ diff --git a/Docs/migration/lists/images/ext-note-type-edit-01.png b/Docs/migration/lists/images/ext-note-type-edit-01.png new file mode 100644 index 0000000000..05038c8787 Binary files /dev/null and b/Docs/migration/lists/images/ext-note-type-edit-01.png differ diff --git a/Docs/migration/lists/images/feature-types-advanced-edit-01.png b/Docs/migration/lists/images/feature-types-advanced-edit-01.png new file mode 100644 index 0000000000..bbd86eed97 Binary files /dev/null and b/Docs/migration/lists/images/feature-types-advanced-edit-01.png differ diff --git a/Docs/migration/lists/images/genres-edit-01.png b/Docs/migration/lists/images/genres-edit-01.png new file mode 100644 index 0000000000..4b8c76faf6 Binary files /dev/null and b/Docs/migration/lists/images/genres-edit-01.png differ diff --git a/Docs/migration/lists/images/languages-list-edit-01.png b/Docs/migration/lists/images/languages-list-edit-01.png new file mode 100644 index 0000000000..62a75a1a06 Binary files /dev/null and b/Docs/migration/lists/images/languages-list-edit-01.png differ diff --git a/Docs/migration/lists/images/lex-ref-edit-01.png b/Docs/migration/lists/images/lex-ref-edit-01.png new file mode 100644 index 0000000000..24acdd93ad Binary files /dev/null and b/Docs/migration/lists/images/lex-ref-edit-01.png differ diff --git a/Docs/migration/lists/images/locations-edit-01.png b/Docs/migration/lists/images/locations-edit-01.png new file mode 100644 index 0000000000..2516d80022 Binary files /dev/null and b/Docs/migration/lists/images/locations-edit-01.png differ diff --git a/Docs/migration/lists/images/morph-type-edit-01.png b/Docs/migration/lists/images/morph-type-edit-01.png new file mode 100644 index 0000000000..443ad7a3d9 Binary files /dev/null and b/Docs/migration/lists/images/morph-type-edit-01.png differ diff --git a/Docs/migration/lists/images/people-edit-01.png b/Docs/migration/lists/images/people-edit-01.png new file mode 100644 index 0000000000..9c8ee5c11d Binary files /dev/null and b/Docs/migration/lists/images/people-edit-01.png differ diff --git a/Docs/migration/lists/images/positions-edit-01.png b/Docs/migration/lists/images/positions-edit-01.png new file mode 100644 index 0000000000..22d06e7de6 Binary files /dev/null and b/Docs/migration/lists/images/positions-edit-01.png differ diff --git a/Docs/migration/lists/images/publications-edit-01.png b/Docs/migration/lists/images/publications-edit-01.png new file mode 100644 index 0000000000..8fb8ac6774 Binary files /dev/null and b/Docs/migration/lists/images/publications-edit-01.png differ diff --git a/Docs/migration/lists/images/rec-type-edit-01.png b/Docs/migration/lists/images/rec-type-edit-01.png new file mode 100644 index 0000000000..58932403f5 Binary files /dev/null and b/Docs/migration/lists/images/rec-type-edit-01.png differ diff --git a/Docs/migration/lists/images/restrictions-edit-01.png b/Docs/migration/lists/images/restrictions-edit-01.png new file mode 100644 index 0000000000..aca53a2c99 Binary files /dev/null and b/Docs/migration/lists/images/restrictions-edit-01.png differ diff --git a/Docs/migration/lists/images/reversal-tool-reversal-index-pos-01.png b/Docs/migration/lists/images/reversal-tool-reversal-index-pos-01.png new file mode 100644 index 0000000000..242478ba29 Binary files /dev/null and b/Docs/migration/lists/images/reversal-tool-reversal-index-pos-01.png differ diff --git a/Docs/migration/lists/images/role-edit-01.png b/Docs/migration/lists/images/role-edit-01.png new file mode 100644 index 0000000000..2cca04edba Binary files /dev/null and b/Docs/migration/lists/images/role-edit-01.png differ diff --git a/Docs/migration/lists/images/semantic-domain-edit-01.png b/Docs/migration/lists/images/semantic-domain-edit-01.png new file mode 100644 index 0000000000..a50cc4d203 Binary files /dev/null and b/Docs/migration/lists/images/semantic-domain-edit-01.png differ diff --git a/Docs/migration/lists/images/sense-status-edit-01.png b/Docs/migration/lists/images/sense-status-edit-01.png new file mode 100644 index 0000000000..8adfdccfc2 Binary files /dev/null and b/Docs/migration/lists/images/sense-status-edit-01.png differ diff --git a/Docs/migration/lists/images/sense-type-edit-01.png b/Docs/migration/lists/images/sense-type-edit-01.png new file mode 100644 index 0000000000..cf05dbaf62 Binary files /dev/null and b/Docs/migration/lists/images/sense-type-edit-01.png differ diff --git a/Docs/migration/lists/images/status-edit-01.png b/Docs/migration/lists/images/status-edit-01.png new file mode 100644 index 0000000000..c3684c2278 Binary files /dev/null and b/Docs/migration/lists/images/status-edit-01.png differ diff --git a/Docs/migration/lists/images/text-markup-tags-edit-01.png b/Docs/migration/lists/images/text-markup-tags-edit-01.png new file mode 100644 index 0000000000..335cdc8075 Binary files /dev/null and b/Docs/migration/lists/images/text-markup-tags-edit-01.png differ diff --git a/Docs/migration/lists/images/time-of-day-edit-01.png b/Docs/migration/lists/images/time-of-day-edit-01.png new file mode 100644 index 0000000000..19e95362d2 Binary files /dev/null and b/Docs/migration/lists/images/time-of-day-edit-01.png differ diff --git a/Docs/migration/lists/images/translation-type-edit-01.png b/Docs/migration/lists/images/translation-type-edit-01.png new file mode 100644 index 0000000000..26b393b2cd Binary files /dev/null and b/Docs/migration/lists/images/translation-type-edit-01.png differ diff --git a/Docs/migration/lists/images/usage-type-edit-01.png b/Docs/migration/lists/images/usage-type-edit-01.png new file mode 100644 index 0000000000..d3ac321692 Binary files /dev/null and b/Docs/migration/lists/images/usage-type-edit-01.png differ diff --git a/Docs/migration/lists/images/variant-entry-type-edit-01.png b/Docs/migration/lists/images/variant-entry-type-edit-01.png new file mode 100644 index 0000000000..9d86fdd06b Binary files /dev/null and b/Docs/migration/lists/images/variant-entry-type-edit-01.png differ diff --git a/Docs/migration/lists/languages-list-edit.md b/Docs/migration/lists/languages-list-edit.md new file mode 100644 index 0000000000..b898660dac --- /dev/null +++ b/Docs/migration/lists/languages-list-edit.md @@ -0,0 +1,25 @@ +# Languages (`languagesListEdit`) + +| | | +|---|---| +| **Tool id** | `languagesListEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Languages (`languagesListEdit`) (Sena 3, Legacy)](./images/languages-list-edit-01.png) + +## What it is +Edit the 'Languages' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/lex-ref-edit.md b/Docs/migration/lists/lex-ref-edit.md new file mode 100644 index 0000000000..40e5e20060 --- /dev/null +++ b/Docs/migration/lists/lex-ref-edit.md @@ -0,0 +1,25 @@ +# Lexical Relations (`lexRefEdit`) + +| | | +|---|---| +| **Tool id** | `lexRefEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Lexical Relations (`lexRefEdit`) (Sena 3, Legacy)](./images/lex-ref-edit-01.png) + +## What it is +Edit the 'Lexical Relations' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/locations-edit.md b/Docs/migration/lists/locations-edit.md new file mode 100644 index 0000000000..2872974e39 --- /dev/null +++ b/Docs/migration/lists/locations-edit.md @@ -0,0 +1,25 @@ +# Locations (`locationsEdit`) + +| | | +|---|---| +| **Tool id** | `locationsEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Locations (`locationsEdit`) (Sena 3, Legacy)](./images/locations-edit-01.png) + +## What it is +Edit the 'Locations' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/morph-type-edit.md b/Docs/migration/lists/morph-type-edit.md new file mode 100644 index 0000000000..405af28141 --- /dev/null +++ b/Docs/migration/lists/morph-type-edit.md @@ -0,0 +1,25 @@ +# Morpheme Types (`morphTypeEdit`) + +| | | +|---|---| +| **Tool id** | `morphTypeEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Morpheme Types (`morphTypeEdit`) (Sena 3, Legacy)](./images/morph-type-edit-01.png) + +## What it is +Edit the 'Morpheme Types' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/people-edit.md b/Docs/migration/lists/people-edit.md new file mode 100644 index 0000000000..61411fb1c9 --- /dev/null +++ b/Docs/migration/lists/people-edit.md @@ -0,0 +1,25 @@ +# People (`peopleEdit`) + +| | | +|---|---| +| **Tool id** | `peopleEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![People (`peopleEdit`) (Sena 3, Legacy)](./images/people-edit-01.png) + +## What it is +Edit the 'People' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/positions-edit.md b/Docs/migration/lists/positions-edit.md new file mode 100644 index 0000000000..b522814f58 --- /dev/null +++ b/Docs/migration/lists/positions-edit.md @@ -0,0 +1,25 @@ +# Positions (`positionsEdit`) + +| | | +|---|---| +| **Tool id** | `positionsEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Positions (`positionsEdit`) (Sena 3, Legacy)](./images/positions-edit-01.png) + +## What it is +Edit the 'Positions' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/publications-edit.md b/Docs/migration/lists/publications-edit.md new file mode 100644 index 0000000000..039effc442 --- /dev/null +++ b/Docs/migration/lists/publications-edit.md @@ -0,0 +1,25 @@ +# Publications (`publicationsEdit`) + +| | | +|---|---| +| **Tool id** | `publicationsEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Publications (`publicationsEdit`) (Sena 3, Legacy)](./images/publications-edit-01.png) + +## What it is +Edit the 'Publications' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/rec-type-edit.md b/Docs/migration/lists/rec-type-edit.md new file mode 100644 index 0000000000..dc4957568b --- /dev/null +++ b/Docs/migration/lists/rec-type-edit.md @@ -0,0 +1,25 @@ +# Notebook Record Types (`recTypeEdit`) + +| | | +|---|---| +| **Tool id** | `recTypeEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Notebook Record Types (`recTypeEdit`) (Sena 3, Legacy)](./images/rec-type-edit-01.png) + +## What it is +Edit the 'Notebook Record Types' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/restrictions-edit.md b/Docs/migration/lists/restrictions-edit.md new file mode 100644 index 0000000000..2b6149e1ae --- /dev/null +++ b/Docs/migration/lists/restrictions-edit.md @@ -0,0 +1,25 @@ +# Restrictions (`restrictionsEdit`) + +| | | +|---|---| +| **Tool id** | `restrictionsEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Restrictions (`restrictionsEdit`) (Sena 3, Legacy)](./images/restrictions-edit-01.png) + +## What it is +Edit the 'Restrictions' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/reversal-tool-reversal-index-pos.md b/Docs/migration/lists/reversal-tool-reversal-index-pos.md new file mode 100644 index 0000000000..e6aa64970e --- /dev/null +++ b/Docs/migration/lists/reversal-tool-reversal-index-pos.md @@ -0,0 +1,25 @@ +# Reversal Index Categories (`reversalToolReversalIndexPOS`) + +| | | +|---|---| +| **Tool id** | `reversalToolReversalIndexPOS` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Reversal Index Categories (`reversalToolReversalIndexPOS`) (Sena 3, Legacy)](./images/reversal-tool-reversal-index-pos-01.png) + +## What it is +Edit the 'Reversal Index Categories' CmPossibility list. + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView detail over the reversal-index PartOfSpeech possibility list. +- CmPossibility list editor (reversal categories) -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/role-edit.md b/Docs/migration/lists/role-edit.md new file mode 100644 index 0000000000..ed32d16cc8 --- /dev/null +++ b/Docs/migration/lists/role-edit.md @@ -0,0 +1,25 @@ +# Roles (`roleEdit`) + +| | | +|---|---| +| **Tool id** | `roleEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Roles (`roleEdit`) (Sena 3, Legacy)](./images/role-edit-01.png) + +## What it is +Edit the 'Roles' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/semantic-domain-edit.md b/Docs/migration/lists/semantic-domain-edit.md new file mode 100644 index 0000000000..d166d4fe82 --- /dev/null +++ b/Docs/migration/lists/semantic-domain-edit.md @@ -0,0 +1,25 @@ +# Semantic Domains (`semanticDomainEdit`) + +| | | +|---|---| +| **Tool id** | `semanticDomainEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Semantic Domains (`semanticDomainEdit`) (Sena 3, Legacy)](./images/semantic-domain-edit-01.png) + +## What it is +Edit the 'Semantic Domains' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/sense-status-edit.md b/Docs/migration/lists/sense-status-edit.md new file mode 100644 index 0000000000..3912b64d0a --- /dev/null +++ b/Docs/migration/lists/sense-status-edit.md @@ -0,0 +1,25 @@ +# Sense Status (`senseStatusEdit`) + +| | | +|---|---| +| **Tool id** | `senseStatusEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Sense Status (`senseStatusEdit`) (Sena 3, Legacy)](./images/sense-status-edit-01.png) + +## What it is +Edit the 'Sense Status' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/sense-type-edit.md b/Docs/migration/lists/sense-type-edit.md new file mode 100644 index 0000000000..ce80037461 --- /dev/null +++ b/Docs/migration/lists/sense-type-edit.md @@ -0,0 +1,25 @@ +# Sense Types (`senseTypeEdit`) + +| | | +|---|---| +| **Tool id** | `senseTypeEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Sense Types (`senseTypeEdit`) (Sena 3, Legacy)](./images/sense-type-edit-01.png) + +## What it is +Edit the 'Sense Types' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/status-edit.md b/Docs/migration/lists/status-edit.md new file mode 100644 index 0000000000..9ff0dc202c --- /dev/null +++ b/Docs/migration/lists/status-edit.md @@ -0,0 +1,25 @@ +# Status (`statusEdit`) + +| | | +|---|---| +| **Tool id** | `statusEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Status (`statusEdit`) (Sena 3, Legacy)](./images/status-edit-01.png) + +## What it is +Edit the 'Status' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/text-markup-tags-edit.md b/Docs/migration/lists/text-markup-tags-edit.md new file mode 100644 index 0000000000..d1932443f4 --- /dev/null +++ b/Docs/migration/lists/text-markup-tags-edit.md @@ -0,0 +1,25 @@ +# Text Markup Tags (`textMarkupTagsEdit`) + +| | | +|---|---| +| **Tool id** | `textMarkupTagsEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Text Markup Tags (`textMarkupTagsEdit`) (Sena 3, Legacy)](./images/text-markup-tags-edit-01.png) + +## What it is +Edit the 'Text Markup Tags' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/time-of-day-edit.md b/Docs/migration/lists/time-of-day-edit.md new file mode 100644 index 0000000000..3397a7762d --- /dev/null +++ b/Docs/migration/lists/time-of-day-edit.md @@ -0,0 +1,25 @@ +# Time Of Day (`timeOfDayEdit`) + +| | | +|---|---| +| **Tool id** | `timeOfDayEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Time Of Day (`timeOfDayEdit`) (Sena 3, Legacy)](./images/time-of-day-edit-01.png) + +## What it is +Edit the 'Time Of Day' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/translation-type-edit.md b/Docs/migration/lists/translation-type-edit.md new file mode 100644 index 0000000000..c8200b73c7 --- /dev/null +++ b/Docs/migration/lists/translation-type-edit.md @@ -0,0 +1,25 @@ +# Translation Types (`translationTypeEdit`) + +| | | +|---|---| +| **Tool id** | `translationTypeEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Translation Types (`translationTypeEdit`) (Sena 3, Legacy)](./images/translation-type-edit-01.png) + +## What it is +Edit the 'Translation Types' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/usage-type-edit.md b/Docs/migration/lists/usage-type-edit.md new file mode 100644 index 0000000000..59b31a234f --- /dev/null +++ b/Docs/migration/lists/usage-type-edit.md @@ -0,0 +1,25 @@ +# Usages (`usageTypeEdit`) + +| | | +|---|---| +| **Tool id** | `usageTypeEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Usages (`usageTypeEdit`) (Sena 3, Legacy)](./images/usage-type-edit-01.png) + +## What it is +Edit the 'Usages' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/lists/variant-entry-type-edit.md b/Docs/migration/lists/variant-entry-type-edit.md new file mode 100644 index 0000000000..5dbd9e324e --- /dev/null +++ b/Docs/migration/lists/variant-entry-type-edit.md @@ -0,0 +1,25 @@ +# Variant Types (`variantEntryTypeEdit`) + +| | | +|---|---| +| **Tool id** | `variantEntryTypeEdit` | +| **Area** | Lists | +| **Type** | list-editor | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | TREE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | tree -> ChooserDialog | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Variant Types (`variantEntryTypeEdit`) (Sena 3, Legacy)](./images/variant-entry-type-edit-01.png) + +## What it is +Edit the 'Variant Types' CmPossibility list. + +## Notes / gotchas +- RecordEditView DataTree detail over a CmPossibility list, with the list hierarchy in the tree bar. +- CmPossibility list editor -> Primitive TREE; Canonical ChooserDialog. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/msa-creator.md b/Docs/migration/msa-creator.md new file mode 100644 index 0000000000..25d7009007 --- /dev/null +++ b/Docs/migration/msa-creator.md @@ -0,0 +1,50 @@ +# Create Grammatical Info / MSA (legacy `MsaCreatorDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.LexText.Controls.MsaCreatorDlg` (`Src/LexText/LexTextControls/MsaCreatorDlg.cs`) | +| **Area / tool** | Lexicon › sense grammatical-info slice › "Create New Grammatical Info." | +| **Primitive(s)** | owned-control form (FwMsaGroupBox) | +| **Canonical reference** | InsertEntryDialog (owned-control form hosting a FwMsaGroupBox) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/MsaCreatorDialogView.axaml(.cs)` + `MsaCreatorDialogViewModel.cs` @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +Lets the user create (or edit) the grammatical info / morpho-syntactic analysis (MSA) for a sense: +choose the POS, slot, inflection class, and inflection features. Opens from the MSA slice and from the +interlinear MSA popup. Returns a `SandboxGenericMSA` for the caller to apply. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![msa-creator legacy](./images/msa-creator-before.png) | ![msa-creator avalonia](./images/msa-creator-after.png) | +## Behaviour to preserve (parity checklist) +- [ ] Read-only lexical-entry headword shown at top. +- [ ] Read-only senses summary shown on the edit path. +- [ ] Owned `FwMsaGroupBox`: POS choosers, slot picker, inflection-class picker, inflection-feature editor. +- [ ] No OK gate — the box always has a valid grammatical-info class (matches the legacy dialog). +- [ ] Help button shown only when a help topic is available. + +## Migration gotchas +- Owned-control hosting: the stub mounts `FwMsaGroupBox` and stages edits into `InMemoryRegionEditContext`. +- The stub header marks this an "MSA-port Stage 5 replacement for the legacy MsaCreatorDlg in New-UI mode" + and notes "Like the legacy dialog there is NO OK gate (the box always has a valid grammatical-info class)". +- PARITY (from `LcmMsaCreatorDialogLauncher.cs`): "the legacy dialog has two consumers with DIFFERENT apply + branches — MSAPopupTreeManager does `m_sense.SandboxMSA = dlg.SandboxMSA` (find-or-create on a sense) and + MSADlgLauncher does `originalMsa.UpdateOrReplace(dlg.SandboxMSA)` (modify an existing MSA)". +- PARITY (Stage 6 inflection class): "this launcher produces a `SandboxGenericMSA` for the CALLER to apply … + and does NOT itself mutate the model." Re-wiring must preserve the caller-applies contract. +- Stub markers: `// Stage 3 wires the feature dialogs` and `// §19b Stage 3: wire the inline create-feature + / add-value affordances (replacing the deferred no-op)`. + +## Wiring +- Legacy call site(s): `Src/LexText/Lexicon/MSADlgLauncher.cs` — the Legacy branch constructs the WinForms + `MsaCreatorDlg` (the second consumer, `MSAPopupTreeManager`, has its own Legacy construction). +- The Avalonia path branched on `UIMode=New` here before back-out: `Src/LexText/Lexicon/MSADlgLauncher.cs:67` — + `LcmMsaCreatorDialogLauncher.Show(...)` (one of two consumers; the other is `MSAPopupTreeManager`). + Launcher: `LcmMsaCreatorDialogLauncher` (`Src/LexText/LexTextControls/LcmMsaCreatorDialogLauncher.cs`). +- Re-wiring target: `MSADlgLauncher` (and `MSAPopupTreeManager`) re-enter the Avalonia dialog behind + `UIMode=New`; each must keep its own apply branch (see PARITY note above). Legacy keeps `MsaCreatorDlg`. diff --git a/Docs/migration/phase2/native-render/buffered-draw.md b/Docs/migration/phase2/native-render/buffered-draw.md new file mode 100644 index 0000000000..aefac3fb32 --- /dev/null +++ b/Docs/migration/phase2/native-render/buffered-draw.md @@ -0,0 +1,21 @@ +# Buffered draw (`VwDrawRootBuffered`) + +| | | +|---|---| +| **Key files** | `Src/ManagedVwDrawRootBuffered/VwDrawRootBuffered.cs` | +| **Area** | Native-render | +| **Type** | native-render | +| **Primitive** | n/a | +| **State** | legacy | +| **Phase** | 2 | +| **Census stage** | 13 | +| **JIRA** | LT-XXXXX | + +## What it is +Managed double-buffered draw helper for the Views engine: renders the root box to an off-screen buffer to avoid flicker on the WinForms host. + +## Notes / gotchas +- Pure decommission target — Avalonia compositing supplies its own buffering, so there is no reimplementation; deleted at Stage 13. +- Tied to the GDI/WinForms paint model; obsolete once the native render path is gone. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/native-render/datatree-slice-framework.md b/Docs/migration/phase2/native-render/datatree-slice-framework.md new file mode 100644 index 0000000000..96ce5fb516 --- /dev/null +++ b/Docs/migration/phase2/native-render/datatree-slice-framework.md @@ -0,0 +1,22 @@ +# DataTree / Slice framework (`DataTree` / `Slice` / `SliceFactory`) + +| | | +|---|---| +| **Key files** | `Src/Common/Controls/DetailControls/DataTree.cs`, `Slice.cs`, `SliceFactory.cs` (DetailControls, 73 files) | +| **Area** | Native-render | +| **Type** | native-render | +| **Primitive** | n/a | +| **State** | region-path supersedes | +| **Phase** | 2 | +| **Census stage** | superseded by region path | +| **JIRA** | LT-XXXXX | + +## What it is +The legacy detail-editing framework: `DataTree` builds a vertical stack of `Slice` controls from a view definition (`SliceFactory` materializes each field's slice) — the WinForms predecessor of the region/composer path. + +## Notes / gotchas +- Phase-2 **retirement target**: the Avalonia region/composer path (`FullEntryRegionComposer` + `FwAvalonia/Region/`) replaces it, not a 1:1 port. +- Frozen during coexistence — no new slice types; new fields go to the region path. +- Large surface (73 DetailControls files); deleted once all detail editing is on the region path. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/native-render/gecko-pdf-preview.md b/Docs/migration/phase2/native-render/gecko-pdf-preview.md new file mode 100644 index 0000000000..7ee384a117 --- /dev/null +++ b/Docs/migration/phase2/native-render/gecko-pdf-preview.md @@ -0,0 +1,22 @@ +# Gecko / PDF preview (`GeckoWebBrowser` / `GeckofxHtmlToPdf`) + +| | | +|---|---| +| **Key files** | `GeckoWebBrowser` / `GeckofxHtmlToPdf` consumers — `Src/xWorks/XhtmlDocView.cs`, `GeneratedHtmlViewer.cs`, `Src/XCore/HtmlControl.cs` | +| **Area** | Native-render | +| **Type** | native-render | +| **Primitive** | n/a | +| **State** | legacy | +| **Phase** | 2 | +| **Census stage** | 10A | +| **JIRA** | LT-XXXXX | + +## What it is +The embedded Gecko (Geckofx) HTML browser used for dictionary preview and the Gecko-based HTML-to-PDF export path. + +## Notes / gotchas +- Decommission target: replace Gecko with a cross-platform HTML render/preview and a non-Gecko PDF export. +- Geckofx is a heavyweight native dependency and a major cross-platform/packaging blocker — removal is a goal, not just a port. +- Multiple consumers (`XhtmlDocView`, `GeneratedHtmlViewer`, `HtmlControl`); migrate them together. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/native-render/graphite-engine.md b/Docs/migration/phase2/native-render/graphite-engine.md new file mode 100644 index 0000000000..011d096564 --- /dev/null +++ b/Docs/migration/phase2/native-render/graphite-engine.md @@ -0,0 +1,22 @@ +# Graphite engine (`GraphiteEngineClass`) + +| | | +|---|---| +| **Key files** | `Src/views/lib/GraphiteEngine.cpp`, `GraphiteEngine.h` | +| **Area** | Native-render | +| **Type** | native-render | +| **Primitive** | n/a | +| **State** | legacy — removal accepted (doc+notify Awami Nastaliq) | +| **Phase** | 2 | +| **Census stage** | 10B (path) / 13 (delete) | +| **JIRA** | LT-XXXXX | + +## What it is +The native Graphite smart-font shaping engine used by the Views renderer for complex-script layout. + +## Notes / gotchas +- Removal accepted: documented decommission with a heads-up to affected scripts (notify Awami Nastaliq), not a reimplementation. +- The replacement render path (HarfBuzz/platform shaping) is evaluated at Stage 10B; native code deleted at Stage 13. +- Registration-free COM constraint applies while it still ships. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/native-render/managed-views-host.md b/Docs/migration/phase2/native-render/managed-views-host.md new file mode 100644 index 0000000000..be04b0410e --- /dev/null +++ b/Docs/migration/phase2/native-render/managed-views-host.md @@ -0,0 +1,22 @@ +# Managed Views host (`SimpleRootSite` / `RootSite`) + +| | | +|---|---| +| **Key files** | `Src/Common/SimpleRootSite/SimpleRootSite.cs`, `Src/Common/RootSite/RootSite.cs` | +| **Area** | Native-render | +| **Type** | native-render | +| **Primitive** | n/a | +| **State** | legacy | +| **Phase** | 2 | +| **Census stage** | 9/13 | +| **JIRA** | LT-XXXXX | + +## What it is +The managed WinForms control that hosts the native Views engine: pumps paint/keyboard/mouse/IME into `VwRootBox` and surfaces selection back to .NET. + +## Notes / gotchas +- Decommission target paired with the Views engine — replaced at Stage 9, deleted at Stage 13. +- The WinForms `Control` boundary where native rendering meets managed UI; the Avalonia render host supersedes it. +- Registration-free COM bridge to native Views — keep activation via manifest only. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/native-render/views-engine.md b/Docs/migration/phase2/native-render/views-engine.md new file mode 100644 index 0000000000..9e15ef61b7 --- /dev/null +++ b/Docs/migration/phase2/native-render/views-engine.md @@ -0,0 +1,22 @@ +# Views engine (`VwRootBox` / `VwSelection` / `VwTextBoxes`) + +| | | +|---|---| +| **Key files** | `Src/views/VwRootBox.cpp`, `VwSelection.cpp`, `VwTextBoxes.cpp` (+ `Src/views/`) | +| **Area** | Native-render | +| **Type** | native-render | +| **Primitive** | n/a | +| **State** | legacy | +| **Phase** | 2 | +| **Census stage** | 9 (replace) / 13 (delete) | +| **JIRA** | LT-XXXXX | + +## What it is +The native C++ rendering/layout/selection engine: `VwRootBox` builds and lays out the view box tree, `VwSelection` models the insertion point/range, `VwTextBoxes` renders styled multi-writing-system text. + +## Notes / gotchas +- Decommission target: the Avalonia presentation IR + render path replaces it at Stage 9; native code deleted at Stage 13. +- Registration-free COM — consumed via the manifest; no global COM registration. +- Carries deep correctness (BiDi, IME, selection semantics) — parity is the hard part; do not delete until consumers are off it. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/shell/app-lifetime-startup.md b/Docs/migration/phase2/shell/app-lifetime-startup.md new file mode 100644 index 0000000000..260f90760a --- /dev/null +++ b/Docs/migration/phase2/shell/app-lifetime-startup.md @@ -0,0 +1,22 @@ +# App lifetime / startup (`FieldWorks.cs`) + +| | | +|---|---| +| **Key files** | `Src/Common/FieldWorks/FieldWorks.cs` (+ `Src/Common/Framework/FwApp.cs`, `IFieldWorksManager.cs`) | +| **Area** | Shell | +| **Type** | shell | +| **Primitive** | n/a | +| **State** | legacy | +| **Phase** | 2 | +| **Census stage** | 11a | +| **JIRA** | LT-XXXXX | + +## What it is +Process entry point and application lifetime manager: single-instance/remoting bootstrap, project open/close, app-to-app handoff, and shutdown. + +## Notes / gotchas +- Reimplement as the cross-platform host bootstrap; today it is WinForms `Application.Run`-centric. +- Registration-free COM constraint — startup activates native Views/COM via the manifest; do not register globally. +- Coordinates with `FwApp`/`IFieldWorksManager`; deleted at end of coexistence once the net10 shell owns lifetime. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/shell/main-window.md b/Docs/migration/phase2/shell/main-window.md new file mode 100644 index 0000000000..1cb8df5a99 --- /dev/null +++ b/Docs/migration/phase2/shell/main-window.md @@ -0,0 +1,22 @@ +# Main window (`xWindow.cs`) + +| | | +|---|---| +| **Key files** | `Src/XCore/xWindow.cs` (2,498 lines, `: Form`) (+ `Src/xWorks/FwXWindow.cs`) | +| **Area** | Shell | +| **Type** | shell | +| **Primitive** | n/a | +| **State** | legacy | +| **Phase** | 2 | +| **Census stage** | 11a | +| **JIRA** | LT-XXXXX | + +## What it is +The top-level XCore application window (`: Form`): hosts the mediator/property table, menu/toolbar adapters, sidebar, and the content pane tree; `FwXWindow` is the FieldWorks specialization. + +## Notes / gotchas +- Reimplement as the Avalonia main window; the 2,498-line `Form` subclass is the central WinForms coupling point. +- Owns the mediator/PropertyTable instance and broadcasts content-switching — keep the bridge seam working during coexistence. +- Deleted at end of coexistence once the net10 shell window is default. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/shell/mediator-propertytable.md b/Docs/migration/phase2/shell/mediator-propertytable.md new file mode 100644 index 0000000000..46602e534a --- /dev/null +++ b/Docs/migration/phase2/shell/mediator-propertytable.md @@ -0,0 +1,22 @@ +# Mediator / PropertyTable (`Mediator.cs` / `PropertyTable.cs`) + +| | | +|---|---| +| **Key files** | `Src/XCore/xCoreInterfaces/Mediator.cs`, `PropertyTable.cs` (+ `ReadOnlyPropertyTable.cs`) | +| **Area** | Framework | +| **Type** | framework | +| **Primitive** | n/a | +| **State** | legacy (bridge seam exists) | +| **Phase** | 2 | +| **Census stage** | 11c (bridge: 2.3) | +| **JIRA** | LT-XXXXX | + +## What it is +The XCore message bus (`Mediator`) and global state store (`PropertyTable`) that route commands, colleague notifications, and shared settings across the shell and tools. + +## Notes / gotchas +- A Phase-2.3 bridge seam already exists so Avalonia surfaces can read/write the table during coexistence. +- Reimplement/replace incrementally; pervasive dependency — almost every tool colleague subscribes here. +- Removal is late (after consumers migrate); keep the bridge faithful until then. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/shell/menus-toolbars-statusbar.md b/Docs/migration/phase2/shell/menus-toolbars-statusbar.md new file mode 100644 index 0000000000..6863d97dbc --- /dev/null +++ b/Docs/migration/phase2/shell/menus-toolbars-statusbar.md @@ -0,0 +1,22 @@ +# Menus / toolbars / status bar (`FlexUIAdapter` / `Inventory` / `Main.xml`) + +| | | +|---|---| +| **Key files** | `Src/XCore/FlexUIAdapter/` (`MenuAdapter.cs`, `BarAdapterBase.cs`), `Src/XCore/Inventory.cs`, `DistFiles/Language Explorer/Configuration/Main.xml` | +| **Area** | Shell | +| **Type** | shell | +| **Primitive** | n/a | +| **State** | legacy | +| **Phase** | 2 | +| **Census stage** | 11b/11f | +| **JIRA** | LT-XXXXX | + +## What it is +The XML-driven command surface: `Main.xml` declares menus/toolbars/commands, `Inventory` loads and merges the XML, and `FlexUIAdapter` realizes it as WinForms menu/toolbar/status-bar widgets. + +## Notes / gotchas +- Reimplement the adapter layer for Avalonia menus/toolbars; keep the `Main.xml`/`Inventory` declarative model so commands route unchanged. +- Command enable/visibility flows through mediator colleague dispatch — preserve during coexistence. +- Adapters deleted at end of coexistence; `Main.xml` config likely survives the shell swap. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/shell/panes-splitters.md b/Docs/migration/phase2/shell/panes-splitters.md new file mode 100644 index 0000000000..c9ac0dedb9 --- /dev/null +++ b/Docs/migration/phase2/shell/panes-splitters.md @@ -0,0 +1,22 @@ +# Panes / splitters (`CollapsingSplitContainer` / `MultiPane` / `PaneBarContainer`) + +| | | +|---|---| +| **Key files** | `Src/XCore/CollapsingSplitContainer.cs` (+ `MultiPane.cs`, `PaneBarContainer.cs`) | +| **Area** | Shell | +| **Type** | shell | +| **Primitive** | n/a | +| **State** | legacy | +| **Phase** | 2 | +| **Census stage** | 11d | +| **JIRA** | LT-XXXXX | + +## What it is +The content-pane layout primitives: collapsible split containers, multi-pane hosting, and the pane-bar container that frames each tool's editing surface. + +## Notes / gotchas +- Reimplement as Avalonia layout (Grid/GridSplitter + pane-bar control); persisted split sizes live in the PropertyTable. +- These host the region/composer content during coexistence — the Avalonia host plugs in here before they retire. +- Deleted at end of coexistence with the legacy shell window. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/phase2/shell/sidebar-navigation.md b/Docs/migration/phase2/shell/sidebar-navigation.md new file mode 100644 index 0000000000..e248acfa84 --- /dev/null +++ b/Docs/migration/phase2/shell/sidebar-navigation.md @@ -0,0 +1,22 @@ +# Sidebar / navigation (`SilSidePane` / `OutlookBar`) + +| | | +|---|---| +| **Key files** | `Src/XCore/SilSidePane/OutlookBar.cs` (+ `OutlookBarButton.cs`, the `SilSidePane/` controls) | +| **Area** | Shell | +| **Type** | shell | +| **Primitive** | n/a | +| **State** | legacy | +| **Phase** | 2 | +| **Census stage** | 11d | +| **JIRA** | LT-XXXXX | + +## What it is +The left-hand area/tool navigation sidebar: an Outlook-bar-style stack of buttons (`SilSidePane`) driving the active area and tool selection. + +## Notes / gotchas +- Reimplement as an Avalonia navigation control; selection state flows through the mediator/PropertyTable. +- Custom-drawn WinForms `OutlookBar` widgetry; no direct Avalonia equivalent — rebuild rather than wrap. +- Deleted at end of coexistence with the legacy shell window. + +> Stub. Phase-2 (net10/shell/cross-platform). Deepen when the Phase-2 stage that owns it is scheduled. diff --git a/Docs/migration/picture-properties.md b/Docs/migration/picture-properties.md new file mode 100644 index 0000000000..3abd271f34 --- /dev/null +++ b/Docs/migration/picture-properties.md @@ -0,0 +1,47 @@ +# Picture Properties (legacy `PicturePropertiesDialog`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.PicturePropertiesDialog` (`Src/FwCoreDlgs/PicturePropertiesDialog.cs`) | +| **Area / tool** | Lexicon › sense picture region › insert / edit picture | +| **Primitive(s)** | plain-form (metadata text fields + image file picker / media) | +| **Canonical reference** | InsertEntryDialog (closest kept canonical for a plain-form with text fields + a picker affordance) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/PicturePropertiesDialogView.cs` (code-only, no `.axaml`) + `PicturePropertiesDialogViewModel.cs` @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +Lets the user choose an image file and edit a picture's metadata (caption, description, license, creator). +Opens when inserting a new picture or editing an existing one in the picture region. Returns the chosen +file plus the edited metadata. + +## What it looks like + +![Picture Properties – initial](./images/picture-properties-01.png) + +## Behaviour to preserve (parity checklist) +- [ ] Metadata text fields: caption, description, license, creator. +- [ ] Image-file display (shows "(no file chosen)" when none). +- [ ] "Choose image…" button triggers an async file picker. +- [ ] OK caption is "Insert" for a new picture, "OK" when editing. +- [ ] OK gated for a new picture on a chosen file (`!_isNew || !string.IsNullOrEmpty(ImageFile)`); for an existing picture the file is optional (metadata-only edits allowed). + +## Migration gotchas +- The Avalonia stub is **code-only** (`PicturePropertiesDialogView.cs`, no `.axaml`) — re-wiring should keep + the programmatic view or convert it deliberately. +- Stub header (`§19d`): "view-model for the Avalonia picture-properties dialog — the parity replacement for + the WinForms `PicturePropertiesDialog`. OK snapshots the edited metadata + file into `Result`; the + launcher's Apply reads it. For a new picture OK is gated on a chosen file … for an existing one the file + is optional." +- Media handling: the file picker is wired through a `ChooseImageRequested` event handled by the launcher + (`LcmRegionMediaServices`). Preserve the linked-files / media-path conventions (see `sil-library-reuse`). + +## Wiring +- Legacy call site(s): the Legacy picture insert/edit path constructs the WinForms `PicturePropertiesDialog` + (`Src/FwCoreDlgs/PicturePropertiesDialog.cs`). +- The Avalonia path branched on `UIMode=New` here before back-out: `Src/xWorks/LcmRegionMediaServices.cs` — + `ShowPictureProperties(...)` (method at line 79; instantiates `PicturePropertiesDialogViewModel` + + `PicturePropertiesDialogView`, wires the picker through `ChooseImageRequested`, shows modal via + `AvaloniaDialogHost.ShowModal`, lines ~82–99). +- Re-wiring target: `LcmRegionMediaServices.ShowPictureProperties` re-enters the Avalonia dialog behind + `UIMode=New`; Legacy keeps `PicturePropertiesDialog`. diff --git a/Docs/migration/special-character.md b/Docs/migration/special-character.md new file mode 100644 index 0000000000..fc5fa11324 --- /dev/null +++ b/Docs/migration/special-character.md @@ -0,0 +1,45 @@ +# Special Character / Unicode Insert (net-new — replaces OS charmap shellout) + +| | | +|---|---| +| **Legacy class** | _None._ The legacy `Format › Special character` command shells out to the OS character map (`charmap.exe` / `gucharmap`); there is no WinForms truth dialog to port. | +| **Area / tool** | Any editable field › Format › Special character (insert a Unicode character) | +| **Primitive(s)** | plain-form + list (filterable character picker) | +| **Canonical reference** | EntryGoDialog (closest kept canonical: a filter text box + a results list) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/SpecialCharacterDialogView.axaml(.cs)` + `SpecialCharacterDialogViewModel.cs` @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +A net-new in-app Avalonia Unicode picker: a filterable list of insertable characters over a gated +Insert + Cancel. It replaces the legacy `Format › Special character` shellout to the OS character map. + +## What it looks like + +![Special Character – initial](./images/special-character-01.png) + +## Behaviour to preserve (parity checklist) +- [ ] Filter text box (watermark prompt) does a case-insensitive search across character name OR code label OR exact character. +- [ ] Character list shows `{Character} {CodeLabel} {Name}` (e.g. "é U+00E9 Latin Small Letter E…"). +- [ ] When the current selection filters out, it is cleared. +- [ ] A curated default set (combining diacritics, IPA, punctuation, arrows — ~25 chars) is shown initially. +- [ ] Insert gated on a selection (`GetValidationErrors` yields `MustSelectMessage` when none selected). +- [ ] In-line validation message shown below the list when nothing is selected. + +## Migration gotchas +- Net-new, so there is no pixel parity baseline against a WinForms dialog — parity is against the OS charmap + *capability* (pick a character and insert it), not its appearance. +- Stub header: "Phase-1 §19g … Unlike the other §19g dialogs there is no WinForms truth dialog to port — + the legacy `Format > Special character` command shells out to the OS character map + (`charmap.exe`/`gucharmap`). This is a net-new in-app Avalonia picker…". +- The default character set is curated in the ViewModel; a future ticket may broaden it to a full Unicode + catalog. + +## Wiring +- **UNWIRED (test-only).** There is no product call site. The only references are the stub definition + (`SpecialCharacterDialogView.axaml.cs`, `SpecialCharacterDialogViewModel.cs`), the tests under + `Src/Common/FwAvaloniaDialogs/FwAvaloniaDialogsTests/`, and a comment in `Src/xWorks/FwXWindow.cs:955` + ("§19g … ships a NET-NEW in-app Avalonia Unicode picker … this legacy OS-charmap shellout is preserved + unchanged"). The legacy charmap shellout is untouched, so there is no call site to revert. +- Re-wiring target (future): the New-UI "insert into field" affordance opens this picker behind `UIMode=New`; + Legacy keeps the OS-charmap shellout. diff --git a/Docs/migration/tools/adhoc-coprohib-edit.md b/Docs/migration/tools/adhoc-coprohib-edit.md new file mode 100644 index 0000000000..54de5d46d2 --- /dev/null +++ b/Docs/migration/tools/adhoc-coprohib-edit.md @@ -0,0 +1,25 @@ +# Ad hoc Rules (`AdhocCoprohibEdit`) + +| | | +|---|---| +| **Tool id** | `AdhocCoprohibEdit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | split-out (own PR) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Ad hoc Rules (`AdhocCoprohibEdit`) (Sena 3, Legacy)](./images/adhoc-coprohib-edit-01.png) + +## What it is +Edit ad hoc co-occurrence prohibition rules. + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView detail. +- Grammar rule editor -> State split-out (own PR). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/analyses.md b/Docs/migration/tools/analyses.md new file mode 100644 index 0000000000..84ed9f5bd0 --- /dev/null +++ b/Docs/migration/tools/analyses.md @@ -0,0 +1,25 @@ +# Word Analyses (`Analyses`) + +| | | +|---|---| +| **Tool id** | `Analyses` | +| **Area** | Texts & Words | +| **Type** | tool-screen | +| **Surface** | interlinear | +| **Primitive** | interlinear | +| **State** | split-out (own PR) | +| **Phase** | 1 | +| **Canonical reference** | n/a | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Word Analyses (`Analyses`) (Sena 3, Legacy)](./images/analyses-01.png) + +## What it is +Word Analyses morph-bundle editor (per-wordform analyses). + +## Notes / gotchas +- Control is RecordEditView (DataTree detail) with WordsEditToolMenuHandler, but the task frames this as the interlinear analyses editor. +- Surface interlinear; State split-out (own PR). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/bulk-edit-entries-or-senses.md b/Docs/migration/tools/bulk-edit-entries-or-senses.md new file mode 100644 index 0000000000..1d66b993a7 --- /dev/null +++ b/Docs/migration/tools/bulk-edit-entries-or-senses.md @@ -0,0 +1,24 @@ +# Bulk Edit Entries (`bulkEditEntriesOrSenses`) + +| | | +|---|---| +| **Tool id** | `bulkEditEntriesOrSenses` | +| **Area** | Lexicon | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Bulk Edit Entries (`bulkEditEntriesOrSenses`) (Sena 3, Legacy)](./images/bulk-edit-entries-or-senses-01.png) + +## What it is +Bulk-edit table over entries or senses. + +## Notes / gotchas +- RecordBrowseView table with bulk-edit bar. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/category-browse.md b/Docs/migration/tools/category-browse.md new file mode 100644 index 0000000000..a55f5583be --- /dev/null +++ b/Docs/migration/tools/category-browse.md @@ -0,0 +1,24 @@ +# Categories Browse (`categoryBrowse`) + +| | | +|---|---| +| **Tool id** | `categoryBrowse` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Categories Browse (`categoryBrowse`) (Sena 3, Legacy)](./images/category-browse-01.png) + +## What it is +Browse table of grammatical categories (Parts of Speech). + +## Notes / gotchas +- RecordBrowseView table. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/complex-concordance.md b/Docs/migration/tools/complex-concordance.md new file mode 100644 index 0000000000..187e62ccc3 --- /dev/null +++ b/Docs/migration/tools/complex-concordance.md @@ -0,0 +1,25 @@ +# Complex Concordance (`complexConcordance`) + +| | | +|---|---| +| **Tool id** | `complexConcordance` | +| **Area** | Texts & Words | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Complex Concordance (`complexConcordance`) (Sena 3, Legacy)](./images/complex-concordance-01.png) + +## What it is +Complex (multi-criteria) concordance search + occurrences table. + +## Notes / gotchas +- Composite (ConcordanceContainer): ComplexConcControl pattern builder + RecordBrowseView results + InterlinMasterNoTitleBar preview. +- Defining surface is the results browse table. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/compound-rule-advanced-edit.md b/Docs/migration/tools/compound-rule-advanced-edit.md new file mode 100644 index 0000000000..743d73cc81 --- /dev/null +++ b/Docs/migration/tools/compound-rule-advanced-edit.md @@ -0,0 +1,25 @@ +# Compound Rules (`compoundRuleAdvancedEdit`) + +| | | +|---|---| +| **Tool id** | `compoundRuleAdvancedEdit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | split-out (own PR) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Compound Rules (`compoundRuleAdvancedEdit`) (Sena 3, Legacy)](./images/compound-rule-advanced-edit-01.png) + +## What it is +Edit morphological compound rules. + +## Notes / gotchas +- Side-by-side: RecordBrowseActiveView list + RecordEditView detail. +- Grammar rule editor -> State split-out (own PR). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/concordance.md b/Docs/migration/tools/concordance.md new file mode 100644 index 0000000000..6bc1d41eaa --- /dev/null +++ b/Docs/migration/tools/concordance.md @@ -0,0 +1,25 @@ +# Concordance (`concordance`) + +| | | +|---|---| +| **Tool id** | `concordance` | +| **Area** | Texts & Words | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Concordance (`concordance`) (Sena 3, Legacy)](./images/concordance-01.png) + +## What it is +Concordance search + occurrences table over the text corpus. + +## Notes / gotchas +- Composite (ConcordanceContainer): ConcordanceControl search pane + RecordBrowseView results + InterlinMasterNoTitleBar preview. +- Defining surface is the results browse table. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/corpus-statistics.md b/Docs/migration/tools/corpus-statistics.md new file mode 100644 index 0000000000..7b0cf3cb79 --- /dev/null +++ b/Docs/migration/tools/corpus-statistics.md @@ -0,0 +1,25 @@ +# Statistics (`corpusStatistics`) + +| | | +|---|---| +| **Tool id** | `corpusStatistics` | +| **Area** | Texts & Words | +| **Type** | tool-screen | +| **Surface** | document (DocView) | +| **Primitive** | n/a | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | n/a | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Statistics (`corpusStatistics`) (Sena 3, Legacy)](./images/corpus-statistics-01.png) + +## What it is +Corpus statistics document. + +## Notes / gotchas +- StatisticsView document view. +- Read-only generated document. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/environment-edit.md b/Docs/migration/tools/environment-edit.md new file mode 100644 index 0000000000..87a318dee4 --- /dev/null +++ b/Docs/migration/tools/environment-edit.md @@ -0,0 +1,25 @@ +# Environments (`EnvironmentEdit`) + +| | | +|---|---| +| **Tool id** | `EnvironmentEdit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | split-out (own PR) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Environments (`EnvironmentEdit`) (Sena 3, Legacy)](./images/environment-edit-01.png) + +## What it is +Edit phonological environments. + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView detail. +- Grammar rule editor -> State split-out (own PR). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/features-advanced-edit.md b/Docs/migration/tools/features-advanced-edit.md new file mode 100644 index 0000000000..b2b78696ae --- /dev/null +++ b/Docs/migration/tools/features-advanced-edit.md @@ -0,0 +1,24 @@ +# Inflection Features (`featuresAdvancedEdit`) + +| | | +|---|---| +| **Tool id** | `featuresAdvancedEdit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Inflection Features (`featuresAdvancedEdit`) (Sena 3, Legacy)](./images/features-advanced-edit-01.png) + +## What it is +Edit inflection feature definitions. + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView detail. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/grammar-sketch.md b/Docs/migration/tools/grammar-sketch.md new file mode 100644 index 0000000000..996cc69c21 --- /dev/null +++ b/Docs/migration/tools/grammar-sketch.md @@ -0,0 +1,25 @@ +# Grammar Sketch (`grammarSketch`) + +| | | +|---|---| +| **Tool id** | `grammarSketch` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | document (DocView) | +| **Primitive** | n/a | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | n/a | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Grammar Sketch (`grammarSketch`) (Sena 3, Legacy)](./images/grammar-sketch-01.png) + +## What it is +Generated grammar sketch document. + +## Notes / gotchas +- GeneratedHtmlViewer document view (GrammarSketchDataRetriever). +- Read-only generated document. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/images/adhoc-coprohib-edit-01.png b/Docs/migration/tools/images/adhoc-coprohib-edit-01.png new file mode 100644 index 0000000000..ff0477098c Binary files /dev/null and b/Docs/migration/tools/images/adhoc-coprohib-edit-01.png differ diff --git a/Docs/migration/tools/images/analyses-01.png b/Docs/migration/tools/images/analyses-01.png new file mode 100644 index 0000000000..b10fc2f619 Binary files /dev/null and b/Docs/migration/tools/images/analyses-01.png differ diff --git a/Docs/migration/tools/images/bulk-edit-entries-or-senses-01.png b/Docs/migration/tools/images/bulk-edit-entries-or-senses-01.png new file mode 100644 index 0000000000..a409dc4bec Binary files /dev/null and b/Docs/migration/tools/images/bulk-edit-entries-or-senses-01.png differ diff --git a/Docs/migration/tools/images/category-browse-01.png b/Docs/migration/tools/images/category-browse-01.png new file mode 100644 index 0000000000..d1aa5a6b15 Binary files /dev/null and b/Docs/migration/tools/images/category-browse-01.png differ diff --git a/Docs/migration/tools/images/complex-concordance-01.png b/Docs/migration/tools/images/complex-concordance-01.png new file mode 100644 index 0000000000..d71edeb062 Binary files /dev/null and b/Docs/migration/tools/images/complex-concordance-01.png differ diff --git a/Docs/migration/tools/images/compound-rule-advanced-edit-01.png b/Docs/migration/tools/images/compound-rule-advanced-edit-01.png new file mode 100644 index 0000000000..43c0911466 Binary files /dev/null and b/Docs/migration/tools/images/compound-rule-advanced-edit-01.png differ diff --git a/Docs/migration/tools/images/concordance-01.png b/Docs/migration/tools/images/concordance-01.png new file mode 100644 index 0000000000..d5d50a05b3 Binary files /dev/null and b/Docs/migration/tools/images/concordance-01.png differ diff --git a/Docs/migration/tools/images/corpus-statistics-01.png b/Docs/migration/tools/images/corpus-statistics-01.png new file mode 100644 index 0000000000..55a9ac456b Binary files /dev/null and b/Docs/migration/tools/images/corpus-statistics-01.png differ diff --git a/Docs/migration/tools/images/environment-edit-01.png b/Docs/migration/tools/images/environment-edit-01.png new file mode 100644 index 0000000000..965ce02e7f Binary files /dev/null and b/Docs/migration/tools/images/environment-edit-01.png differ diff --git a/Docs/migration/tools/images/features-advanced-edit-01.png b/Docs/migration/tools/images/features-advanced-edit-01.png new file mode 100644 index 0000000000..9c1720c218 Binary files /dev/null and b/Docs/migration/tools/images/features-advanced-edit-01.png differ diff --git a/Docs/migration/tools/images/grammar-sketch-01.png b/Docs/migration/tools/images/grammar-sketch-01.png new file mode 100644 index 0000000000..2cbc2ecd5c Binary files /dev/null and b/Docs/migration/tools/images/grammar-sketch-01.png differ diff --git a/Docs/migration/tools/images/interlinear-edit-01.png b/Docs/migration/tools/images/interlinear-edit-01.png new file mode 100644 index 0000000000..e963bd1621 Binary files /dev/null and b/Docs/migration/tools/images/interlinear-edit-01.png differ diff --git a/Docs/migration/tools/images/lexicon-browse-01.png b/Docs/migration/tools/images/lexicon-browse-01.png new file mode 100644 index 0000000000..546e4249bd Binary files /dev/null and b/Docs/migration/tools/images/lexicon-browse-01.png differ diff --git a/Docs/migration/tools/images/lexicon-classified-dictionary-01.png b/Docs/migration/tools/images/lexicon-classified-dictionary-01.png new file mode 100644 index 0000000000..21efa8a4d2 Binary files /dev/null and b/Docs/migration/tools/images/lexicon-classified-dictionary-01.png differ diff --git a/Docs/migration/tools/images/lexicon-dictionary-01.png b/Docs/migration/tools/images/lexicon-dictionary-01.png new file mode 100644 index 0000000000..6cc1e5b7ec Binary files /dev/null and b/Docs/migration/tools/images/lexicon-dictionary-01.png differ diff --git a/Docs/migration/tools/images/lexicon-edit-01.png b/Docs/migration/tools/images/lexicon-edit-01.png new file mode 100644 index 0000000000..8e11f2982a Binary files /dev/null and b/Docs/migration/tools/images/lexicon-edit-01.png differ diff --git a/Docs/migration/tools/images/lexicon-edit-popup-01.png b/Docs/migration/tools/images/lexicon-edit-popup-01.png new file mode 100644 index 0000000000..8e11f2982a Binary files /dev/null and b/Docs/migration/tools/images/lexicon-edit-popup-01.png differ diff --git a/Docs/migration/tools/images/lexicon-problems-01.png b/Docs/migration/tools/images/lexicon-problems-01.png new file mode 100644 index 0000000000..61a810f16a Binary files /dev/null and b/Docs/migration/tools/images/lexicon-problems-01.png differ diff --git a/Docs/migration/tools/images/natural-classedit-01.png b/Docs/migration/tools/images/natural-classedit-01.png new file mode 100644 index 0000000000..f5db8c647c Binary files /dev/null and b/Docs/migration/tools/images/natural-classedit-01.png differ diff --git a/Docs/migration/tools/images/notebook-browse-01.png b/Docs/migration/tools/images/notebook-browse-01.png new file mode 100644 index 0000000000..47812303d0 Binary files /dev/null and b/Docs/migration/tools/images/notebook-browse-01.png differ diff --git a/Docs/migration/tools/images/notebook-document-01.png b/Docs/migration/tools/images/notebook-document-01.png new file mode 100644 index 0000000000..02136782c7 Binary files /dev/null and b/Docs/migration/tools/images/notebook-document-01.png differ diff --git a/Docs/migration/tools/images/notebook-edit-01.png b/Docs/migration/tools/images/notebook-edit-01.png new file mode 100644 index 0000000000..7599a517d4 Binary files /dev/null and b/Docs/migration/tools/images/notebook-edit-01.png differ diff --git a/Docs/migration/tools/images/phoneme-edit-01.png b/Docs/migration/tools/images/phoneme-edit-01.png new file mode 100644 index 0000000000..a1aaf5e68b Binary files /dev/null and b/Docs/migration/tools/images/phoneme-edit-01.png differ diff --git a/Docs/migration/tools/images/phonological-features-advanced-edit-01.png b/Docs/migration/tools/images/phonological-features-advanced-edit-01.png new file mode 100644 index 0000000000..a89b9b5105 Binary files /dev/null and b/Docs/migration/tools/images/phonological-features-advanced-edit-01.png differ diff --git a/Docs/migration/tools/images/phonological-rule-edit-01.png b/Docs/migration/tools/images/phonological-rule-edit-01.png new file mode 100644 index 0000000000..812a430fb8 Binary files /dev/null and b/Docs/migration/tools/images/phonological-rule-edit-01.png differ diff --git a/Docs/migration/tools/images/pos-edit-01.png b/Docs/migration/tools/images/pos-edit-01.png new file mode 100644 index 0000000000..28e9cc33cd Binary files /dev/null and b/Docs/migration/tools/images/pos-edit-01.png differ diff --git a/Docs/migration/tools/images/prod-restrict-edit-01.png b/Docs/migration/tools/images/prod-restrict-edit-01.png new file mode 100644 index 0000000000..4ee929fc3f Binary files /dev/null and b/Docs/migration/tools/images/prod-restrict-edit-01.png differ diff --git a/Docs/migration/tools/images/rapid-data-entry-01.png b/Docs/migration/tools/images/rapid-data-entry-01.png new file mode 100644 index 0000000000..36ad7f6d4f Binary files /dev/null and b/Docs/migration/tools/images/rapid-data-entry-01.png differ diff --git a/Docs/migration/tools/images/reversal-tool-bulk-edit-reversal-entries-01.png b/Docs/migration/tools/images/reversal-tool-bulk-edit-reversal-entries-01.png new file mode 100644 index 0000000000..d78033e744 Binary files /dev/null and b/Docs/migration/tools/images/reversal-tool-bulk-edit-reversal-entries-01.png differ diff --git a/Docs/migration/tools/images/reversal-tool-edit-complete-01.png b/Docs/migration/tools/images/reversal-tool-edit-complete-01.png new file mode 100644 index 0000000000..7bd370d007 Binary files /dev/null and b/Docs/migration/tools/images/reversal-tool-edit-complete-01.png differ diff --git a/Docs/migration/tools/images/spelling-01.png b/Docs/migration/tools/images/spelling-01.png new file mode 100644 index 0000000000..102fdf5d3e Binary files /dev/null and b/Docs/migration/tools/images/spelling-01.png differ diff --git a/Docs/migration/tools/images/tool-bulk-edit-phonemes-01.png b/Docs/migration/tools/images/tool-bulk-edit-phonemes-01.png new file mode 100644 index 0000000000..316a564bd7 Binary files /dev/null and b/Docs/migration/tools/images/tool-bulk-edit-phonemes-01.png differ diff --git a/Docs/migration/tools/images/tool-bulk-edit-wordforms-01.png b/Docs/migration/tools/images/tool-bulk-edit-wordforms-01.png new file mode 100644 index 0000000000..ac63c5f84e Binary files /dev/null and b/Docs/migration/tools/images/tool-bulk-edit-wordforms-01.png differ diff --git a/Docs/migration/tools/images/word-list-concordance-01.png b/Docs/migration/tools/images/word-list-concordance-01.png new file mode 100644 index 0000000000..8702b11560 Binary files /dev/null and b/Docs/migration/tools/images/word-list-concordance-01.png differ diff --git a/Docs/migration/tools/interlinear-edit.md b/Docs/migration/tools/interlinear-edit.md new file mode 100644 index 0000000000..a12aa5334f --- /dev/null +++ b/Docs/migration/tools/interlinear-edit.md @@ -0,0 +1,25 @@ +# Interlinear Texts (`interlinearEdit`) + +| | | +|---|---| +| **Tool id** | `interlinearEdit` | +| **Area** | Texts & Words | +| **Type** | tool-screen | +| **Surface** | interlinear | +| **Primitive** | interlinear | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | n/a | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Interlinear Texts – Analyze tab (Sena 3)](./images/interlinear-edit-01.png) + +## What it is +Interlinear text editor (baseline/word/morph/gloss lines over texts). + +## Notes / gotchas +- InterlinMaster interlinear master control. +- Interlinear surface -> Primitive interlinear; Canonical n/a. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/lexicon-browse.md b/Docs/migration/tools/lexicon-browse.md new file mode 100644 index 0000000000..b99c4dd95a --- /dev/null +++ b/Docs/migration/tools/lexicon-browse.md @@ -0,0 +1,25 @@ +# Browse (`lexiconBrowse`) + +| | | +|---|---| +| **Tool id** | `lexiconBrowse` | +| **Area** | Lexicon | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Browse (`lexiconBrowse`) (Sena 3, Legacy)](./images/lexicon-browse-01.png) + +## What it is +Browse/bulk-view table of lexical entries. + +## Notes / gotchas +- RecordBrowseView table. +- Already flipped to the Avalonia path -> State coexist. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/lexicon-classified-dictionary.md b/Docs/migration/tools/lexicon-classified-dictionary.md new file mode 100644 index 0000000000..26cd456250 --- /dev/null +++ b/Docs/migration/tools/lexicon-classified-dictionary.md @@ -0,0 +1,25 @@ +# Classified Dictionary (`lexiconClassifiedDictionary`) + +| | | +|---|---| +| **Tool id** | `lexiconClassifiedDictionary` | +| **Area** | Lexicon | +| **Type** | tool-screen | +| **Surface** | document (DocView) | +| **Primitive** | n/a | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | n/a | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Classified Dictionary (`lexiconClassifiedDictionary`) (Sena 3, Legacy)](./images/lexicon-classified-dictionary-01.png) + +## What it is +Dictionary entries organised/classified by semantic domain, as a document. + +## Notes / gotchas +- XhtmlDocView document view. +- Has a 'Show Unused Items' (ShowFailingItems) toggle. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/lexicon-dictionary.md b/Docs/migration/tools/lexicon-dictionary.md new file mode 100644 index 0000000000..6788f0f6f1 --- /dev/null +++ b/Docs/migration/tools/lexicon-dictionary.md @@ -0,0 +1,25 @@ +# Dictionary (`lexiconDictionary`) + +| | | +|---|---| +| **Tool id** | `lexiconDictionary` | +| **Area** | Lexicon | +| **Type** | tool-screen | +| **Surface** | document (DocView) | +| **Primitive** | n/a | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | n/a | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Dictionary (`lexiconDictionary`) (Sena 3, Legacy)](./images/lexicon-dictionary-01.png) + +## What it is +Formatted dictionary publication view of entries. + +## Notes / gotchas +- XhtmlRecordDocView document view (rendered dictionary publication, configurable layout). +- Read-oriented document surface; not a table or DataTree editor. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/lexicon-edit-popup.md b/Docs/migration/tools/lexicon-edit-popup.md new file mode 100644 index 0000000000..77b8d34e0a --- /dev/null +++ b/Docs/migration/tools/lexicon-edit-popup.md @@ -0,0 +1,26 @@ +# Lexicon Edit Popup (`lexiconEditPopup`) + +| | | +|---|---| +| **Tool id** | `lexiconEditPopup` | +| **Area** | Lexicon | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Lexicon Edit Popup (`lexiconEditPopup`) (Sena 3, Legacy)](./images/lexicon-edit-popup-01.png) + +## What it is +Test/popup composite: dictionary preview over a RecordEditView edit pane for the current entry. + +## Notes / gotchas +- NOT a normal navigable screen -- embedded popup/test edit composite (id 'TestEditMulti'); not wired into any sidebar tool list. +- PaneBarContainer > MultiPane: Dictionary preview pane + RecordEditView edit pane. +- Included only for inventory completeness. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/lexicon-edit.md b/Docs/migration/tools/lexicon-edit.md new file mode 100644 index 0000000000..1916bcc94a --- /dev/null +++ b/Docs/migration/tools/lexicon-edit.md @@ -0,0 +1,26 @@ +# Lexicon Edit (`lexiconEdit`) + +| | | +|---|---| +| **Tool id** | `lexiconEdit` | +| **Area** | Lexicon | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Lexicon Edit (`lexiconEdit`) (Sena 3, Legacy)](./images/lexicon-edit-01.png) + +## What it is +Browse list + detail edit of lexical entries (the primary dictionary entry editor). + +## Notes / gotchas +- Side-by-side: RecordBrowseView list pane + RecordEditView DataTree detail pane (MultiPane). +- Already flipped to the Avalonia path -> State coexist. +- This is the canonical detail-editor reference surface (FullEntryRegionComposer). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/lexicon-problems.md b/Docs/migration/tools/lexicon-problems.md new file mode 100644 index 0000000000..d369187d4d --- /dev/null +++ b/Docs/migration/tools/lexicon-problems.md @@ -0,0 +1,24 @@ +# Problems (`lexiconProblems`) + +| | | +|---|---| +| **Tool id** | `lexiconProblems` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Problems (`lexiconProblems`) (Sena 3, Legacy)](./images/lexicon-problems-01.png) + +## What it is +View/edit parser/grammar problem items. + +## Notes / gotchas +- RecordEditView detail. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/natural-classedit.md b/Docs/migration/tools/natural-classedit.md new file mode 100644 index 0000000000..8676a04d46 --- /dev/null +++ b/Docs/migration/tools/natural-classedit.md @@ -0,0 +1,25 @@ +# Natural Classes (`naturalClassedit`) + +| | | +|---|---| +| **Tool id** | `naturalClassedit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | split-out (own PR) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Natural Classes (`naturalClassedit`) (Sena 3, Legacy)](./images/natural-classedit-01.png) + +## What it is +Edit phonological natural classes. + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView detail. +- Grammar rule editor -> State split-out (own PR). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/notebook-browse.md b/Docs/migration/tools/notebook-browse.md new file mode 100644 index 0000000000..ad826735c7 --- /dev/null +++ b/Docs/migration/tools/notebook-browse.md @@ -0,0 +1,24 @@ +# Browse (`notebookBrowse`) + +| | | +|---|---| +| **Tool id** | `notebookBrowse` | +| **Area** | Notebook | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Browse (`notebookBrowse`) (Sena 3, Legacy)](./images/notebook-browse-01.png) + +## What it is +Browse table of notebook records. + +## Notes / gotchas +- RecordBrowseView table. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/notebook-document.md b/Docs/migration/tools/notebook-document.md new file mode 100644 index 0000000000..9890d58435 --- /dev/null +++ b/Docs/migration/tools/notebook-document.md @@ -0,0 +1,24 @@ +# Document (`notebookDocument`) + +| | | +|---|---| +| **Tool id** | `notebookDocument` | +| **Area** | Notebook | +| **Type** | tool-screen | +| **Surface** | document (DocView) | +| **Primitive** | n/a | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | n/a | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Document (`notebookDocument`) (Sena 3, Legacy)](./images/notebook-document-01.png) + +## What it is +Document view of notebook records. + +## Notes / gotchas +- XmlDocView document view. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/notebook-edit.md b/Docs/migration/tools/notebook-edit.md new file mode 100644 index 0000000000..2aeb16bb4a --- /dev/null +++ b/Docs/migration/tools/notebook-edit.md @@ -0,0 +1,25 @@ +# Record Edit (`notebookEdit`) + +| | | +|---|---| +| **Tool id** | `notebookEdit` | +| **Area** | Notebook | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Record Edit (`notebookEdit`) (Sena 3, Legacy)](./images/notebook-edit-01.png) + +## What it is +Browse list + detail edit of anthropology/notebook records. + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView DataTree detail. +- Already flipped to the Avalonia path -> State coexist. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/phoneme-edit.md b/Docs/migration/tools/phoneme-edit.md new file mode 100644 index 0000000000..76d4381c5e --- /dev/null +++ b/Docs/migration/tools/phoneme-edit.md @@ -0,0 +1,25 @@ +# Phonemes (`phonemeEdit`) + +| | | +|---|---| +| **Tool id** | `phonemeEdit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | split-out (own PR) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Phonemes (`phonemeEdit`) (Sena 3, Legacy)](./images/phoneme-edit-01.png) + +## What it is +Edit the phoneme inventory. + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView detail. +- Grammar rule editor -> State split-out (own PR). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/phonological-features-advanced-edit.md b/Docs/migration/tools/phonological-features-advanced-edit.md new file mode 100644 index 0000000000..8b376f121a --- /dev/null +++ b/Docs/migration/tools/phonological-features-advanced-edit.md @@ -0,0 +1,24 @@ +# Phonological Features (`phonologicalFeaturesAdvancedEdit`) + +| | | +|---|---| +| **Tool id** | `phonologicalFeaturesAdvancedEdit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Phonological Features (`phonologicalFeaturesAdvancedEdit`) (Sena 3, Legacy)](./images/phonological-features-advanced-edit-01.png) + +## What it is +Edit phonological feature definitions. + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView detail. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/phonological-rule-edit.md b/Docs/migration/tools/phonological-rule-edit.md new file mode 100644 index 0000000000..1cc7a4f183 --- /dev/null +++ b/Docs/migration/tools/phonological-rule-edit.md @@ -0,0 +1,25 @@ +# Phonological Rules (`PhonologicalRuleEdit`) + +| | | +|---|---| +| **Tool id** | `PhonologicalRuleEdit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | split-out (own PR) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Phonological Rules (`PhonologicalRuleEdit`) (Sena 3, Legacy)](./images/phonological-rule-edit-01.png) + +## What it is +Edit phonological rules. + +## Notes / gotchas +- Side-by-side: RecordBrowseActiveView list + RecordEditView detail (rule formula editor). +- Grammar rule editor -> State split-out (own PR). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/pos-edit.md b/Docs/migration/tools/pos-edit.md new file mode 100644 index 0000000000..92df5274b8 --- /dev/null +++ b/Docs/migration/tools/pos-edit.md @@ -0,0 +1,26 @@ +# Category Edit (`posEdit`) + +| | | +|---|---| +| **Tool id** | `posEdit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | coexist | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Category Edit (`posEdit`) (Sena 3, Legacy)](./images/pos-edit-01.png) + +## What it is +Edit the Parts of Speech (grammatical category) hierarchy with a detail editor. + +## Notes / gotchas +- RecordEditView DataTree detail over the PartsOfSpeech possibility hierarchy. +- Lives in Grammar (not Lists) -> Type tool-screen. +- Already flipped to the Avalonia path -> State coexist. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/prod-restrict-edit.md b/Docs/migration/tools/prod-restrict-edit.md new file mode 100644 index 0000000000..ca32d1e64d --- /dev/null +++ b/Docs/migration/tools/prod-restrict-edit.md @@ -0,0 +1,25 @@ +# Exception "Features" (`ProdRestrictEdit`) + +| | | +|---|---| +| **Tool id** | `ProdRestrictEdit` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Exception "Features" (`ProdRestrictEdit`) (Sena 3, Legacy)](./images/prod-restrict-edit-01.png) + +## What it is +Edit exception 'features' (productivity restrictions). + +## Notes / gotchas +- Side-by-side: RecordBrowseView list + RecordEditView detail. +- Edits the CmPossibility-based productivity-restriction list, but lives in Grammar -> Type tool-screen. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/rapid-data-entry.md b/Docs/migration/tools/rapid-data-entry.md new file mode 100644 index 0000000000..ebce8485a2 --- /dev/null +++ b/Docs/migration/tools/rapid-data-entry.md @@ -0,0 +1,26 @@ +# Collect Words (`rapidDataEntry`) + +| | | +|---|---| +| **Tool id** | `rapidDataEntry` | +| **Area** | Lexicon | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Collect Words (`rapidDataEntry`) (Sena 3, Legacy)](./images/rapid-data-entry-01.png) + +## What it is +Rapid Data Entry: collect words by semantic domain (RDE). + +## Notes / gotchas +- Composite: semantic-domain tree/record list + RecordBrowseView + RecordEditView edit pane (SemanticCategoryAndItems MultiPane). +- Defining surface is the RDE browse/collect grid; secondary RecordEditView detail pane. +- Driven by a PossibilityRecordList over Semantic Domains. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/reversal-tool-bulk-edit-reversal-entries.md b/Docs/migration/tools/reversal-tool-bulk-edit-reversal-entries.md new file mode 100644 index 0000000000..02ed7afacb --- /dev/null +++ b/Docs/migration/tools/reversal-tool-bulk-edit-reversal-entries.md @@ -0,0 +1,24 @@ +# Bulk Edit Reversal Entries (`reversalToolBulkEditReversalEntries`) + +| | | +|---|---| +| **Tool id** | `reversalToolBulkEditReversalEntries` | +| **Area** | Lexicon | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Bulk Edit Reversal Entries (`reversalToolBulkEditReversalEntries`) (Sena 3, Legacy)](./images/reversal-tool-bulk-edit-reversal-entries-01.png) + +## What it is +Bulk-edit table over Reversal Index entries. + +## Notes / gotchas +- RecordBrowseView table with bulk-edit (BulkReversalEntryPosEditor). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/reversal-tool-edit-complete.md b/Docs/migration/tools/reversal-tool-edit-complete.md new file mode 100644 index 0000000000..5ec8d46f07 --- /dev/null +++ b/Docs/migration/tools/reversal-tool-edit-complete.md @@ -0,0 +1,25 @@ +# Reversal Indexes (`reversalToolEditComplete`) + +| | | +|---|---| +| **Tool id** | `reversalToolEditComplete` | +| **Area** | Lexicon | +| **Type** | tool-screen | +| **Surface** | edit (RecordEditView/detail) | +| **Primitive** | detail (composed editor) | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | detail editor -> Lexicon Edit entry pane (FullEntryRegionComposer) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Reversal Indexes (`reversalToolEditComplete`) (Sena 3, Legacy)](./images/reversal-tool-edit-complete-01.png) + +## What it is +Browse/edit Reversal Index entries. + +## Notes / gotchas +- Composite: XhtmlDocView preview pane + RecordEditView DataTree detail pane for reversal index entries. +- Reversal-specific menu handler (ReversalIndexEntryMenuHandler). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/spelling.md b/Docs/migration/tools/spelling.md new file mode 100644 index 0000000000..74fe72cb2c --- /dev/null +++ b/Docs/migration/tools/spelling.md @@ -0,0 +1,26 @@ +# (Spelling Proto) (`spelling`) + +| | | +|---|---| +| **Tool id** | `spelling` | +| **Area** | Texts & Words | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![(Spelling Proto) (`spelling`) (Sena 3, Legacy)](./images/spelling-01.png) + +## What it is +Prototype spelling tool (browse table). + +## Notes / gotchas +- RecordBrowseView table. +- NOT wired into the active Texts & Words area (its toolConfiguration is not included by areaConfiguration.xml) -- prototype, not a live screen. +- Included only for inventory completeness. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/tool-bulk-edit-phonemes.md b/Docs/migration/tools/tool-bulk-edit-phonemes.md new file mode 100644 index 0000000000..09af9d3a03 --- /dev/null +++ b/Docs/migration/tools/tool-bulk-edit-phonemes.md @@ -0,0 +1,24 @@ +# Bulk Edit Phoneme Features (`toolBulkEditPhonemes`) + +| | | +|---|---| +| **Tool id** | `toolBulkEditPhonemes` | +| **Area** | Grammar | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Bulk Edit Phoneme Features (`toolBulkEditPhonemes`) (Sena 3, Legacy)](./images/tool-bulk-edit-phonemes-01.png) + +## What it is +Bulk-assign phonological features to phonemes. + +## Notes / gotchas +- RecordBrowseView table with bulk-edit (AssignFeaturesToPhonemes / PhonologicalFeatureEditor). + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/tool-bulk-edit-wordforms.md b/Docs/migration/tools/tool-bulk-edit-wordforms.md new file mode 100644 index 0000000000..bd078e3120 --- /dev/null +++ b/Docs/migration/tools/tool-bulk-edit-wordforms.md @@ -0,0 +1,24 @@ +# Bulk Edit Wordforms (`toolBulkEditWordforms`) + +| | | +|---|---| +| **Tool id** | `toolBulkEditWordforms` | +| **Area** | Texts & Words | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Bulk Edit Wordforms (`toolBulkEditWordforms`) (Sena 3, Legacy)](./images/tool-bulk-edit-wordforms-01.png) + +## What it is +Bulk-edit table over wordforms. + +## Notes / gotchas +- RecordBrowseView table with bulk-edit bar. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/tools/word-list-concordance.md b/Docs/migration/tools/word-list-concordance.md new file mode 100644 index 0000000000..072a363db3 --- /dev/null +++ b/Docs/migration/tools/word-list-concordance.md @@ -0,0 +1,25 @@ +# Word List Concordance (`wordListConcordance`) + +| | | +|---|---| +| **Tool id** | `wordListConcordance` | +| **Area** | Texts & Words | +| **Type** | tool-screen | +| **Surface** | browse (RecordBrowseView/table) | +| **Primitive** | TABLE | +| **State** | legacy (default) | +| **Phase** | 1 | +| **Canonical reference** | browse/table -> Lexicon Browse pane (LexicalBrowseView) | +| **JIRA** | LT-XXXXX | + +## What it looks like +![Word List Concordance (`wordListConcordance`) (Sena 3, Legacy)](./images/word-list-concordance-01.png) + +## What it is +Concordance driven by a word list, with occurrences table. + +## Notes / gotchas +- Composite (ConcordanceContainer): word-list RecordBrowseView + InterlinMasterNoTitleBar preview. +- Defining surface is the results browse table. + +> Stub. Deepen using `Docs/migration/_TEMPLATE.md` (capture legacy PNGs via the `fieldworks-winapp` skill) when this ticket is picked up. diff --git a/Docs/migration/writing-system-properties.md b/Docs/migration/writing-system-properties.md new file mode 100644 index 0000000000..9af91c5b4c --- /dev/null +++ b/Docs/migration/writing-system-properties.md @@ -0,0 +1,61 @@ +# Writing System Properties — bounded core (legacy `FwWritingSystemSetupDlg`) + +| | | +|---|---| +| **Legacy class** | `SIL.FieldWorks.FwCoreDlgs.FwWritingSystemSetupDlg` (`Src/FwCoreDlgs/FwWritingSystemSetupDlg.cs`) | +| **Area / tool** | Format › Set up Writing Systems › single-WS Add/Edit properties (bounded core) | +| **Primitive(s)** | plain-form (name / abbreviation / font / RTL / sort) | +| **Canonical reference** | OptionsDialog (closest kept canonical for a plain-form of fields; legacy is tabbed but this bounded core is plain) | +| **Backed-out Avalonia stub** | `Src/Common/FwAvaloniaDialogs/WritingSystemPropertiesDialogView.axaml(.cs)` + `WritingSystemPropertiesDialogViewModel.cs` @ git `this branch (recover from history)` | +| **JIRA** | LT-XXXXX | + +## What it is +A bounded managed core of the writing-system setup dialog: edit a single WS's name, abbreviation, default +font, right-to-left direction, and sort label. **PARTIAL** — the full `FwWritingSystemSetupDlg` surface +(SLDR sharing, encoding converters, merge, the advanced script/region/variant editor, keyboard assignment, +numbering/character-inventory tabs) is NOT ported. + +## What it looks like (before / after) +Legacy "before" captured by the screenshot harness (ScreenshotHarnessTests, option 2). Avalonia "after" +comes from the surface's FwAvaloniaDialogs(Tests) visual test (same data); attach both to the JIRA ticket. + +| Legacy (WinForms) — "before" | Avalonia (New) — "after" | +|---|---| +| ![fw-writing-system-setup legacy](./images/fw-writing-system-setup-before.png) | ![fw-writing-system-setup avalonia](./images/fw-writing-system-setup-after.png) | + +Tabs (legacy): + +![characters](./images/fw-writing-system-setup-tab-characters.png) ![converters](./images/fw-writing-system-setup-tab-converters.png) ![font](./images/fw-writing-system-setup-tab-font.png) ![general](./images/fw-writing-system-setup-tab-general.png) ![keyboard](./images/fw-writing-system-setup-tab-keyboard.png) ![numbers](./images/fw-writing-system-setup-tab-numbers.png) ![sorting](./images/fw-writing-system-setup-tab-sorting.png) +## Behaviour to preserve (parity checklist) +- [ ] Name field (required, trimmed). +- [ ] Abbreviation field (required, trimmed). +- [ ] Default-font combo (from the available-fonts list). +- [ ] Right-to-left checkbox. +- [ ] Sort label field (trimmed). +- [ ] OK gated: non-empty name + abbreviation, and a valid (non-duplicate, case-insensitive) tag — mirroring the legacy model's `IsListValid` / duplicate guard at this granularity. +- [ ] In-line validation message (first error) shown when invalid. + +## Migration gotchas +- PARTIAL — PARITY (stub `WritingSystemPropertiesDialogViewModel.cs`, §19g): "this is the managed + name/abbr/font/direction/sort core only. The full `FwWritingSystemSetupModel` surface — SLDR sharing, + encoding converters, merge, the advanced script/region/variant editor, keyboard assignment, and the + numbering/character-inventory tabs — is NOT ported here; those remain the legacy `FwWritingSystemSetupDlg`. + OK is gated on a non-empty name + abbreviation and a valid (non-duplicate) tag, mirroring the legacy + model's IsListValid/duplicate guard at this granularity." +- WS/RTL: this dialog *defines* a WS's direction — the RTL flag must persist correctly. +- The list-management surface (add/remove/merge across all WSs) is explicitly out of scope; only single-WS + Add/Edit properties are migrated. + +## Wiring +- **UNWIRED (test-only).** There is no product call site. The only references are the stub definition + (`WritingSystemPropertiesDialogView.axaml.cs`, `WritingSystemPropertiesDialogViewModel.cs`) and the tests + under `Src/Common/FwAvaloniaDialogs/FwAvaloniaDialogsTests/`. The legacy list-management call site is still + active and is NOT gated onto the Avalonia stub — so there is no call site to revert. +- PARITY note at the legacy call site (`Src/xWorks/FwXWindow.cs:1547`–`1553`, in `ShowWsPropsDialog`): + "PARITY §19g: the full Writing System SETUP dialog (`FwWritingSystemSetupDlg` over + `FwWritingSystemSetupModel` …) stays on the legacy WinForms path: it is far larger than a bounded §19g + slice. The bounded managed name/abbr/font/direction/sort PROPERTIES core IS migrated to the Avalonia kit + … for the future single-WS Add/Edit New-UI surface; this list-management call site is not yet gated onto + it." The live legacy dialog is constructed at `FwXWindow.cs:1557`. +- Re-wiring target (future): a single-WS Add/Edit New-UI surface enters this dialog behind `UIMode=New`; + Legacy keeps `FwWritingSystemSetupDlg`. diff --git a/openspec/changes/avalonia-end-game/.openspec.yaml b/openspec/changes/avalonia-end-game/.openspec.yaml new file mode 100644 index 0000000000..18edba1f54 --- /dev/null +++ b/openspec/changes/avalonia-end-game/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-20 diff --git a/openspec/changes/avalonia-end-game/design.md b/openspec/changes/avalonia-end-game/design.md new file mode 100644 index 0000000000..efc31769bc --- /dev/null +++ b/openspec/changes/avalonia-end-game/design.md @@ -0,0 +1,96 @@ +## Context + +`lexical-edit-avalonia-migration` (Phase 1) rebuilt the functional UI of FieldWorks in Avalonia while keeping WinForms switchable, hosting everything through the WinForms shell, and deleting nothing. The functional risk — every editor, field type, dialog, browse interaction, undo/redo, clipboard/drag-drop, command execution, and the WinForms-density visual system — is retired and tester-validated; Graphite shaping is the only intentional drop. The remaining functional gaps are tracked and burned down in Phase-1 §19. + +Phase 2 (`avalonia-end-game`) is the cutover. It absorbs and supersedes `fieldworks-avalonia-shell-migration`, consuming its already-specified Avalonia application/window lifetime, command bridge, screen registry, dialog/service abstractions, and decommissioning plan, and the frozen seam capabilities (`avalonia-command-focus`, `avalonia-ui-scheduler`, `avalonia-lifetime`, `avalonia-edit-sessions`, `avalonia-undo-redo`, `avalonia-validation`) from Phase 1. The shell change's section numbers are referenced where this plan absorbs them. + +The native-code reality the spec must be precise about: FieldWorks rendering is now fully managed (Avalonia/Skia/HarfBuzz; Views/Graphite/Uniscribe are replaced and isolation-tested). The retained native `ITsString`/writing-system kernel is the **data model** consumed via managed interop, not a renderer, and is app-wide. ICU is reached via managed `icu.net`. EncConverters, XAmple, spelling, and parser are non-UI services. Therefore "kill WinForms / go net10 / cross-platform" does **not** require removing the native data kernel. + +## Goals / Non-Goals + +**Goals:** + +- Avalonia is the sole default application shell, lifetime, windowing system, and main-screen host; no WinForms default path remains. +- The managed FieldWorks application retargets from .NET Framework 4.8 to .NET 10 and builds/runs on Windows, macOS, and Linux through the repo scripts. +- The WinForms shell/adapters/default dialogs, Gecko/Graphite-on-Avalonia, and native-Views-in-default are deleted under explicit per-target gates. +- Retained native data/services stay behind non-UI seams via managed interop and outside the Avalonia render/editor path. +- Localization, accessibility, and keyboarding are preserved post-cutover. + +**Non-Goals:** + +- No LCModel/data-schema rewrite; no removal of the native `ITsString`/writing-system data kernel. +- No new functional behavior; functional parity is owned by Phase 1. +- No permanent runtime WinForms/Avalonia fallback switch in the final app. + +## Decisions + +### 1. Dual lifetime collapses to a single Avalonia lifetime + +Phase 1 ran WinForms as the application lifetime and hosted Avalonia content inside it (`WinFormsAvaloniaControlHost`). Phase 2 inverts this permanently: the app starts on the Avalonia classic desktop lifetime with explicit top-level window ownership and active-window tracking. The framework-neutral lifetime/dialog/dispatcher contracts specified by the shell change (its §2) become Avalonia-only implementations; the WinForms compatibility adapters for those contracts are deleted. Canonical startup obligations (diagnostics, project selection/open, LCModel cache init, service registration, safe shutdown, remote-request listener, no-UI/app-server modes) are preserved on the Avalonia path. (Absorbs shell §2, §5.) + +### 2. net48 → .NET 10 retarget strategy + +The managed FieldWorks projects move from `net48` to `net10` (SDK-style). Strategy: + +- **Retarget the managed app graph** (`Src/Common/FieldWorks`, `Src/Common/Framework`, `Src/XCore`, `Src/xWorks`, FLEx screens, and their dependencies) to `net10`. Phase-1 Avalonia projects already target modern .NET; this brings the rest of the default graph forward to a single TFM. +- **Build** (`build.ps1`, `FieldWorks.proj`): keep native-C++-before-managed ordering (a non-negotiable repo constraint) and traversal structure; update SDKs, package references, and analyzers for net10. Build remains driven by the repo scripts — no separate build lane. +- **Stays (behind service seams, via managed interop):** the native C++ `ITsString`/writing-system data kernel, ICU (`icu.net`), EncConverters, XAmple, spelling, and parser. Registration-free COM is preserved — no global COM registration, no registry hacks. +- **Removed:** net48-only and WinForms-only managed projects/code paths in the default graph (see Decision 4), plus net48-pinned packages/analyzers superseded by net10 equivalents. +- **Test discovery** moves to the net10 runner for the retargeted projects while the build/test scripts stay the entry points. + +### 3. True cross-platform (Windows / macOS / Linux) + +The default app targets all three desktop OSes. The native data kernel and non-UI services are consumed via managed interop on each platform; ICU rides `icu.net`. Packaging produces per-OS artifacts (Windows installer, macOS bundle, Linux package) harvesting the Avalonia runtime and required native dependencies per platform. CI builds, runs, and smoke-tests on all three. Platform-specific concerns (keyboard/IME services, file/font pickers — already Avalonia-managed in Phase 1, fonts, path/casing) are validated per OS rather than assumed. + +### 4. Deletion gates (per target) + +Each WinForms-era target is deleted only when its replacement is the sole default path and proven. Gates: + +- **WinForms shell + main-window services:** deletable once the Avalonia single-lifetime shell is default and full-app smoke passes on all three OSes with no default-path dependency on the WinForms shell (startup audit fails on any hidden WinForms shell creation). +- **`FlexUIAdapter` default behavior:** deletable once command routing/state and screen composition run through the Avalonia path with no default-path adapter dependency. +- **WinForms-only default dialogs:** deletable per dialog once its Avalonia equivalent (built/hosted in Phase 1) is the sole default and parity evidence exists. +- **Gecko / Graphite-on-Avalonia:** deletable once the default app has no runtime call path through Gecko/XULRunner or native Graphite shaping; browser/PDF behavior is either replaced by the chosen non-Graphite strategy or moved outside the default boundary (per Phase-1 5.6–5.8). +- **Native Views rendering in default path:** deletable once dependency audit proves no default-path call path through native display/layout/measurement/hit-testing/selection/editor-realization. The native `ITsString`/WS **data kernel** is explicitly **not** under this gate — it is the retained data model, not a renderer. + +Each deletion is reversible only by revert (it is a cutover, not a flag), so each gate requires green smoke + audit evidence before the deletion lands. + +### 5. Phase-1-proven precondition + +The cutover does not start removing WinForms until the Phase-1 functional parity burn-down (`lexical-edit-avalonia-migration` §19, including the §19h tester burn-down to zero) shows no open functional regression. This is the load-bearing gate: because functional behavior was proven in Phase 1, Phase-2 validation is lifecycle/retarget/packaging/deletion correctness, not feature correctness. + +### 6. Rollback posture + +This is a cutover, not a dual-run feature flag. There is no permanent runtime WinForms fallback in the final app. The safety mechanism is the **gate**, not a switch: the Phase-1 burn-down must be zero before deletions begin, and each per-target deletion (Decision 4) lands only behind green smoke + dependency-audit evidence on all three OSes. Rollback of a bad step is by source revert of that step, not by toggling WinForms back on at runtime. + +### 7. Consume frozen seams and the shell spec rather than redefining them + +`avalonia-end-game` does not reopen the seam decisions or re-specify shell composition. The shell-global phases of `avalonia-command-focus`, `avalonia-ui-scheduler`, and `avalonia-lifetime` and the shell change's composition/registry/dialog-service specifications are consumed as-is. Where this change references shell behavior it cites the absorbed shell section numbers (§2–§10) rather than restating them. + +## Risks / Trade-offs + +- **net10 retarget surfaces hidden net48/WinForms coupling** → retarget the managed graph incrementally to a single TFM and let the build fail loudly on residual coupling; deletions (Decision 4) gate on audits, not assumptions. +- **Cross-platform native interop gaps (kernel, ICU, keyboard/IME) on macOS/Linux** → validate the retained native data kernel and `icu.net` per OS in CI before claiming cross-platform; treat per-OS keyboard/IME as an explicit bring-up item. +- **Packaging/installer divergence across three OSes** → per-OS harvest with a shared runtime list; CI builds the installer artifact on each OS. +- **Deleting too early breaks a not-yet-replaced path** → per-target gates require the Avalonia replacement to be sole-default and smoke-green before deletion. +- **Phase-1 burn-down slips** → the precondition (Decision 5) blocks the cutover; Phase 2 work that does not delete WinForms (retarget prep, cross-platform bring-up scaffolding) can proceed, but no WinForms removal lands until burn-down is zero. + +## Migration Plan + +1. Confirm the Phase-1 functional parity burn-down (`lexical-edit-avalonia-migration` §19) is at zero open functional regressions; otherwise track the blockers explicitly. +2. Retarget the managed app graph net48 → .NET 10 (projects, build, packages, analyzers, test discovery), keeping native-first build ordering and registration-free COM. +3. Stand up the single Avalonia application lifetime + windowing as the default (absorb shell §2–§5). +4. Compose the shell — navigation/menus/toolbars/status/screen-registry — on the Avalonia path (absorb shell §3, §6, §7). +5. Route commands and state without XCore-WinForms adapters (absorb shell §4). +6. Host the global dialogs/services (built in Phase 1) as Avalonia shell services (absorb shell §8). +7. Migrate any remaining main-screen composition area by area (absorb shell §9). +8. Bring up macOS and Linux: build, run, package, CI on all three OSes (extends shell §10). +9. Delete the WinForms shell, adapters, default dialogs, Gecko/Graphite-on-Avalonia, and native-Views-in-default under per-target gates (the deletions Phase 1 forbade; absorb shell §10.7). +10. Final validation: full-app smoke on all three OSes, UIA/accessibility, performance budgets, installer harvest. + +## Open Questions + +1. Exact .NET 10 SLA/support window alignment with the FieldWorks release cadence and the installer's bundled-runtime vs framework-dependent choice per OS. +2. macOS/Linux packaging format decisions (e.g. notarized `.app`/`.dmg`; `.deb`/`.rpm`/AppImage/Flatpak) and code-signing per OS. +3. The non-Graphite browser/PDF default strategy decision (recommended-not-decided in Phase-1 `gecko-pdf-audit.md`) must be finalized before the Gecko deletion gate. +4. Per-OS keyboard/IME service coverage parity (Keyman/system IME) on macOS/Linux versus the Windows baseline. +5. Whether any retained native service requires a per-OS build of its interop layer, and how that is harvested per platform. diff --git a/openspec/changes/avalonia-end-game/proposal.md b/openspec/changes/avalonia-end-game/proposal.md new file mode 100644 index 0000000000..f5d5175108 --- /dev/null +++ b/openspec/changes/avalonia-end-game/proposal.md @@ -0,0 +1,53 @@ +## Why + +Phase 1 (`lexical-edit-avalonia-migration`) derisked the hard part. Every functional surface — detail editors, every field type, the browse table with full sort/filter/bulk-edit, the dialog kit, undo/redo, clipboard/drag-drop, command execution, the WinForms-density visual system — was rebuilt in Avalonia with **WinForms still switchable in/out**, **everything still hosted through the WinForms shell**, and **no code deleted**. Full linguistic depth was preserved with no functional regression; Graphite shaping is the only intentional drop (rendered via OpenType/HarfBuzz instead, per `graphite-transition-support`). The real buttons, dialogs, strings, editors, and undo behavior were implemented and tester-validated. + +What remains is the frame rip-out. FieldWorks still **starts and composes** through a WinForms/XCore shell on .NET Framework 4.8, so the default product is a fully functional Avalonia island inside the old shell rather than a true cross-platform application. Phase 2 is the cutover: kill WinForms completely, retarget the managed app from net48 to .NET 10, and ship a real cross-platform (Windows/macOS/Linux) Avalonia application. + +Because the functional behavior was already proven in Phase 1, this change reads as **"it works or it doesn't."** The risk is not whether a button or editor behaves correctly — that was validated. The risk is purely lifecycle, retarget, packaging, and the disciplined removal of code that Phase 1 deliberately left in place. + +## What Changes + +- Absorb and supersede `fieldworks-avalonia-shell-migration`: this change consumes the frozen seam decisions and the shell's already-specified Avalonia application/window lifetime, command bridge, screen registry, dialog/service abstractions, and final WinForms decommissioning, and is the single active Phase-2 plan as of 2026-06-20. +- Make Avalonia the **sole** default shell: no WinForms shell, host, dynamic content host, or `FlexUIAdapter` default path is created in the default application path. +- Retarget the FieldWorks managed projects and build (`build.ps1` / `FieldWorks.proj`) from **.NET Framework 4.8 to .NET 10**, including package, analyzer, and language-level changes. +- Establish a **single Avalonia application lifetime** (classic desktop lifetime, explicit top-level window ownership) in place of the Phase-1 dual lifetime where WinForms hosts Avalonia content. +- Bring the application up as **true cross-platform**: build, run, package, and CI on Windows, macOS, and Linux. +- **Delete** the code Phase 1 forbade removing, behind per-target gates: the WinForms shell and main-window services, WinForms-only default dialogs, the `FlexUIAdapter` default behavior, Gecko/Graphite-on-Avalonia assumptions, and native Views rendering from the default path. +- Keep retained native services (the native `ITsString`/writing-system kernel as the **data model**, ICU via managed `icu.net`, EncConverters, XAmple, spelling, parser) behind non-UI service seams; these are consumed via managed interop and are **not** in the Avalonia render/editor path. +- Gate the entire cutover on the **Phase-1 functional parity burn-down reaching zero** (`lexical-edit-avalonia-migration` §19): no open functional regression before any WinForms removal. + +## Non-goals + +- No LCModel rewrite, lexicon data schema change, or project-format change. +- No removal of the retained native `ITsString`/writing-system **data kernel**; killing WinForms and going net10/cross-platform does not require removing the native data model consumed via managed interop. +- No removal of retained non-UI linguistics services (XAmple, spelling, parser, ICU, EncConverters) when isolated behind service seams. +- No new functional behavior, editor, dialog, or field type — that work belongs to Phase 1; Phase 2 is lifecycle/retarget/packaging/deletion only. +- No re-derisking of Avalonia functional parity; this change consumes Phase-1's proven parity rather than reopening it. +- No runtime WinForms/Avalonia fallback switch in the final app: the cutover gate is the Phase-1 burn-down, not a permanent dual-run flag. + +## Capabilities + +### New Capability + +- `avalonia-end-game`: the Phase-2 cutover — Avalonia as the sole default shell, net48 → .NET 10 retarget, single Avalonia application lifetime, cross-platform (Windows/macOS/Linux) build/packaging/CI, gated deletion of the WinForms shell/adapters/Gecko-Graphite-on-Avalonia/native-Views-in-default, retained native services kept behind non-UI seams, and the Phase-1-parity cutover precondition. + +### Architecture Areas Covered + +- `architecture/layers/entry-points`: single Avalonia application lifetime replacing the WinForms host; canonical startup preserved. +- `architecture/build-deploy/build-phases`: net48 → .NET 10 retarget of managed projects and the traversal build; native-first ordering preserved. +- `architecture/build-deploy/installer`: cross-platform packaging/installers and Avalonia runtime harvest; WinForms/Gecko retirement. +- `architecture/ui-framework/winforms-patterns`: WinForms shell, adapters, and dialogs removed from the default path under deletion gates. +- `architecture/ui-framework/xcore-mediator`: XCore-WinForms composition removed from the default path; command routing/state runs without WinForms adapters. +- `architecture/interop/native-boundary`: retained native data/services behind non-UI seams; no native viewing/rendering in the default app; registration-free COM preserved. +- `architecture/testing/test-strategy`: full-app smoke, accessibility, and performance gates run on all three OSes. +- `architecture/build-deploy/localization`: localization, accessibility, and keyboarding preserved through the cutover. + +## Impact + +- Managed shell/framework code: `Src/Common/FieldWorks/`, `Src/Common/Framework/`, `Src/XCore/`, `Src/xWorks/`, and main FLEx screens under `Src/LexText/` — retargeted to .NET 10 and stripped of the WinForms default path. +- Avalonia code: `Src/Common/FwAvalonia/`, `Src/Common/FwAvaloniaPreviewHost/`, and the Avalonia shell projects become the sole default UI. +- Build/deploy: `build.ps1`, `FieldWorks.proj`, package/analyzer references, installer/runtime packaging, and CI across Windows/macOS/Linux. +- Native/interop: native `ITsString`/writing-system kernel, ICU (`icu.net`), EncConverters, XAmple, spelling, and parser remain behind non-UI service seams via managed interop; native Views/Graphite/Gecko rendering is removed from the default path; registration-free COM is preserved. +- Deletion: WinForms shell + main-window services, WinForms-only default dialogs, `FlexUIAdapter` default, Gecko/Graphite-on-Avalonia, and native-Views-in-default are removed under per-target gates (the deletions Phase 1 forbade). +- Precondition: gated on the Phase-1 functional parity burn-down (`lexical-edit-avalonia-migration` §19) reaching zero open functional regressions. diff --git a/openspec/changes/avalonia-end-game/specs/avalonia-end-game/spec.md b/openspec/changes/avalonia-end-game/specs/avalonia-end-game/spec.md new file mode 100644 index 0000000000..3eefdb5a54 --- /dev/null +++ b/openspec/changes/avalonia-end-game/specs/avalonia-end-game/spec.md @@ -0,0 +1,100 @@ +## ADDED Requirements + +### Requirement: Avalonia is the sole default shell + +FieldWorks SHALL use Avalonia as the sole default application shell, with no WinForms shell, host, dynamic content host, or `FlexUIAdapter` default behavior created in the default application path. + +#### Scenario: Default startup creates only the Avalonia shell +- **WHEN** FieldWorks starts in the default (cutover) mode +- **THEN** the top-level shell, windowing, navigation, content host, menus, toolbars, status panes, and dialogs SHALL be Avalonia-owned +- **AND** no WinForms shell, WinForms dynamic content host, WinForms main-window service, or `FlexUIAdapter` default path SHALL be created + +#### Scenario: Startup audit fails on hidden WinForms shell +- **WHEN** default-startup dependency validation runs +- **THEN** it SHALL fail if the default path creates a WinForms shell, dynamic content host, WinForms main-window service, or `FlexUIAdapter` default behavior + +### Requirement: Application uses a single Avalonia lifetime + +The default application SHALL run on a single Avalonia desktop lifetime with explicit top-level window ownership, not a WinForms application lifetime hosting Avalonia content. + +#### Scenario: No WinForms application lifetime hosts the default app +- **WHEN** the default app starts, opens a project, and shuts down +- **THEN** application lifetime, window ownership, active-window tracking, modal ownership, UI dispatch, and shutdown SHALL be provided by the Avalonia implementation +- **AND** the default path SHALL NOT require `Application.Run`, a WinForms `Form` host, or a WinForms message loop + +### Requirement: Application retargets to .NET 10 + +The managed FieldWorks application SHALL retarget from .NET Framework 4.8 to .NET 10, built through the repository build script with native-C++-before-managed ordering and registration-free COM preserved. + +#### Scenario: Managed graph builds on net10 via repo scripts +- **WHEN** the default managed application graph is built through `build.ps1` / `FieldWorks.proj` +- **THEN** the managed projects SHALL target .NET 10 +- **AND** native C++ SHALL build before managed projects +- **AND** COM SHALL remain registration-free with no global COM registration or registry hacks + +#### Scenario: net48-only default paths are removed +- **WHEN** the retarget is complete +- **THEN** the default application graph SHALL contain no net48-only or WinForms-only default code path or package required for default startup + +### Requirement: Application runs on Windows, macOS, and Linux + +The default application SHALL build, run, and package on Windows, macOS, and Linux. + +#### Scenario: App launches on each supported OS +- **WHEN** the cutover application is built and launched on Windows, on macOS, and on Linux +- **THEN** it SHALL start, open or create a project, and present the Avalonia shell on each OS +- **AND** continuous integration SHALL build and smoke-test the app on all three OSes + +### Requirement: WinForms shell and default-path UI infrastructure are removed + +The WinForms shell, WinForms main-window services, WinForms-only default dialogs, the `FlexUIAdapter` default path, Gecko/Graphite-on-Avalonia assumptions, and native Views rendering SHALL be removed from the default application. + +#### Scenario: Default dependency audit fails on removed infrastructure +- **WHEN** default-app dependency validation runs after cutover +- **THEN** it SHALL fail if the default path references the WinForms shell, WinForms-only default dialogs, the `FlexUIAdapter` default behavior, Gecko/XULRunner, native Graphite shaping, or native Views display/layout/hit-testing/selection/editor-realization for default UI + +#### Scenario: Deletion lands behind a per-target gate +- **WHEN** a WinForms-era target is deleted +- **THEN** its Avalonia replacement SHALL already be the sole default path +- **AND** full-app smoke and dependency-audit evidence SHALL exist on all three OSes before the deletion lands + +### Requirement: Retained native services stay behind non-UI seams + +The retained native `ITsString`/writing-system data kernel, ICU, EncConverters, XAmple, spelling, and parser services SHALL remain available behind non-UI service seams via managed interop and SHALL NOT participate in Avalonia display, layout, hit testing, selection, or editor realization. + +#### Scenario: Native data kernel is consumed as data, not as a renderer +- **WHEN** the Avalonia shell or screens use `ITsString`/writing-system data, ICU, EncConverters, XAmple, spelling, or parser services +- **THEN** those services SHALL be reached through non-UI service contracts via managed interop +- **AND** they SHALL NOT be on the Avalonia render or editor path + +#### Scenario: Native data kernel is not removed by the cutover +- **WHEN** WinForms removal and the net10/cross-platform cutover complete +- **THEN** the native `ITsString`/writing-system data kernel SHALL remain as the retained data model +- **AND** native-render removal SHALL NOT remove the native data kernel + +### Requirement: Cutover requires proven Phase-1 functional parity + +WinForms removal SHALL NOT begin until the Phase-1 functional parity burn-down shows no open functional regression. + +#### Scenario: WinForms deletion is blocked by open functional regressions +- **WHEN** any WinForms-removal step is proposed +- **THEN** the `lexical-edit-avalonia-migration` Phase-1 functional parity burn-down SHALL show zero open functional regressions before the step proceeds + +### Requirement: Cross-platform packaging and CI + +Installer/packaging logic SHALL produce per-OS artifacts harvesting the Avalonia runtime and required native dependencies for Windows, macOS, and Linux, validated in continuous integration on each OS. + +#### Scenario: Per-OS artifacts are produced and validated +- **WHEN** packaging runs for the cutover application +- **THEN** it SHALL produce the artifact for each of Windows, macOS, and Linux including the Avalonia runtime and required native dependencies for that OS +- **AND** CI SHALL build and smoke-test the packaged app on each OS + +### Requirement: Localization, accessibility, and keyboarding survive the cutover + +The cutover application SHALL preserve localization, accessibility identity, and keyboarding behavior present before the cutover. + +#### Scenario: Localized, accessible, keyboard-capable app post-cutover +- **WHEN** the cutover application runs on a supported OS +- **THEN** user-facing strings SHALL resolve from existing localization resources +- **AND** controls SHALL expose stable accessibility identity for UIA/accessibility tooling +- **AND** writing-system keyboard/IME switching SHALL behave equivalently to the pre-cutover baseline for covered workflows diff --git a/openspec/changes/avalonia-end-game/tasks.md b/openspec/changes/avalonia-end-game/tasks.md new file mode 100644 index 0000000000..97b2f7f69f --- /dev/null +++ b/openspec/changes/avalonia-end-game/tasks.md @@ -0,0 +1,81 @@ +# Tasks + +> Phase 2 (cutover). This change absorbs `fieldworks-avalonia-shell-migration`; absorbed shell task +> sections are cited as "shell §N". No new functional behavior lands here — functional parity is owned +> by Phase 1 (`lexical-edit-avalonia-migration`). These tasks are lifecycle, retarget, packaging, and +> gated deletion only. + +## 0. Preconditions and Phase-1 parity gate + +- [ ] 0.1 Confirm the Phase-1 functional parity burn-down (`lexical-edit-avalonia-migration` §19, incl. §19h tester burn-down) shows zero open functional regressions; otherwise track each blocker explicitly and hold WinForms-removal tasks (§8) until clear. +- [ ] 0.2 Inventory the default-path WinForms shell, main-window services, `FlexUIAdapter` default behavior, WinForms-only default dialogs, Gecko/Graphite-on-Avalonia, and native-Views-in-default targets to be removed, each with its replacement and deletion gate (Decision 4 of design.md). +- [ ] 0.3 Inventory net48-only / WinForms-only managed projects, packages, and analyzers in the default graph that the net10 retarget removes or replaces. +- [ ] 0.4 Confirm the retained native services (ITsString/WS data kernel, ICU/`icu.net`, EncConverters, XAmple, spelling, parser) are reachable only behind non-UI seams and record them as out-of-scope for render/editor-path removal. + +## 1. net48 → .NET 10 retarget + +- [ ] 1.1 Retarget the managed app graph (`Src/Common/FieldWorks`, `Src/Common/Framework`, `Src/XCore`, `Src/xWorks`, FLEx screens, and dependencies) from `net48` to `net10`. +- [ ] 1.2 Update `build.ps1` / `FieldWorks.proj` for net10 while preserving native-C++-before-managed ordering and the traversal structure; keep the repo scripts as the only build/test entry points. +- [ ] 1.3 Update package references and analyzers for net10; remove net48-pinned packages superseded by net10 equivalents. +- [ ] 1.4 Move test discovery to the net10 runner for retargeted projects; keep `test.ps1` as the entry point. +- [ ] 1.5 Preserve registration-free COM and the managed-interop boundary to the retained native data kernel and services; no global COM registration, no registry hacks. +- [ ] 1.6 Build the retargeted default graph green via `build.ps1` and let the build fail loudly on residual net48/WinForms coupling. + +## 2. Single Avalonia application lifetime and windowing (absorb shell §2, §5) + +- [ ] 2.1 Make Avalonia classic desktop lifetime the default application lifetime with explicit top-level window ownership and active-window tracking; collapse the Phase-1 dual lifetime. +- [ ] 2.2 Provide Avalonia-only implementations of the framework-neutral lifetime/dialog-owner/dispatcher/shutdown contracts (shell §2); retire the WinForms compatibility adapters for those contracts. +- [ ] 2.3 Preserve canonical startup on the Avalonia path: diagnostics, project selection/open, LCModel cache init, service registration, splash/safe-mode, remote-request listener, no-UI/app-server modes, update checks, safe shutdown. +- [ ] 2.4 Add Avalonia.Headless tests for app lifetime, active-window tracking, dialog ownership, UI dispatch, and deterministic shutdown/disposal. + +## 3. Shell composition: navigation, menus, toolbars, status, screen registry (absorb shell §3, §6, §7) + +- [ ] 3.1 Compose navigation, content host, side/record panes, menus, context menus, toolbars, and status/progress regions on the Avalonia path from the typed shell definition (shell §3, §7). +- [ ] 3.2 Drive area/tool navigation through the Avalonia screen registry with persisted `areaChoice`/`currentContentControl` compatibility (shell §6). +- [ ] 3.3 Add Avalonia.Headless tests for navigation host swapping, menu/toolbar/status rendering, pane state, and layout persistence. + +## 4. Command routing and state without XCore-WinForms (absorb shell §4) + +- [ ] 4.1 Route shell commands and state through the Avalonia command path (typed descriptors, shell-global `avalonia-command-focus`) with no default-path `FlexUIAdapter`/WinForms menu/toolstrip adapter. +- [ ] 4.2 Run the XCore mediator/property-table only as a non-WinForms compatibility bridge where still needed; it SHALL NOT own default UI composition. +- [ ] 4.3 Add tests for command enable/visible/checked state, shortcuts, one-at-a-time commands, and target resolution without WinForms adapters. + +## 5. Global dialogs and services as Avalonia shell services (absorb shell §8) + +- [ ] 5.1 Host the global dialogs/services as Avalonia shell services — the *functional* dialogs were built and validated in Phase 1; here they are composed/owned by the Avalonia shell (project, writing-system, settings, import/export, find/replace, styles, help, feedback, progress, keyboarding, clipboard, print). +- [ ] 5.2 Provide the non-Graphite browser/PDF default strategy or keep affected paths outside the default boundary (per Phase-1 5.6–5.8); finalize the recommended-not-decided decision before the Gecko deletion gate (§8.4). +- [ ] 5.3 Add owner/modal, focus-return, cancellation, accessibility, and localization tests for the shell-hosted dialogs/services. + +## 6. Main-screen composition area by area (absorb shell §9) + +- [ ] 6.1 Compose the remaining main screens (Lexicon, Words/Interlinear, Grammar/Morphology, Notebook, Lists) through the Avalonia screen registry with no default-path WinForms dynamic content host. +- [ ] 6.2 Verify each area's screen manifest evidence (commands, navigation, accessibility, localization, native-boundary status, performance) on the Avalonia path. + +## 7. Cross-platform: macOS and Linux bring-up, packaging, CI + +- [ ] 7.1 Bring up build + run on macOS and on Linux; validate the retained native data kernel and `icu.net` interop per OS. +- [ ] 7.2 Validate per-OS keyboard/IME, file/font pickers (Avalonia-managed), fonts, and path/casing behavior. +- [ ] 7.3 Produce per-OS packaging artifacts (Windows installer, macOS bundle, Linux package) harvesting the Avalonia runtime and required native dependencies for each OS. +- [ ] 7.4 Add CI lanes that build and smoke-test the app on Windows, macOS, and Linux. + +## 8. Decommission / DELETE WinForms default path (the deletions Phase 1 forbade) (absorb shell §10.7) + +> Each deletion lands only behind its per-target gate (Decision 4 of design.md): the Avalonia +> replacement is sole-default and full-app smoke + dependency-audit evidence is green on all three OSes. +> Blocked until §0.1 (Phase-1 burn-down to zero) is satisfied. + +- [ ] 8.1 Delete the WinForms shell and WinForms main-window services from the default path (gate: Avalonia single-lifetime shell default + smoke green; startup audit fails on hidden WinForms shell). +- [ ] 8.2 Delete the `FlexUIAdapter` default behavior (gate: command routing/state + screen composition run on the Avalonia path with no default-path adapter dependency). +- [ ] 8.3 Delete WinForms-only default dialogs per dialog (gate: Avalonia equivalent is sole default + parity evidence). +- [ ] 8.4 Delete Gecko/XULRunner and Graphite-on-Avalonia assumptions from the default path (gate: no default-path Gecko/native-Graphite call path; browser/PDF replaced or moved outside default per §5.2). +- [ ] 8.5 Delete native Views rendering from the default path (gate: dependency audit proves no default-path native display/layout/measurement/hit-testing/selection/editor-realization). The native `ITsString`/WS data kernel is explicitly NOT deleted — it is the retained data model. +- [ ] 8.6 Remove obsolete net48/WinForms shell-XML runtime pieces and dead build wiring left by the deletions above. + +## 9. Final validation + +- [ ] 9.1 Full-app smoke on all three OSes: launch, open/create project, switch representative areas/tools, execute representative commands, show dialogs, close windows, shut down cleanly. +- [ ] 9.2 UIA/accessibility evidence on the cutover shell and screens. +- [ ] 9.3 Performance budget evidence against the recorded baselines for representative workflows. +- [ ] 9.4 Installer/packaging harvest validated per OS (runtime + native dependencies present and launchable). +- [ ] 9.5 Localization evidence: user-facing strings resolve from existing resources post-cutover. +- [ ] 9.6 Final default-app dependency audit green: no WinForms shell, `FlexUIAdapter` default, WinForms-only default dialogs, Gecko/native-Graphite, or native-Views-in-default remain; retained native data/services confirmed behind non-UI seams. diff --git a/openspec/changes/avalonia-migration-roadmap/complete-migration-program.md b/openspec/changes/avalonia-migration-roadmap/complete-migration-program.md new file mode 100644 index 0000000000..7015699b18 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/complete-migration-program.md @@ -0,0 +1,699 @@ +# FieldWorks → Avalonia: Complete Migration Program Plan + +> **Purpose.** This is the master, end-to-end plan for migrating **all** of FieldWorks' +> WinForms UI and native C++ UI/rendering code to Avalonia + C#. It extends the existing +> `avalonia-migration-roadmap` (which covers Phase 0 spike → Lexical Edit → shell) to the +> *whole* application, organizes the work into stages sized to become **JIRA epics**, and +> sequences the foundation work that must precede broad hand-off to less-AI-experienced +> developers. +> +> **Status:** planning only. No code/behavior change. JIRA epics/issues will be created +> from this document later, not now. +> +> **Grounding:** built from (a) the frozen architecture in +> `.claude/skills/fieldworks-winforms-to-avalonia-migration/references/`, (b) the as-built +> work in `Src/Common/FwAvalonia/` and `openspec/changes/lexical-edit-avalonia-migration/`, +> (c) a full repo surface inventory, and (d) external research on incremental desktop +> migrations (sources cited in the Appendix). + +--- + +## 1. Executive summary + +FieldWorks is a large desktop app: ~16 WinForms UI projects, 200+ Form/UserControl classes, +~200 dialogs, a 73-file DataTree slice framework, a 44-file XMLViews browse framework, and a +native C++ **Views** rendering/editing engine (`Src/views/`, VwRootBox/VwSelection/VwTextBoxes) +consumed through registration-free COM and hosted by `SimpleRootSite`. Conservative sizing: +**~150k LOC of UI/render code, an 18–30 month program.** + +The first vertical slice — the **Lexical Edit / Advanced Entry** region — is essentially done +and proves the whole approach: a typed view-definition IR compiled from XML, a region +model + composer, owned dense Avalonia controls, frozen seam contracts, a managed +multi-writing-system text-editing foundation (no native Views), a global surface-selection +switch with explicit per-host behavior, and a triangulated parity-evidence harness +(semantic + visual + workflow + performance). **The architecture is decided; do not reinvent it.** + +What remains is (1) **hardening that one-off into a reusable platform** other developers can +drive, (2) **a shared editable virtualized grid/tree** (the single biggest off-the-shelf gap), +(3) **migrating each remaining surface** in parallel vertical slices, (4) **replacing the native +Views engine** for the document/multi-paragraph/interlinear surfaces (the long pole), and +(5) **the shell + final cutover + cross-platform enablement**. + +The plan below is **13 stages in 4 tracks**. Tracks I (foundation) and III (native engine) are +senior-led; Track II (surfaces) is built for parallel hand-off; Track IV (shell, runtime +modernization, cutover) lands last. + +--- + +## 1a. Post-review revision status (2026-06-15) + +This plan was reviewed **stage-by-stage by 13 independent subagents** (one per stage), each grounded +in repo inspection + targeted web research. Full evidence is in `reviews/stage-NN-*.md`; the +cross-comparison is in `reviews/00-cross-comparison-synthesis.md`. **Read the synthesis before +creating epics.** Headlines: + +- **The 13 stages and their ordering are confirmed sound** — including the contested calls (dialogs + before shell; the .NET 10 + Avalonia 12 jump kept late). The late runtime jump is in fact *forced*: + Avalonia 12 dropped `netstandard2.0`/net48 (repo pins **Avalonia 11.3.17** for exactly this reason), + and the one-CLR rule means net48 can't be left until the WinForms host (Stage 11) is gone. +- **Sizing is the main problem.** Eight stages (**3, 6, 7, 8, 9, 10, 11, 13**) are too coarse for a + single epic and decompose into the gated sub-epics mapped in synthesis §6. JIRA epics should be + created from that sub-epic map, not from the one-row-per-stage table in §4. +- **Several "build" items are already built** (JSON view-def serializer, 10k-row read-only browse, + custom-field rendering, host bridge, command-bridge seam, chooser virtualization, AutomationId + convention). Stages 1–4 are largely *finish / re-home / generalize*, not *build*. +- **Stage 9 is the gravity well and the #1 correctness risk:** its scope text is too narrow for what + Stages 6b and 7A depend on (in-memory presentation cache / `CachePair`, hit-test combo editing, + aligned interlinear grid). Expanded below; decomposed in synthesis §6. +- **Conflicts/double-bookings resolved** in synthesis §3 (surface registry → Stage 2 only; dictionary + preview → Stage 10 only; Find/Replace + Styles dialogs → Stage 9-gated, not junior Stage 5; native + Graphite + Views deletion → Stage 13, not 10/9; ViewsInterfaces split to keep `IVwCacheDa`). +- **Missing dependency edges** (synthesis §7), most importantly **`S9 → S6`**. +- **A user decision must be re-opened — Graphite vs. Awami Nastaliq** — see §11. + +The per-stage prose in §6 below is **retained as the scope baseline**; where a review supersedes it, +the change is captured in the synthesis and flagged inline. Sub-epic decomposition (synthesis §6) is +the authoritative structure for JIRA. + +--- + +## 2. Guiding principles (research-grounded; some already chosen by the repo) + +1. **Strangler Fig, never big-bang.** Keep shipping FieldWorks on Windows; build Avalonia + surfaces beside the old ones; retire WinForms surface-by-surface behind a feature flag. + (Avalonia's own WinForms guide, Fowler, Spolsky/Netscape all converge here.) +2. **`WinFormsAvaloniaControlHost` is the coexistence spine.** Host whole Avalonia *views* + inside the running WinForms shell (already proven as `LexicalEditHostControl`). Windows-only + during transition is acceptable — FieldWorks targets Windows today. +3. **Decouple logic from the UI framework first.** The framework-free seams, typed IR, and + region models are reusable by both surfaces and are what make per-screen strangling possible. +4. **Prove behavior before replacing it.** Characterization/golden-master baselines (semantic + + visual + workflow + performance) gate every surface. This is non-negotiable *because* the work + is AI-assisted — the dominant AI failure mode is **hallucinated parity**. +5. **Reuse the frozen architecture; don't reinvent.** Typed IR, region/composer, owned dense + controls, plugin registry, seam catalog, surface-selection switch, Path-3 evidence bundle. +6. **Repo divergence from generic advice — managed rendering, not native interop.** Standard + advice is "keep the native engine via `NativeControlHost`, rewrite last." FieldWorks instead + bet on a **managed-only** path (no native Views on the Avalonia surface; managed `ITsString` + editing already works for fields). Tradeoff: more engine work up front, but it unblocks + cross-platform and avoids the airspace/transparency limits of native hosting. The long pole is + therefore *replacing* the Views engine for document surfaces, not hosting it. +7. **Editable virtualized grids are the #1 framework gap.** TreeDataGrid is display-only and + licensing-blocked; standard DataGrid is slow at scale. Plan an **owned virtualized control**. +8. **`AutomationId` is mandatory, day one, every control** — it is the single prerequisite for + both accessibility identity and Appium/WinAppDriver automation. +9. **One global undo/redo stack** (LCModel action handler). Never a parallel Avalonia history. +10. **Slice work as vertical, file-creating, independently-owned units.** New surfaces are new + files (conflict-free); defer host-wiring/config edits to a single integration step; cap + concurrency at ~4–6 active streams; give dialogs to juniors, engine/shell to seniors. +11. **Budget an explicit integration-and-verification phase per milestone.** Clean merges ≠ working + software (one practitioner spent ~83% of integration time post-merge even with zero conflicts). +12. **Upgrade the look while keeping the functionality.** This program is also chartered to modernize + the UX: adopt a modernized **Fluent-based** ControlTheme rather than mimicking the legacy WinForms + chrome. The hard contract is **functional fidelity + density**, not pixel- or look-for-look parity — + parity evidence asserts behavior/semantics/density, the visual lane allows an intentional restyle. +13. **Fully managed text — Graphite is being sunset.** No native Graphite (or native Views) shaping on + any Avalonia surface. Complex-script shaping is HarfBuzz/managed only. This supersedes the earlier + per-writing-system "classify-and-warn" compromise in `graphite-transition-support`: Graphite is + *removed*, not warned. +14. **Land .NET 10 + Avalonia 12 before the program closes** (its own stage). Sequenced *late* — after + the WinForms shell and most WinForms surfaces are gone — so the runtime jump ports surviving managed + code, not throwaway WinForms (same "don't invest in code being deleted" principle that froze DataTree). + +--- + +## 3. Current state (what is already done — "Stage 0") + +This is the proven baseline the program builds on. Source: `lexical-edit-avalonia-migration` +(Phases 0–1 complete, 2–8 mostly complete) and `avalonia-multi-writing-system-text-foundation` +(completed 2026-06-15). + +| Asset | Where | Reusable for | +| --- | --- | --- | +| Typed view-definition IR + XML importer + compiler (deterministic, cached, off-thread) | `Src/Common/FwAvalonia/ViewDefinition/` | Every surface driven by XML layouts | +| Region model + composer (boundary **above** DataTree) | `Src/xWorks/FullEntryRegionComposer.cs`, `Src/Common/FwAvalonia/Region/LexicalEditRegionModel.cs` | Detail/tree/browse surfaces | +| Owned dense controls (multi-WS text, chooser flyout, reference vector, dialog launcher) | `Src/Common/FwAvalonia/Region/FwFieldControls.cs`, `FwOptionPicker.cs`, `RegionMenuFlyout.cs` | All field/cell rendering | +| Managed multi-WS `ITsString` editing (fonts, RTL/bidi, IME, grapheme clusters, ghost realization) | `FwMultiWsTextField` | All text fields (no native Views) | +| Frozen seam contracts (edit session, undo, validation, scheduler, lifetime, refresh, navigation, command bridge, clipboard, drag-drop) | `Src/Common/FwAvalonia/Seams/ISeams.cs`, `ActiveHostContract.cs` | Every surface | +| Plugin registry for custom/legacy slice classes | `Src/xWorks/RegionEditorPlugins.cs`, `ChorusNotesPlugin.cs` | Custom slices anywhere | +| Surface-selection switch (explicit Supported / ExplicitLegacyFallback / Blocked per host) | `Src/Common/FwAvalonia/LexicalEditSurfaceSelectionService.cs`, `Src/xWorks/RecordEditView.cs` | Every host | +| In-process WinForms→Avalonia host bridge | `Src/Common/FwAvalonia/LexicalEditHostControl.cs` | Coexistence spine | +| Path-3 parity evidence harness (semantic + visual + workflow + performance), legacy timing baselines | `FwAvaloniaTests/Path3BundleTests.cs`, `DetailControlsTests/DataTreeTimingBaselines.json` | Every parity claim | +| Engine-isolation symbol audit (forbidden WinForms/Views/Graphite/Gecko symbols) | `FwAvaloniaTests/EngineIsolationAuditTests.cs` | Every migrated region | +| Region manifest template (gates, perf budgets, rollback) | `lexical-edit-avalonia-migration/region-manifest.md` | Every surface's definition-of-done | + +**Open items still inside Stage 0** (fold into Stage 4): table/browse virtualization (7.x), full +P0/P1 field parity beyond first slice, 150% DPI + typing-latency budgets, JSON view-definition +serialization/retirement (9.x), Path-3 completeness across all scenarios. + +--- + +## 4. The stages at a glance + +| # | Stage (→ JIRA epic) | Track | Depends on | Parallel? | Lead level | Exit gate | +| --- | --- | --- | --- | --- | --- | --- | +| 1 | Migration platform & developer-enablement kit | I Foundation | Stage 0 | — | Senior | Kit + runbook proven by a junior+Claude migrating a trivial surface end-to-end | +| 2 | Coexistence shell spine & host contracts | I Foundation | 1 | with 3 | Senior | Any Avalonia view hostable in WinForms shell behind the flag; theming + AutomationId conventions locked | +| 3 | Shared editable virtualized grid/tree control | I Foundation | 1 | with 2 | Senior | Owned control passes 10k-row browse + 253-slice tree at 100%/150% DPI within perf budget | +| 4 | Finish Lexical / Advanced Entry surface (exemplar) | II Surfaces | 2,3 | — | Senior | Region manifest fully green; becomes the reference implementation | +| 5 | Global dialogs & choosers (FwCoreDlgs + shared) | II Surfaces | 2 | yes (4–6 streams) | **Junior-friendly** | Each dialog: parity bundle + AutomationIds + localization; WinForms-owned modality honored | +| 6 | Lexicon completion + Grammar/Morphology detail | II Surfaces | 4 | yes | Mid | Detail surfaces at parity via region/composer + plugins | +| 7 | Texts & Words / Interlinear + Discourse | II Surfaces | 3,9 | partial | Senior | Interlinear/concordance/chart parity on managed document engine | +| 8 | Notebook, Lists, Dictionary-config UI, remaining tools | II Surfaces | 4,5 | yes | Mid | Remaining areas at parity | +| 9 | Managed document/text rendering & editing engine (Views replacement) | III Long pole | 1 | with II | Senior | Multi-paragraph/StText + structured editing parity; no native Views on Avalonia path | +| 10 | Browser/PDF & dictionary-preview replacement; **Graphite full removal** | III Long pole | 9 | with II | Senior | Gecko + Graphite **removed** from codebase; fully-managed shaping; preview/print parity | +| 11 | Application shell replacement (window, areas, menus, toolbars, lifetime) | IV Shell | 2, enough of 5–8 | partial | Senior | Avalonia shell hosts migrated screens; `fieldworks-avalonia-shell-migration` gates pass | +| 12 | **Runtime & toolchain modernization (.NET 10 + Avalonia 12)** | IV Shell | 11, most of 5–10 | — | Senior | Whole process on .NET 10 + Avalonia 12; build/test/CI green | +| 13 | Final cutover, native decommission & cross-platform enablement | IV Cutover | all | — | Senior | Avalonia default; WinForms + native Views/COM UI retired; Linux/macOS smoke green | + +Cross-cutting concerns (accessibility/UIA, localization, performance, parity evidence) are **not** +separate stages — they are continuous per-surface gates enforced by the harness from Stage 1 on. + +--- + +## 5. Sequencing and parallelism + +```mermaid +flowchart TB + S0["Stage 0 (done)\nLexical-edit foundation + first region"]:::done + + subgraph TI["Track I — Foundation (senior, sequential-ish)"] + S1["1. Platform & enablement kit"]:::found + S2["2. Coexistence shell spine + contracts"]:::found + S3["3. Shared editable virtualized grid/tree"]:::found + end + + subgraph TII["Track II — Surfaces (parallel hand-off)"] + S4["4. Finish Lexical/Advanced Entry (exemplar)"]:::surf + S5["5. Global dialogs & choosers (junior)"]:::surf + S6["6. Lexicon + Grammar/Morphology"]:::surf + S7["7. Texts & Words / Interlinear + Discourse"]:::surf + S8["8. Notebook/Lists/Dict-config/remaining"]:::surf + end + + subgraph TIII["Track III — Long pole (senior, parallel)"] + S9["9. Managed document/text engine (Views replacement)"]:::pole + S10["10. Browser/PDF + Graphite full removal"]:::pole + end + + subgraph TIV["Track IV — Shell, modernization & cutover"] + S11["11. Application shell replacement"]:::shell + S12["12. Runtime modernization (.NET 10 + Avalonia 12)"]:::shell + S13["13. Final cutover + native decommission + cross-platform"]:::cut + end + + S0 --> S1 --> S2 --> S4 + S1 --> S3 --> S4 + S1 --> S9 --> S10 + S2 --> S5 + S4 --> S6 + S3 --> S7 + S9 --> S7 + S4 --> S8 + S5 --> S8 + S5 & S6 & S7 & S8 --> S11 + S2 --> S11 + S11 --> S12 + S10 --> S12 + S12 --> S13 + + classDef done fill:#e2e8f0,stroke:#64748b,color:#0f172a; + classDef found fill:#fef9c3,stroke:#ca8a04,color:#422006; + classDef surf fill:#dcfce7,stroke:#16a34a,color:#052e16; + classDef pole fill:#f3e8ff,stroke:#7e22ce,color:#3b0764; + classDef shell fill:#dbeafe,stroke:#2563eb,color:#1e3a8a; + classDef cut fill:#fee2e2,stroke:#b91c1c,color:#450a0a; +``` + +**The critical path** is `Stage 1 → 2/3 → 4 → 11 → 12 → 13` for the shell/runtime, and +`Stage 1 → 9 → 10 → 13` for the native engine. Track II surfaces fan out wide and absorb most of the +parallel head-count. **The .NET 10 + Avalonia 12 jump (Stage 12) is deliberately late** — it runs after +the shell and most surfaces are Avalonia so it ports surviving code, not soon-to-be-deleted WinForms. +During coexistence the whole process stays on the current Avalonia 11.x / .NET Framework 4.8 (one CLR +per process); new code is written Avalonia-12-ready (avoiding APIs removed in 12) but the actual bump +ships in Stage 12. +**Hand-off to less-experienced developers begins after Stage 1 completes** (the kit exists) and +**accelerates after Stage 4** (a fully-worked exemplar exists to copy). + +--- + +## 6. Stage detail (JIRA epic → representative sub-tasks) + +Each stage is one **epic**. Sub-bullets are representative **issues** (real backlog will be finer). +Every surface-migrating issue inherits the per-region **Definition of Done** in §7. + +### Track I — Foundation (must precede broad hand-off) + +#### Stage 1 — Migration platform & developer-enablement kit *(senior)* +Turn the lexical-edit one-off into a reusable, documented platform a mid/junior dev + Claude can drive. +- Extract a reusable **region scaffolding** template (new-surface generator: composer skeleton, region + model, view, manifest stub, test bundle stub). +- Promote the **Path-3 evidence harness** to a shared test base any surface test can derive from. +- Freeze & document the **seam catalog** and **plugin-registry** onboarding ("how to add a custom slice"). +- Author a **"migrate-a-surface" runbook** mapping the 10-step workflow to concrete repo actions; wire it + to the `fieldworks-winforms-to-avalonia-migration` skill so AI assistants follow it. +- Lock **conventions**: AutomationId derivation from StableId, density tokens, ControlTheme baseline, + localization lanes (StringTable for labels, `FwAvaloniaStrings.resx` for product strings), ``. +- **Validation:** a junior+Claude migrates one trivial surface (e.g. a single simple dialog) end-to-end + using only the kit + runbook, with a green parity bundle. That is the gate. + +> **Post-review (reviews/stage-01).** Deliver **two kits**, not one: the **region/IR** scaffolding (feeds +> Stages 4/6) *and* an **MVVM-dialog** scaffolding (CommunityToolkit.Mvvm + compiled bindings + first +> `.axaml`; feeds Stage 5's ~200-dialog reservoir). The repo today has **zero `.axaml` and no +> CommunityToolkit.Mvvm** — this stage is the program's first XAML/MVVM adoption, so that toolchain (and a +> dialog-flavored evidence bundle without an IR semantic anchor) must be explicit here, not assumed in +> Stage 5. The validation gate (migrate a dialog) currently exercises only the dialog kit — add a region +> mini-surface so both are proven. "Freeze the seam catalog" → **"version + amendment protocol"** (it still +> grows via Stages 3/9). Mark the region template **provisional** until Stage 4 closes (its exemplar gates +> are still Partial). Much is already reusable (`FwFieldControls`/`FwOptionPicker` are surface-agnostic; +> AutomationId convention + `` already in place) — this is generalization, not green-field. + +#### Stage 2 — Coexistence shell spine & host contracts *(senior)* +Generalize the host bridge and surface switch beyond Lexical Edit so any surface can coexist. +- Generalize `LexicalEditHostControl` → a reusable `WinFormsAvaloniaControlHost`-based region host. +- Generalize `LexicalEditSurfaceSelectionService` → an app-wide surface registry/switch with explicit + per-host `Supported/ExplicitLegacyFallback/Blocked` decisions. +- Stand up the **XCore mediator/PropertyTable bridge** seam (`IXCoreCommandBridge`) for shell-scope commands. +- Global **feature-flag** plumbing (default WinForms) and the dual-run build. +- **ControlTheme** baseline matching legacy look + density; theming resource pipeline. +- Pull forward the **contract layer** of `fieldworks-avalonia-shell-migration` (window/dialog ownership + contracts) without yet replacing the shell. +- Apply `fieldworks-ui-wiring-review`. + +> **Post-review (reviews/stage-02).** Feasibility is high and the riskiest dependency is **already retired**: +> the build ships on net48 + Avalonia 11.3.17 with `WinFormsAvaloniaControlHost` in production. Five of six +> deliverables are **generalizations of existing code**, not net-new. **This stage — not Stage 1 — owns** the +> app-wide surface registry (resolve the Stage 1/2 double-booking) **and a living surface-census artifact** +> (the asset Stage 8's straggler-sweep presumes). **Name the exact ownership ports** Stage 2 extracts +> (lifetime, main-window, active-window registry, dialog owner, dispatcher, shutdown, modal state) and declare +> them the **single source of truth shared with Stage 11** — reuse `IUiScheduler`/`IRegionLifetime`, don't +> redefine. Disambiguate "dual-run build" as a **CI build matrix**, not just the `UIMode` preference. Fix +> `LexicalEditSurfaceResolver` so unregistered tools default to **legacy/blocked, never silent Avalonia**. All +> Stage-2 theming/host code must be **Av12-delta-localized** (theming APIs change 11→12). + +#### Stage 3 — Shared editable virtualized grid/tree control *(senior)* +The identified off-the-shelf gap; build it once, many surfaces depend on it. +- Spike & decide: owned virtualizing list/table over `VirtualizingStackPanel` vs. fully-owned realization + window (record the fired pivot trigger per `seam-catalog.md` §3 if deviating). +- Implement an **owned virtualized table** (browse/XMLViews) and **owned virtualized tree** (slice/detail, + flattened with expander/indent) with editing, selection, keyboard, and custom automation peers. +- Prove against **large fixtures**: 10k-row browse, 253-slice detail, at **100% and 150% DPI**, within the + measured legacy performance budget. +- **Do not** build on TreeDataGrid (display-only, licensing). Record the decision in the manifest. + +> **Post-review (mis-sized — reviews/stage-03).** The **tree half is largely already solved**: the detail +> surface is a flat indented stack (already rendered by `LexicalEditRegionView`, budget tops at 253 slices — +> below where virtualization matters) and the unbounded chooser is **already virtualized** (`FwOptionPicker`). +> Re-scope to **"editable virtualized *table* + indented row chrome."** The table is the real, unsolved work +> (`LexicalBrowseView` is **read-only**; legacy `BrowseViewer`/`BulkEditBar`/`FilterBar` are ~15K LOC of +> editing/bulk-edit/filter). Ship **consumer-gated sub-milestones**: **3a read@scale** (unblocks 7/8 display) +> → **3b editable cells** (unblocks Stage 4 — top priority) → **3c bulk-edit/checkbox/filter** (unblocks 8). +> The no-TreeDataGrid call is now **stronger** (FOSS repo archived 2025-10; editing behind a commercial +> license). Promote **custom AutomationPeer** to a first-class item (zero exist in-repo; virtualized UIA +> enumeration is non-trivial). Make the spike measure **scroll/expand on production fixtures**, not just +> realization count. + +### Track II — Surface migration (parallel, hand-off-friendly) + +#### Stage 4 — Finish the Lexical / Advanced Entry surface (the exemplar) *(senior)* +Close the open Stage-0 items so this region is 100% green and becomes the copy-me reference. +- Tables/browse in the entry view on the Stage-3 control (lexical-edit tasks 7.x). +- Full **P0/P1 field parity** beyond the first slice (custom fields, rich references, media/pronunciation). +- **150% DPI + scroll/expand/typing-latency** budgets (tasks 2.13, 7.7). +- **JSON view-definition** serialization + override migrator + runtime-XML-disable for the gated surface (9.x). +- Path-3 bundle completeness across all entry scenarios; region manifest fully green. + +> **Post-review (scope stale — reviews/stage-04).** Several items are already built: the **JSON serializer +> exists** (`ViewDefinitionJsonSerializer.cs`; the open 9.x work is the **override migrator** + +> **runtime-XML-disable** + override fixtures), the **browse table is built and 10k-row-proven** (open work +> = re-home onto the Stage-3 control at 150% DPI), and **custom fields / reference vectors / pictures / +> pronunciation already render** in the 2524-line `FullEntryRegionComposer`. Reword items 1 & 4 as +> **"re-home / finish," not "build."** The real heart is **latency + 150% DPI budgets** and flipping the +> manifest §6 rows (Layout/Validation/Accessibility/Performance) from **Partial** (today's verdict: "Default +> stays Legacy"). Add an explicit **exemplar-quality exit criterion** — unify the dual projector and document +> `RegionViewingServices` + the plugin burn-down as the **copy-me contract** — or Stages 6/8 will clone a +> 2524-line composer. Disambiguate the exit gate: **"manifest green + enable-able"** ≠ "enabled by default" +> (the latter wrongly inherits Stage 10/13 default-path validation). "Rich references" = reference vectors, +> **not** rich text; StText/ORC editing is **Stage 9**. + +#### Stage 5 — Global dialogs & choosers *(junior-friendly, 4–6 parallel streams)* +~200 dialogs; mostly mechanical, file-creating, low merge contention — the main hand-off reservoir. +- `Src/FwCoreDlgs/` first (most reused): Font, Styles, Apply-Style, Writing-System setup, New-Project + wizard, Project properties, Backup/Restore, Find/Replace, Valid-Characters, Chooser, converters. +- Shared chooser/launcher infrastructure (the `FwOptionPicker`/flyout pattern). +- Domain dialogs across `xWorks`, `LexText`, `FdoUi` as their owning areas migrate. +- **Coexistence rule:** until the shell migrates (Stage 11), anything modal stays a WinForms dialog owned + by the host form (`dialog-ownership.md`); new Avalonia dialog *content* is fine inside the host. +- Each dialog: parity bundle + AutomationIds + localization review. + +> **Post-review (modal tension resolved — reviews/stage-05).** The "no Avalonia modal windows during +> coexistence" rule does **not** block Stage 5. The mechanism is the **content/chrome split**: replace a +> dialog's *body* with an Avalonia view hosted via `WinFormsAvaloniaControlHost` inside a thin WinForms +> `Form` that still owns `ShowDialog`/modality. Make this **host-wrapped-body contract the first rule of the +> epic** (juniors must not write `new Window().ShowDialog()` — the unsupported path). `S5 → S11` is a +> *finishing* edge (Stage 11 later strips the WinForms wrapper), not a block. **Re-tier the backlog:** +> Tier A junior (small, Views-free), Tier B mid (New-Project wizard, WS-setup, Project-props, Valid-Chars), +> **Tier C → Stage 9** (`FwFindReplaceDlg`, `FwStylesDlg` host `IVwRootSite`/`SimpleRootSite` — *not* junior +> work). The dialog-authoring stack (CommunityToolkit.Mvvm, compiled bindings, dialog scaffolding) must come +> from Stage 1; allow a code-behind exception so proven owned controls embed in XAML dialogs without rewrite. + +#### Stage 6 — Lexicon completion + Grammar/Morphology detail *(mid)* +Detail-view-heavy areas that reuse region/composer + plugin registry directly. +- Lexicon (`Src/LexText/Lexicon`, `LexTextControls`) remaining slices/launchers (MSA, references, examples). +- Morphology (`Src/LexText/Morphology`): inflection features/classes, phonological environments, categories. +- Grammar detail editors via `FdoUi` editors (POS, inflection, phonological features). +- Custom slice classes → plugin registry with burn-down tracking. + +> **Post-review (mis-bundled across 3 substrates — reviews/stage-06).** Split into **6a** Lexicon detail +> (mid; truly reuses region/composer + plugin registry — references are already `LaneAbsorbed`), **6b** +> Morphology/grammar **document editors** (`InflAffixTemplateSlice`, `RuleFormulaSlice`×4, +> `PhEnvStrRepresentationSlice`, `InterlinearSlice` — these are **Views-based document surfaces; re-parent +> under Stage 9**), and **6c** the four `FdoUi` editors (**bulk-edit-bar controls on the browse surface → +> move to Stage 8**, gated on Stage 3). **Add the missing edges** `S9 → S6` (the plan's single biggest graph +> error), `S3 → S6`, `S5 → S6` (every MSA/feature launcher opens a dialog — row is 6, dialog body is 5). +> Promote `FwDialogLauncherField` to a first-class `RegionFieldKind`. Extend the existing +> `LexemeEditorBurnDownTests` census to morphology/grammar layouts to make burn-down concrete. + +#### Stage 7 — Texts & Words / Interlinear + Discourse *(senior; depends on Stage 9)* +The most complex non-shell surface; Views-engine-heavy document rendering. +- Interlinear doc + sandbox (`Src/LexText/Interlinear`, ~98 files): word/morpheme breakdown, glossing, POS. +- Concordance + raw-text + statistics views. +- Constituent charts (`Src/LexText/Discourse`): chart body, logic, export. +- Import wizards (SFM, LinguaLinks) — can be split to Stage-5-style hand-off. +- Depends on the managed document engine (Stage 9) and shared tables (Stage 3). + +> **Post-review (mis-sized; 5 surfaces, 1–2 orders of magnitude apart in Views coupling — reviews/stage-07).** +> Split: **7A interlinear + Sandbox** (~27K lines; the deepest Views construction in FieldWorks — private +> `VwCacheDa` via `CachePair`, hit-test combo editing, aligned grid; **senior; hard-blocks on extended Stage +> 9** — confirm 9.0 covers these constructs), **7B constituent chart** (`ConstituentChartLogic` is ~5.5K lines +> of **pure logic that ports as-is**; rendering is ~1.8K lines → **Stage 3 grid**, not Stage 9), **7C +> concordance/statistics** (`UserControl`, no `IVwEnv` in host — Stage-5-class), **7D import wizards** +> (junior/MVVM per §11.3). Only RawTextPane (plain StText) is clearly inside Stage 9 as currently scoped. +> Move 7C/7D off the Stage-9 critical path; rebalance 7B toward Stage 3. + +#### Stage 8 — Notebook, Lists, Dictionary-config UI, remaining tools *(mid)* +- Notebook area, Lists editors, bulk-edit surfaces. +- Dictionary configuration dialogs (`DictionaryConfigurationDlg` family) and config preview wiring. +- Remaining utilities/tools; sweep for stragglers via the surface registry. + +> **Post-review (grab-bag — reviews/stage-08).** Split **8a** Notebook/Lists/bulk-edit (region/composer + +> shared grid; **bulk-edit `BulkEditBar` is the real engineering item, gated on Stage 3**) and **8b** +> dictionary-config dialogs (already MVP with `IDictionary*View` + `*Controller` — unusually **MVVM-ready**; +> use the Stage-5 idiom, not the region pattern). **Move "config preview wiring" out — the preview is Gecko +> and belongs to Stage 10** (`DictionaryConfigurationDlg` hard-requires `GeckoWebBrowser`); the XHTML/CSS +> generators are framework-agnostic (reclassify non-UI). Add **Stage 3 and Stage 9** to the dependency row. +> The "straggler sweep" presumes a **living surface-census artifact** that does not exist yet — hoist it +> into Stage 1/2 (see Stage 2 post-review). + +### Track III — The long pole (native engine; senior, parallel with Track II) + +#### Stage 9 — Managed document/text rendering & editing engine (Views replacement) *(senior)* +Replace the native C++ Views engine for **document/multi-paragraph/structured** surfaces. (Field-level +multi-WS editing is already managed — `FwMultiWsTextField`.) +- **Spike first** (de-risk the #1 framework gap): mixed bidi (Arabic/Hebrew + Latin), CJK IME, custom + writing systems, multi-paragraph `StText` editing, selection across structured content. Decide build-vs-extend. +- **Fully managed shaping — no Graphite, no native Views.** Complex-script shaping is HarfBuzz/managed only + (decision confirmed 2026-06-15). Verify HarfBuzz coverage for the scripts Graphite formerly handled during + the spike; this is a gating risk for the Graphite sunset. +- Managed text layout/shaping on HarfBuzz/SkiaSharp (`TextLayout`); managed selection/caret model replacing + `VwSelection`; structured document model replacing `VwRootBox`/box hierarchy for the surfaces that need it. +- Editing path: keystroke/IME/clipboard/undo through the existing seams and one global undo stack. +- Validate against the rich-text/bidi/IME open-issue risks identified in research; keep the bridge coarse if + any residual native call survives transitionally. +- Forbidden-symbol audit stays green on every migrated surface. + +> **Post-review (decompose; scope is too narrow — synthesis §3/§6).** This is the program's long pole and +> is **not one stage**. Decompose: **9.0 spike + G0–G3 coverage scan (gate)** → **9.1 multi-paragraph +> `StText`** → **9.2 owned selection/caret model** (the ~14.4K-LOC `IVwSelection` replacement) → +> **9.3 owned `TextLayout`-based layout/box model** → **9.4 embedded objects / tables / footnotes / +> overlays**. Reframe scope as the **DELTA over the landed field foundation** (`FwMultiWsTextField` is stock +> `TextBox`-per-WS with bolt-on bidi/grapheme/`ITsString` write-back — *not* a managed engine; it explicitly +> deferred `StText` and ORC editing). **Critically, add the constructs Stage 7A needs** that the bullets +> above omit: a secondary in-memory presentation cache/decorator editing model (replacing +> `CachePair`/`VwCacheDa`), icon/picture-anchored hit-test combo editing, and the aligned multi-line +> interlinear grid layout — include a Sandbox/interlinear scenario in the 9.0 spike or 7A stalls late. +> Build-vs-extend: extend the foundation for 9.1; **build** an owned selection/layout layer for 9.2+ (open +> Avalonia bidi/caret `TextBox` defects don't scale to documents). Native `Src/views` deletion is **Stage 13**. + +#### Stage 10 — Browser/PDF & dictionary-preview replacement; **Graphite full removal** *(senior)* +- Replace Gecko/XULRunner-based dictionary preview & PDF export (`GeckoWebBrowser`, `GeckofxHtmlToPdf`). +- Managed print/preview parity. +- **Sunset Graphite entirely** (decision confirmed 2026-06-15): this supersedes + `graphite-transition-support`'s per-WS "classify-and-warn" compromise. Remove the native Graphite engine + (`GraphiteEngineClass`) and its references from the codebase, not just from the default path. Gated on + Stage 9 proving HarfBuzz/managed shaping covers the formerly-Graphite scripts. + +> **Post-review (split + correct — reviews/stage-10).** Split **10A** (Gecko/PDF/preview) and **10B** +> (Graphite). They have different gates and blast radius. **10A:** the preview is generated as managed +> XHTML/CSS (`LcmXhtmlGenerator`/`CssGenerator`) — Gecko only *displays* it, so replacement is "render this +> HTML" (Avalonia WebView in Av12, or CoreWebView2); **decouple the process-wide XULRunner bootstrap first** +> (`FieldWorks.cs` hard-fails without it); PDF is one call site → `CoreWebView2.PrintToPdfAsync`. **10B:** +> the `RenderEngineFactory` Graphite branch is shared by **legacy** WinForms+Views surfaces, so **native +> deletion must wait for Stage 13** (deleting at 10 breaks legacy); Stage 10B only removes Graphite from the +> *managed/Avalonia* path and runs the **G0–G3 classifier** as pre-removal evidence. **Keep +> `DefaultFontFeatures`** (LCModel-owned, reused for OpenType export — do not delete). Add superseded banners +> to `graphite-transition-support`'s four files. ⚠ See §11.1 — the **Awami Nastaliq** gap may block "full +> removal" outright. Note Av12's WebView (10A's preferred impl) creates an **`S10 ↔ S12` ordering tension** +> to resolve. + +### Track IV — Shell, runtime modernization & cutover + +> Track IV is ordered **shell → runtime jump → cutover** on purpose: replacing the WinForms shell (11) +> and surfaces first means the .NET 10 + Avalonia 12 jump (12) ports surviving managed code rather than +> WinForms that's about to be deleted. + +#### Stage 11 — Application shell replacement *(senior; = `fieldworks-avalonia-shell-migration` body)* +- Avalonia application lifetime, main-window ownership, multi-window, active-window tracking, shutdown. +- Compile `Language Explorer/Configuration/Main.xml` into typed shell definitions (commands, menus, context + menus, toolbars, sidebars, status, shortcuts, listeners, screen/tool registrations). +- Avalonia shell composition: navigation (replace `SilSidePane`/OutlookBar), content hosting, record/side + panes (replace `CollapsingSplitContainer`/`MultiPane`), menus/toolbars/status, diagnostics, accessibility. +- Retire `FlexUIAdapter` default behavior; route mediator/PropertyTable through the typed command bridge. +- Migrate screens area-by-area into the Avalonia main-screen registry. + +> **Post-review (a second program in one row — reviews/stage-11).** Decompose into **11a** app-lifetime/ +> windowing (critical path — re-homing the `Form dialogOwner` seams threaded through ~15 lifecycle methods; +> `xWindow.cs` *is* a 2,498-line `Form`), **11b** Main.xml typed-shell compiler (the *easy* part — reuses the +> `ViewDefinition/` pipeline; types already have runtime peers), **11c** command/state bridge (the seams +> already exist in `ISeams.cs`), **11d** navigation/panes (owned controls replacing SilSidePane/MultiPane/ +> CollapsingSplitContainer — comparable to the Stage-3 build), **11e** screen registry + area-by-area, **11f** +> startup/installer/default-switch + FlexUIAdapter (~3.2K lines) removal. State the **Stage 2/11 split +> explicitly** (2 = ports + contract design; 11 = implementation + shell-scope + default switch — *not* +> redefinition). Add a **dialog-modality re-host task in 11a** to flip Stage-5 content from WinForms-owned to +> `Window`-owned at the switch. Enumerate which areas must be Avalonia before the switch (Interlinear/Stage 7 +> is likely a legacy island). 11 code must be **Av12-delta-localized** (`XWindow : Form` + `Application.Run()` +> pin the process to net48/Av11 through all of 11). + +#### Stage 12 — Runtime & toolchain modernization (.NET 10 + Avalonia 12) *(senior)* +The chartered "move to modern tools" jump, sequenced late by design. +- Port the surviving managed codebase from **.NET Framework 4.8 → .NET 10** (WinForms-on-net48 host is gone + or nearly gone by now; any residual WinForms moves to WinForms-on-.NET 10, Windows-only). +- Bump **Avalonia 11.x → 12**; resolve breaking changes flagged during coexistence (new code was written + Avalonia-12-ready to minimize this). +- One CLR per process: this is a coordinated whole-process bump, not a per-project trickle. Land it behind + the green build/test/CI gate (apply `fieldworks-managed-netfx-review`). +- Prerequisite for cross-platform (net48 is Windows-only; .NET 10 is not). + +> **Post-review (synthesis §3/§5; reviews/stage-12).** The late, coordinated, one-CLR shape is **forced, +> not just wise**: Avalonia 12 dropped `netstandard2.0`/net48 (repo pins **Avalonia 11.3.17** with an +> in-repo comment to this effect), so the Av11→12 bump is *impossible* until the process leaves net48, +> which can't happen until the WinForms host (Stage 11) is gone. Corrections: (1) the repo is **uniformly +> net48 (130 projects, no net8 multi-targeting)** — this is a **single-target port**, not a "retarget +> multi-targeting"; the `fieldworks-managed-netfx-review` net48-vs-net8/C#7.3 premise is itself stale +> (repo defaults C#8). (2) Don't split net10 vs Av12 (coupled), but insert an **intermediate green +> checkpoint: net10 + Avalonia 11.3.17** (legal — 11.3.x is netstandard2.0) to decouple the two biggest +> risk sources. (3) **Add an explicit NUnit 3 → 4 deliverable** — Avalonia 12 headless requires NUnit 4 +> (repo pins NUnit 3.14.0 across ~40 test projects). (4) Restate "Av12-ready" as **"Av12-delta-localized"**: +> confine unavoidable 11-only APIs (clipboard `IDataObject`→`IAsyncDataTransfer`, binding, focus, theming) +> to named seams, enforced by a Stage 2/11 exit gate. + +#### Stage 13 — Final cutover, native decommission & cross-platform enablement *(senior)* +- Flip the global default to Avalonia after all region/shell manifests pass. +- Delete the WinForms shell, WinForms-only dialogs, DataTree/Slice, SimpleRootSite/RootSite, XMLViews, + and the WinForms↔Avalonia interop spine. +- Decommission native C++ **UI/render** projects (`Src/views/`, `ManagedVwDrawRootBuffered`) and the + `IVwRootBox`/`IVwGraphics`/`IVwEnv` COM surface; keep non-UI native/linguistics services (Kernel, Generic, + ICU, XAmple, encoding converters, parsers) behind service seams. +- Installer/packaging changes; remove Gecko harvest. +- **Cross-platform enablement** (now unblocked by the managed path + .NET 10 from Stage 12): Linux/macOS + build + headless + smoke. Held to this final stage by decision (2026-06-15) — no cross-platform validation + cost is incurred earlier in the program. +- Final cross-cutting gates: accessibility (Narrator/NVDA spot-checks), localization parity, performance. + +> **Post-review (five workstreams in one — reviews/stage-13).** Split **13a** flip + bake (flip `UIMode` +> default; **WinForms stays as live rollback**, reversible), **13b** decommission (**irreversible deletion**, +> gated on 13a's bake metric), **13c** cross-platform + final gates. The irreversible step must not share a +> stage with the reversible one it rolls back to. **Correct the COM-retirement scope:** `ViewsInterfaces.cs` +> defines render interfaces *alongside* `IVwCacheDa` (a **data-access** contract used by 45+ projects) — +> **split it; keep data-access behind a seam, delete only render interfaces** (`ITsString` is already safe, +> lives in `Src/Kernel`). Use a **leaf-first deletion runbook** (consumers → DetailControls/XMLViews → +> RootSite → SimpleRootSite → ManagedVwDrawRootBuffered → ViewsInterfaces split → `Src/views` → render COM); +> native Graphite deletes here too. `retire-linux-era-view-shims` lands **first** (preserves VwTextStore/ +> IViewInputMgr/ManagedVwDrawRootBuffered). **De-risk the cross-platform deferral within the decision:** run +> the OS-portable `Avalonia.Headless` lane on Linux CI **from Stage 1**, lint Windows-only APIs as code is +> written, stand up a Linux/macOS **compile-only** build at end of Stage 12, and budget an explicit +> integration-debug sub-phase in 13c. Linux/macOS **packaging is net-new** (WiX6 is Windows-only). + +--- + +## 7. Definition of Done (per surface — applies inside every Track-II/III issue) + +Reuse the frozen per-region checklist +(`.claude/skills/fieldworks-winforms-to-avalonia-migration/references/migration-checklist.md`). +Condensed gate: a surface is **migrated** only when — +1. Custom slice census taken; plugins exist or explicit "unsupported" rows render (never silent fallback). +2. Semantic + visual + workflow + performance baselines captured *before* refactor and matched after. +3. Seams reused from `ISeams.cs`; any new seam recorded in `seam-catalog.md` with a pivot trigger. +4. Owned-control choices per `architecture-patterns.md` §4; deviations justified by a fired pivot trigger. +5. Composer walks compiled IR; stable AutomationIds from StableId; ghost rows are runtime-only. +6. Explicit `HostUiBehavior` per host; full wiring path traced; active-host contract holds (no hidden DataTree/Views). +7. Path-3 parity bundle per scenario; perf ≤ legacy × 1.2 or accepted delta recorded; **100% + 150% DPI**. +8. Localization lanes correct; AutomationIds nonlocalized, Names localized. +9. `EngineIsolationAuditTests` + active-host contract tests pass; `./build.ps1` + `./test.ps1` green. +10. Retrospective folds new lessons back into the skill set in the **same** PR. + +**Evidence language is enforced:** a checked task whose evidence says *substitute / placeholder / +skipped / future / partial* is a review blocker (`parity-evidence.md`). + +--- + +## 8. Staffing & hand-off model + +- **Seniors (with Claude):** Stages 1, 2, 3, 4, 7, 9, 10, 11, 12, 13 — foundation, engine, shell, runtime + modernization, exemplar. +- **Mid (with Claude):** Stages 6, 8 — detail surfaces that follow the exemplar pattern. +- **Junior (with Claude):** Stage 5 dialog streams — high-volume, mechanical, well-fenced; the runbook + + exemplar make these safe. Cap at **4–6 parallel streams**; each stream owns whole files. +- **Integration owner:** one senior runs a per-milestone **integration-and-verification** pass (host wiring, + cross-surface refresh/undo, headless + screen-reader + perf) — clean merges are not "done." +- **Hand-off prerequisites:** Stage 1 (kit + runbook) before any junior work; Stage 4 (worked exemplar) + before scaling Track II head-count. + +--- + +## 9. Risk register (top risks + mitigation) + +| Risk | Likelihood | Impact | Mitigation | +| --- | --- | --- | --- | +| Rich-text / bidi / IME gaps in Avalonia for complex scripts | High | High | Managed text foundation already proven for fields; Stage 9 spike-first on document editing; keep coarse interop fallback if needed | +| Editable virtualized grid at scale | High | High | Stage 3 owns the control; validate on 10k-row/253-slice fixtures at 150% DPI before any dependent surface | +| AI "hallucinated parity" (claims done, isn't) | High | High | Mandatory Path-3 evidence bundle + evidence-language enforcement + integration owner verification | +| Native Views engine replacement underestimated (long pole) | Med | High | Senior-only; spike-first; runs in parallel so it doesn't block dialogs/detail surfaces | +| Merge contention across parallel teams | Med | Med | Vertical file-owning slices; defer wiring to integration step; ~4–6 stream cap | +| Coexistence threading/focus/modality bugs | Med | Med | WinForms owns all modality until Stage 11; `dialog-ownership.md` rules; finalizer-safe sync context | +| Scope drift / mixed PRs | Med | Med | `fieldworks-migration-scope-review`; one surface per PR; skill retrospective in same PR | +| Shell migration timing pulled too early | Low | High | Gate 11 on enough of Stages 5–8; existing roadmap already defers shell | +| Graphite-only scripts (Awami Nastaliq) lose support — **accepted loss** (decision §11.1) | High (confirmed) | Med (mitigated by comms) | Stage 9.0 G0–G3 scan enumerates exact dropped scripts; **document + notify affected users with migration guidance** before removal ships (Stage 10B/13 deliverable); native deletion stays Stage 13 | +| Stage 9 scope too narrow for Stage 7 interlinear/sandbox (in-memory cache, hit-test combo, aligned grid) | Med | High | Add interlinear/sandbox scenario to the Stage 9.0 spike; make the Stage 9↔7A engine seam explicit before freezing the 9 API | +| Stage decomposition deferred — coarse epics hide critical-path bottlenecks | Med | Med | Create JIRA epics from the sub-epic map (synthesis §6), not the one-row-per-stage table; ship consumer-gated sub-milestones (esp. Stage 3a/3b/3c) | +| .NET 10 / Avalonia 12 breaking changes ripple late | Med | Med | New code written Av12-ready; jump sequenced after WinForms is mostly gone so the port surface is smaller; `fieldworks-managed-netfx-review` | +| Cross-platform regressions surface only at the end | Med | Med | Accepted tradeoff (held to Stage 13 by decision); headless tests are cross-platform-capable from Stage 1 to catch logic regressions early even though OS smoke is deferred | + +--- + +## 10. JIRA structure suggestion + +- **1 program/initiative:** "FieldWorks → Avalonia complete migration." +- **Epics: use the sub-epic map in `reviews/00-cross-comparison-synthesis.md` §6**, not the one-row-per-stage + table in §4. Eight stages (3, 6, 7, 8, 9, 10, 11, 13) decompose into gated sub-epics (e.g. 3a/3b/3c, + 6a/6b/6c, 7A–7D, 9.0–9.4, 10A/10B, 11a–11f, 13a/13b/13c); the other five map 1:1. +- **Issues under each epic:** one per surface/dialog/control, carrying the §7 Definition of Done as a + checklist and the region-manifest fields as acceptance criteria. +- **Labels:** `track-foundation | track-surfaces | track-longpole | track-shell`, `lead-junior|mid|senior`, + `parallel-safe`, `parity-blocked-by:`. +- **Dependencies:** wire epic links per the §5 graph; mark Stage 1 as blocking all junior issues and + Stage 4 as the "scale-up" milestone. +- Existing OpenSpec changes map onto epics: `lexical-edit-avalonia-migration` → Stage 4 close-out; + `avalonia-multi-writing-system-text-foundation` → Stage 0/9; `graphite-transition-support` → Stage 10; + `fieldworks-avalonia-shell-migration` → Stage 11. + +--- + +## 11. Decisions (resolved 2026-06-15) + +1. **Graphite → fully removed, fully managed only. RESOLVED 2026-06-15: accept the loss, document + notify.** + The Stage 9 review confirmed HarfBuzz covers the large majority of formerly-Graphite scripts (SIL itself + dropped Graphite from Charis/Doulos v7 in 2025) **except Graphite-only scripts such as Awami Nastaliq + (Urdu/Arabic Nastaliq), which have no OpenType/HarfBuzz path.** The decision is to **remove Graphite + entirely anyway** — no escape-hatch, no gating on an external Nastaliq solution. **Obligation incurred:** + the program must (a) run the Stage 9.0 LDML **G0–G3 coverage scan** (salvaged from + `graphite-transition-support`) to enumerate the **exact** dropped-script list and affected projects, + (b) **document** the dropped scripts and (c) **notify affected users with migration guidance** before the + removal ships. This user-comms deliverable is now part of Stage 10B / Stage 13, not optional. Engineering + sequencing unchanged: Stage 10B removes Graphite from the managed/Avalonia path; **native + `GraphiteEngineClass` deletion stays in Stage 13** (deleting earlier breaks *legacy* surfaces that still + use it during coexistence). +2. **Cross-platform held to the final stage (13).** No Linux/macOS validation cost is incurred earlier; + headless tests stay cross-platform-capable so logic regressions are still caught during the program. +3. **New standalone dialogs/wizards use modern Avalonia MVVM — CommunityToolkit.Mvvm + compiled bindings — + NOT the region/composer pattern.** *Rationale:* the region/IR/owned-control machinery exists for surfaces + driven by FieldWorks' **XML view-definitions** (the entry/detail/browse views) — it compiles XML layouts + into a typed IR and data-binds dense owned controls. **Dialogs and wizards have no XML layout to compile**; + they are hand-authored UI with bespoke logic, so forcing them through the IR machinery is a misfit and + pure overhead. Idiomatic MVVM fits them, and aligns with the "migrate to modern tools" charter: + *CommunityToolkit.Mvvm* gives source-generated observable properties/commands (less boilerplate, gentler + curve for less-experienced devs), and *compiled bindings* (`x:CompileBindings`) make bindings + statically checked and refactor-safe — which catches a whole class of AI-introduced binding errors at + build time. The **owned writing-system-aware field controls** (`FwMultiWsTextField`, `FwOptionPicker`) + are still reused *inside* dialogs wherever WS-aware text or chooser fields appear. Net rule: + **region pattern for IR-driven surfaces; MVVM + compiled bindings for dialogs/wizards/shell.** + **RESOLVED 2026-06-15 — the XAML-compiler/MSBuild question is decided: yes, adopt Avalonia XAML + + CommunityToolkit.Mvvm.** To keep the documented "pure-C#, no XAML" guarantee of the **foundation** + project (`Src/Common/FwAvalonia`) intact, XAML/MVVM dialogs live in a **dedicated XAML-enabled project** + (e.g. `Src/Common/FwAvaloniaDialogs`), not in the foundation. That project enables the Avalonia XAML + compiler + `EnableDefaultAvaloniaItems` + compiled bindings (`x:CompileBindings`) and references + CommunityToolkit.Mvvm (added to `Directory.Packages.props`). The owned field controls remain consumable + from the foundation. The one-time MSBuild integration (proving Avalonia's XAML targets compose with the + repo's customized build on net48) is the **first task of Stage 1.2** and the gating spike for Stage 5 — + it is now unblocked (decision made), no longer "needs an owner decision." +4. **Upgrade the look; keep the functionality.** Adopt a modernized **Fluent-based** ControlTheme rather + than mimicking the legacy WinForms chrome. The contract is functional fidelity + density (asserted by + the semantic/workflow/perf parity lanes); the visual lane permits an intentional restyle. This is the + chartered UX upgrade, not a regression. +5. **150% DPI parity deferred to post-100%-conversion (decided 2026-06-15).** During coexistence, DPI + problems are handled by the **WinForms fallback** (a user on a scaled display can run WinForms-only), + so 150% DPI mixed-mode parity is **not a coexistence/Stage-4 gate**. Full 150% DPI validation happens + once the app is fully Avalonia — there is then no mixed-mode WinForms-vs-Avalonia DPI surface to + reconcile, and it can be tested properly end-to-end. (Stage 4's exit stays "manifest green + + enable-able"; the DPI lane is explicitly out of that gate.) +6. **Standard Avalonia input paths first; custom IME is "do not build unless there is no other way" + (decided 2026-06-15).** All text input rides the **stock Avalonia `TextBox`** (TSF on Windows, IBus on + Linux) + **libpalaso per-writing-system keyboard activation, including Keyman keyboards** — the platform + and Keyman do the input-method work. The managed `RegionImeCompositionState` composition model is + **forward foundation only and is consciously NOT wired** onto the live input path (**task 18.10 = will + not build** unless the standard path is *demonstrated* insufficient for a specific scenario, verified on + a real desktop with the relevant Keyman/IME keyboard). *Rationale:* FieldWorks' historical custom IME + (`VwTextStore`, the IBus handler) existed only because the native **Views** editing surface was + non-standard and couldn't receive platform input on its own; a standard control offloads that to the OS + + Keyman, so custom composition is unnecessary by default. **Document/Sandbox surfaces (Stages 7/9) + should likewise move toward standard input controls** where feasible (so they too inherit platform IME), + rather than re-creating a custom text store — a *later* goal, not now. + +### Remaining open question (spike decides, not blocking epic creation) + +- **Document-engine build-vs-extend (Stage 9):** fully-managed rewrite of the document/structured-text + surfaces vs. extending an existing managed editor. The Stage 9 spike output decides; both paths are + fully managed (no native Views, no Graphite) per decision 1. + +--- + +## Appendix — external research sources (high-signal) + +- Avalonia official **WinForms migration guide** (incremental; `WinFormsAvaloniaControlHost`): https://docs.avaloniaui.net/docs/migration/winforms/ +- Avalonia **native interop / NativeControlHost** (airspace limits): https://docs.avaloniaui.net/docs/app-development/native-interop +- Avalonia **XPF** (WPF-compat product — *not* applicable to WinForms origin): https://docs.avaloniaui.net/xpf/welcome +- **TreeDataGrid** (display-only, no editing): https://avaloniaui.net/blog/announcing-the-release-of-treedatagrid +- Avalonia **rich-text editor** (Pro tier; RTL unverified): https://avaloniaui.net/blog/rich-text-editor +- **AvaloniaEdit** (code editor, not rich text): https://github.com/AvaloniaUI/AvaloniaEdit +- Avalonia **headless testing**: https://docs.avaloniaui.net/docs/concepts/headless/ • **accessibility**: https://docs.avaloniaui.net/docs/app-development/accessibility • **Appium UI testing**: https://docs.avaloniaui.net/docs/testing/ui-testing-with-appium • **ControlThemes**: https://docs.avaloniaui.net/docs/basics/user-interface/styling/control-themes +- **Strangler Fig** (Fowler): https://martinfowler.com/bliki/StranglerFigApplication.html • **Branch by Abstraction**: https://martinfowler.com/bliki/BranchByAbstraction.html +- **Working Effectively with Legacy Code** (seams) summary: https://understandlegacycode.com/blog/key-points-of-working-effectively-with-legacy-code/ • **Characterization tests**: https://en.wikipedia.org/wiki/Characterization_test +- **Joel Spolsky — never rewrite from scratch**: https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/ • **Netscape 6**: https://en.wikipedia.org/wiki/Netscape_6 +- **JetBrains WPF→Avalonia** case: https://avaloniaui.net/success/jetbrains • WinForms→Avalonia "near-100% rewrite" maintainer note: https://github.com/AvaloniaUI/Avalonia/discussions/11104 • **Expert guide to porting** (~9 hrs/view): https://avaloniaui.net/blog/the-expert-guide-to-porting-wpf-applications-to-avalonia +- **C++/CLI interop perf**: https://learn.microsoft.com/en-us/cpp/dotnet/performance-considerations-for-interop-cpp +- **Google LLM migration at scale** (~50% time saved, checkpoints+review): https://getdx.com/research/migrating-code-at-scale-with-llms-at-google/ +- **Vertical slicing / parallel dev**: https://medium.com/@kmorpex/vertical-slicing-the-key-to-better-net-projects-991c1c757393 • zero-conflict architecture: https://dev.to/aviad_rozenhek_cba37e0660/zero-conflict-architecture-the-8020-of-parallel-development-5aok + +> Caveat: the richest WinForms→Avalonia case studies are vendor-published (avaloniaui.net) and +> promotional in framing; independent numbers-rich retrospectives are scarce. FieldWorks may become +> one of the more substantial public examples. diff --git a/openspec/changes/avalonia-migration-roadmap/epics/PHASES-1-4-COMPLETION-LEDGER.md b/openspec/changes/avalonia-migration-roadmap/epics/PHASES-1-4-COMPLETION-LEDGER.md new file mode 100644 index 0000000000..c03babcff7 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/PHASES-1-4-COMPLETION-LEDGER.md @@ -0,0 +1,127 @@ +# Phases 1–4 — completion ledger (in-session) + +> **Close-out update (2026-06-16).** Two items previously bucketed as blocked/unbuilt have since +> **landed and are integrated on this branch**, so the buckets below are partly stale (corrected +> inline): +> - **Stage 3 editable virtualized table (3a/3b/3c + AutomationPeers)** — built and consolidated +> (`LexicalBrowseView` + capability interfaces + peer, `ClerkBrowseRowSource`/`ClerkBrowseEditContext`, +> `LexicalBrowseHostControl`, `RecordBrowseView` wiring behind `ResolveBrowse`/`UIMode=New`). No longer +> "Bucket D / not a session." +> - **Dialog-MVVM kit (1.2)** — the net48 compiled-bindings spike is done and verified +> (`FwAvaloniaDialogs` builds green through `build.ps1`; first dialog Tools→Options; dialog suite 9/9). +> No longer "unbuilt — top item." +> +> A multi-layer close-out review this session (architecture, dead/POC/naming, test coverage, +> completeness) found: **no dead/POC/spike code in production assemblies**, naming sound, OpenSpec +> `--strict` valid for both changes, **no overclaims**. Cleanups applied off the review: reparented +> `LexicalBrowseHostControl` onto the shared `AvaloniaRegionHostControl` base (removed duplicate Avalonia +> bootstrap + restored directional-key interop) and unified the surface-resolve precedence +> (`ResolveFromPreference`). Remaining xWorks browse-class test coverage and the full native+managed +> traversal are tracked below. + +> Honest definition-of-done for the "finish phases 1–4" pass, under the user's directive +> **"do everything feasible, skip blocked."** Buckets every item as **verified-done this session**, +> **already built (pre-existing)**, **feasible-remaining (tracked follow-up)**, or **blocked +> (out-of-session by nature)**. Phases 1–4 are **spec-complete** (finished epic docs +> `stage-01..04-*.md`); this ledger tracks the *code*. + +## A. Verified done this session (built + tests green) +| Item | Stage | Evidence | +| --- | --- | --- | +| Resolver tool-gating contract tests — unregistered tool never silently resolves to Avalonia (incl. override/New cases); null = "no tool gate" documented | 2.2 | `FwAvaloniaTests/LexicalEditSurfaceResolverTests.cs`; `.\test.ps1 -SkipNative -TestProject FwAvaloniaTests` → **20/20 passed** | +| Owned-control AutomationId convention made executable (stable id stamp + localized Name + per-WS suffix) | 1.5 | `FwAvaloniaTests/OwnedControlAutomationConventionTests.cs` → **2/2 passed** (headless `[AvaloniaTest]`) | +| Confirmed the in-progress viewing-replacement unit compiles + passes | 1/4 | `RegionViewingServices.cs` + `RegionViewingServiceReplacementTests.cs` green in baseline run | +| **View-definition override differ** — base vs customized IR → sparse StableId-keyed patch with the full representable op set (**setVisibility, setLabel, reorderChildren, hideNode, addNode** with parent+index); only changed binding/editor/kind remains a diagnostic, never a silent drop | 4.3 / task 9.2 (core) | `ViewDefinition/ViewDefinitionOverrideDiffer.cs` + `FwAvaloniaTests/ViewDefinitionOverrideDifferTests.cs` → **8/8 passed** | +| **Override JSON wire format** — deterministic canonical serialization of the sparse patch (mirrors `ViewDefinitionJsonSerializer`: Newtonsoft, ordered keys, defaults omitted, formatVersion header) + lossless round-trip of all op kinds incl. addNode + audit diagnostics; wrong-version guard | 4.3 / task 9.2 (wire format) | `ViewDefinition/ViewDefinitionOverrideJsonSerializer.cs` + `FwAvaloniaTests/ViewDefinitionOverrideJsonSerializerTests.cs` → **6/6 passed** | +| **Override applier (load side)** — applies a sparse patch to a shipped model to produce the customized model; inverse of the differ (`Apply(base, Diff(base, custom)) == custom` proven for representable changes); stale patch targets reported as diagnostics, not fatal | 4.3 / task 9.2 (apply) | `ViewDefinition/ViewDefinitionOverrideApplier.cs` + `FwAvaloniaTests/ViewDefinitionOverrideApplierTests.cs` → **5/5 passed** | +| **`.fwlayout` migrator core** — imports a shipped layout + a project's customized copy via the real `XmlLayoutImporter` and diffs them into a sparse patch (+ `MigrateLayoutToJson`); the framework-neutral core of the legacy-override → sparse-patch migration | 4.3 / task 9.2 (driver) | `ViewDefinition/ViewDefinitionOverrideMigrator.cs` + `FwAvaloniaTests/ViewDefinitionOverrideMigratorTests.cs` → **3/3 passed** | +| **`.fwlayout` file-I/O driver** — reads a project's whole-copy override file from disk, diffs vs the shipped layout, writes the canonical JSON patch file (shipped-layout + parts injected, so only the `Inventory` lookup is left to XCore) | 4.3 / task 9.2 (file driver) | `ViewDefinition/ViewDefinitionOverrideFileMigrator.cs` + `FwAvaloniaTests/ViewDefinitionOverrideFileMigratorTests.cs` → **3/3 passed** (override suite **28/28**, verified with temp files) | +| **Runtime-XML-disable loader (9.4)** — a gated/migrated surface loads committed canonical JSON instead of runtime XML, with the XML compile retained as the audit/fallback lane; missing/invalid JSON falls back with an explicit diagnostic, never silent | 4.4 / task 9.4 | `ViewDefinition/ViewDefinitionLoader.cs` + `FwAvaloniaTests/ViewDefinitionLoaderTests.cs` → **4/4 passed** (whole ViewDefinition area **42/42**) | +| **`duplicateNode` op** — completes the design's full representable override op set (setVisibility/setLabel/reorderChildren/hideNode/addNode/duplicateNode); leaf copy-with-new-id; subtree/missing source → diagnostics | 4.3 / task 9.2 (op set) | applier + serializer + tests → override suite **25/25 passed** | +| **App-wide surface registry (2.2)** — generalizes the resolver's hardcoded supported-tool list into an injectable registry; tools opt in by registration; unregistered tools never resolve to Avalonia; resolver refactored with zero regression | 2.2 | `LexicalEditSurfaceRegistry.cs` + resolver overloads + tests → LexicalEditSurface suite **35/35 passed** | +| **Surface census (2.2)** — the living UI-surface inventory + migration-state tracker that Stage 8's straggler sweep reconciles against | 2.2 | `epics/SURFACE-CENSUS.md` (doc artifact) | +| **Dual-projector unify (18.11)** — extracted `RegionStructureProjector` owning the section-header construction + child-indent rule; **both** the thin `LexicalEditRegionMapper` (FwAvalonia) and the full `FullEntryRegionComposer` (xWorks) now route through it (editor→kind already shared via `EditorKindMap`). Behavior-preserving by construction; verified on both paths | 4.5 / task 18.11 | `Region/RegionStructureProjector.cs` + `RegionStructureProjectorTests.cs`; FwAvalonia region **91/91** + xWorks composer region **137/137** passed | +| **Host generalization (2.1)** — extracted the reusable `AvaloniaRegionHostControl` base (Avalonia bootstrap, companion strip, WinForms/Avalonia directional-key interop, focus-safe content swap, context menus, message/clear) out of `LexicalEditHostControl`, which now adds only the lexical-edit `ShowRegion` + splitter memory; public API preserved (RecordEditView unchanged) | 2.1 | `AvaloniaRegionHostControl.cs` + slimmed `LexicalEditHostControl.cs`; build-verified across the solution + host/surface/region **134/134** + xWorks host-contract/switch **10/10** (interop test re-pointed at the base) | +| **9.2 `.fwlayout` Inventory glue** — `LexicalEditOverrideMigration` (xWorks): bridges the live `Inventory` (parts root + shipped layout node) to the tested migration core. Resolved the earlier "baseline" concern by keeping the shipped layout a **caller-provided** param (the adapter doesn't decide the baseline) — a clean composition of tested parts | 4.3 / task 9.2 (xWorks adapter) | `Src/xWorks/LexicalEditOverrideMigration.cs` + `LexicalEditOverrideMigrationTests.cs`; XElement core **2/2** + Inventory overload build-verified by the xWorks build. (Only the caller's choice of *pristine* shipped-layout source needs a real-project smoke test.) | + +> Finding (no code change needed): the resolver's **safety property already held** — non-null +> unrecognized tools already return WinForms. The gap was *test coverage*, now closed. Changing the +> `null`→permissive behavior would have been a regression (existing `Resolve()` tests depend on it), not a fix. + +## B. Already built (pre-existing — counted toward "finished") +- Region pipeline, owned controls, surface switch, host bridge, **JSON view-definition serializer** + (`ViewDefinitionJsonSerializer.cs`), engine-isolation audit, Path-3 evidence harness, + multi-WS text foundation. 90%+ of `lexical-edit-avalonia-migration/tasks.md` is `[x]`. + +## C. Feasible-remaining + +> **Progress (2026-06-15):** the cleanly-verifiable, framework-neutral portion of bucket C is now +> **DONE & verified** — the entire **9.2 override system** (differ + serializer + applier + `.fwlayout` +> migrate core + full op set incl. addNode/duplicateNode), the **9.4 loader**, and **2.2** (surface +> registry + census). What remains is **heavier integration that cannot be cleanly verified in the +> headless FwAvalonia test lane in-session** (cross-project xWorks builds, WinForms/realized-UI coupling, +> or central build-file changes) — listed below with the specific reason. Each is a focused effort, not +> a refusal: point me at one and I'll do it with proper verification. + +| Item | Stage/task | Why not done in-session | +| --- | --- | --- | +| Unify the dual projector into one shared structural projector — **DONE & verified** (`RegionStructureProjector`; both mapper + composer route through it; 91/91 + 137/137) | 4.5 / task 18.11 | **Done** — header construction + indent rule unified; editor→kind already shared; product path behavior-preserved | +| XML→typed override migrator — **DONE & verified**: differ + JSON wire format + applier + full op set + `.fwlayout` file-I/O driver (override suite 28/28) **+ xWorks `Inventory` adapter** (`LexicalEditOverrideMigration`, core 2/2 + build-verified). Only the caller's choice of *pristine* shipped-layout source needs a real-project smoke test | 4.3 / task 9.2 | **Done** end-to-end (logic verified; one caller-side baseline-source smoke test remains) | +| Runtime-XML-disable for the gated surface — **DELIVERED & verified** (`ViewDefinitionLoader`, 4/4): gated surface prefers committed JSON, XML retained as audit/fallback with explicit diagnostics | 4.4 / task 9.4 | **Done** (framework-neutral core); only the XCore gate-source + JSON-file-location wrapper remains | +| End-to-end IME compose/cancel/commit wiring → **moved to bucket D (discovered verification blocker)** | 4.6 / task 18.10 | The `RegionImeCompositionState` machine is already unit-tested; *wiring it to replace the working native TextBox IME* needs real IME input on a realized desktop surface — see bucket D | +| Region/IR scaffolding generator + dialog MVVM kit → **moved to bucket D (documented architectural decision)** | 1.1 / 1.2 | `FwAvalonia.csproj` lines 14-16 deliberately keep the project XAML-free — see bucket D | +| **Host generalization (2.1) — DONE & verified** (`AvaloniaRegionHostControl`); surface registry + census (2.2) **DONE** | 2.1 / 2.2 | **Done** — reusable host base extracted, 134/134 | + +## D. Blocked — needs a verification environment / decision not present in this session +| Item | Stage | Reason | +| --- | --- | --- | +| Editable virtualized **table** (3a/3b/3c) + custom AutomationPeers | 3 | **DONE & integrated (2026-06-16)** — built over a separate effort and consolidated onto this branch: `LexicalBrowseView` (sort/selection/keyboard/density, in-cell edit, checkbox, filter, multi-sort, bulk-edit) + `BrowseTableAutomationPeer`/`BrowseRowAutomationPeer`, product-wired via `ClerkBrowseRowSource`/`ClerkBrowseEditContext`/`LexicalBrowseHostControl`/`RecordBrowseView` behind `ResolveBrowse`. Remaining: desktop UIA2/FlaUI evidence (3.3), xWorks browse-class unit tests, the 10k real-DPI perf budget (2.7), and live FLEx verification. | +| 150% DPI parity (mixed-mode coexistence) | 4.2 / task 7.7 (manifest §5.4) | **DEFERRED by decision 2026-06-15** to post-100%-conversion. During coexistence, DPI issues are mitigated by the **WinForms fallback** (users revert to WinForms-only); full 150% DPI parity is validated once the app is fully Avalonia (no mixed-mode DPI surface to compare). Not a coexistence/Stage-4 gate. | +| scroll/expand/typing-latency budgets | 4.2 / task 7.7 (manifest §5.4) | Perf-measurement items needing a named-machine baseline; partial open-time budgets already measured. Not blocking the Stage-4 *enable-able* gate. | +| **18.10 IME custom composition wiring — WILL NOT BUILD (conscious decision 2026-06-15)** | 4.6 / task 18.10 | **Policy: "do not build unless there is no other way."** Text input rides the **stock Avalonia `TextBox`** (TSF/IBus) + **libpalaso per-WS keyboard activation incl. Keyman** — the platform + Keyman do the input-method work, so ordinary IME/Keyman typing already works without custom code. `RegionImeCompositionState` stays **forward foundation, consciously un-wired**. The historical custom IME (`VwTextStore`/IBus handler) existed only because the native Views surface was non-standard; a standard control offloads input to the OS + Keyman. Build explicit composition control **only if** the standard path is *demonstrated* insufficient for a specific scenario (verified on a real desktop with the relevant Keyman/IME). Not a gate; not a blocker — a deliberate non-goal. | +| **1.1/1.2 dialog MVVM / XAML kit** | 1.1 / 1.2 | **DECISION MADE 2026-06-15: adopt MVVM (Avalonia XAML + CommunityToolkit.Mvvm + compiled bindings) in a dedicated XAML-enabled project (`FwAvaloniaDialogs`), keeping the foundation pure-C#.** No longer decision-blocked. Remaining is implementation: the one-time MSBuild/XAML-compiler integration spike on net48 (Stage 1.2 first task) + the scaffolding generator — ready to build, not blocked. | +| Full `./build.ps1` + `./test.ps1` native+managed traversal | task 18.12 | Heavy full-graph run; partial targeted runs done this session. | + +## Verdict (updated 2026-06-15 — blockers resolved via decisions) + +The foundation/exemplar work of phases 1–4 is **substantially complete and verified**, and the former +blockers are now **cleared by decision or in progress** — none remain as hard "can't proceed" blockers: + +- **XAML/dialog-MVVM** → **decided** (MVVM; dedicated `FwAvaloniaDialogs` project). Unblocked. +- **150% DPI** → **deferred by decision** to post-100%-conversion; WinForms fallback covers coexistence. Not a gate. +- **18.10 IME** → **conscious non-goal** ("do not build unless there is no other way"); standard TextBox + Keyman path. Not a gate. +- **9.2 Inventory baseline** → **reframed + built** (caller-provides-baseline); only a caller-side smoke test remains. +- **Stage 3 editable grid** → **landed & integrated (2026-06-16)** — the consolidated browse table is on this branch (see the close-out update at top and Bucket D). Remaining is verification/evidence, not the build. + +**Remaining to call phases 1–4 done (ready work + verification, not blockers):** +1. **Dialog MVVM kit follow-ons** — the net48 XAML-compiler/MSBuild spike + `FwAvaloniaDialogs` + first dialog are **done & verified** (dialog suite 9/9). Remaining: the host-modal WinForms wrapper for coexistence, the localization lane, and the dialog scaffolding generator (Stage 1.2 acceptance; gates Stage 5). +2. **Stage 4 manifest finalization** — flip region-manifest §6 rows once Stage 3 lands the entry tables (DPI lane excluded by decision §11.5). +3. **Full `./build.ps1` + `./test.ps1` traversal** (18.12) as the final 1–4 verification (this session ran targeted suites, all green per-component). +4. Minor setup: dual-run CI matrix, modernized-Fluent theme baseline, the 9.2 caller-side pristine-baseline smoke test. + +## Close-out review follow-ups (2026-06-16) + +Findings from the multi-layer close-out review, with what was done and what is deliberately deferred. + +**Done this review:** reparented `LexicalBrowseHostControl` onto `AvaloniaRegionHostControl` (removed +duplicate Avalonia bootstrap; restored directional-key interop — fixes a latent arrow-key-swallow bug +when the table has focus); unified the surface-resolve precedence (`ResolveFromPreference`, shared by +`Resolve`/`ResolveBrowse`); browse gate now covers `lexiconEdit` + `lexiconBrowse`; row density pinned +to `FwAvaloniaDensity` tokens on the integrated view; added `ClerkBrowseEditContextTests` (row-switch = +separate undo steps; non-LexEntry/invalid = no-op); renamed the `SenseTreeSpike` fixture; reconciled the +stale ledger/epic claims + test counts above. + +**Tracked follow-ups (not close-out blockers):** +- **xWorks browse test coverage** — `ClerkBrowseEditContext` is now unit-tested. `ClerkBrowseRowSource` + (filter index-map / `HvoAt` remap / sort-clears-filter) and the `RecordBrowseView` selection-mirror + echo-guard are **build-verified only**: both are coupled to a live `BrowseViewer`/clerk, so they are + desktop/integration-lane tests, not headless. Add when the desktop UIA2/FlaUI lane (3.3) is set up. +- **M3 — `IRegionEditContext` is monolithic** (text/rich/option/reference/validate in one interface); + the browse contexts stub the reference verbs. Consider splitting into capability interfaces (mirroring + the browse `IBrowseSortSource`/`IBrowseFilterSource` pattern) **before Stage 5** adds chooser/dialog + edit surfaces. Watch item, not a defect. +- **M4 — `FullEntryRegionComposer` (~2,500 lines)** routes shared rules through `RegionStructureProjector` + + `EditorKindMap` but keeps all per-field-kind walkers in one class. Extract a per-kind dispatch table + before adding the next object type in Stage 5+. Watch item. +- **Filter perf** — `ClerkBrowseRowSource.RebuildFilter` scans the whole clerk list (per-cell finder + eval) per keystroke; fine for typical lists, but fold into the 10k real-DPI scroll/filter budget (2.7). diff --git a/openspec/changes/avalonia-migration-roadmap/epics/SURFACE-CENSUS.md b/openspec/changes/avalonia-migration-roadmap/epics/SURFACE-CENSUS.md new file mode 100644 index 0000000000..43db4eb55e --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/SURFACE-CENSUS.md @@ -0,0 +1,61 @@ +# FieldWorks UI surface census (living artifact) + +> Stage 2.2 deliverable: the single inventory the migration tracks burn-down against, and the backstop +> Stage 8's "straggler sweep" reconciles against. Update the **State** column as surfaces migrate. State +> values: `legacy` (WinForms only), `coexist` (Avalonia behind the flag), `migrated` (Avalonia default), +> `retired` (legacy deleted). This is a tracking artifact, not a spec — the per-surface plans live in the +> stage epics and `reviews/`. + +## Shell & framework +| Surface | Project / key files | Stage | State | +| --- | --- | --- | --- | +| App lifetime / startup | `Src/Common/FieldWorks/FieldWorks.cs`, `Src/Common/Framework/` | 11a | legacy | +| Main window | `Src/XCore/xWindow.cs` (2,498 lines, `: Form`), `Src/xWorks/FwXWindow.cs` | 11a | legacy | +| Mediator / PropertyTable | `Src/XCore/xCoreInterfaces/` | 11c (bridge: 2.3) | legacy (bridge seam exists) | +| Sidebar / navigation | `Src/XCore/SilSidePane/`, OutlookBar | 11d | legacy | +| Panes / splitters | `Src/XCore/CollapsingSplitContainer.cs`, `MultiPane.cs`, `PaneBarContainer.cs` | 11d | legacy | +| Menus / toolbars / status | `Src/XCore/FlexUIAdapter/`, `Inventory.cs`, `Main.xml` | 11b/11f | legacy | +| Surface selection switch | `Src/Common/FwAvalonia/LexicalEditSurfaceResolver.cs` + `LexicalEditSurfaceRegistry.cs` | 2.2 | **registry done** | +| Coexistence host bridge | `Src/Common/FwAvalonia/LexicalEditHostControl.cs` | 2.1 | coexist (lexical-edit only; generalization open) | + +## Detail / browse frameworks +| Surface | Project / key files | Stage | State | +| --- | --- | --- | --- | +| DataTree / slices | `Src/Common/Controls/DetailControls/` (73 files) | superseded by region path | legacy (frozen) | +| Lexical/Advanced Entry region | `Src/xWorks/FullEntryRegionComposer.cs`, `Src/Common/FwAvalonia/Region/` | 4 | coexist (manifest §6 Partial) | +| XMLViews browse / bulk-edit | `Src/Common/Controls/XMLViews/` (`BrowseViewer`, `BulkEditBar`, `FilterBar`) | 3 | legacy (Avalonia table unbuilt) | +| Owned field controls | `Src/Common/FwAvalonia/Region/FwFieldControls.cs`, `FwOptionPicker.cs` | done | coexist | +| View-definition IR + override system | `Src/Common/FwAvalonia/ViewDefinition/` (importer, compiler, JSON, **override differ/applier/migrator/loader**) | 9.2/9.4 | **pure-logic core done** | + +## Tool areas (RecordEditView/BrowseView/DocView consumers) +| Area | Project | Stage | State | +| --- | --- | --- | --- | +| Lexicon edit | `Src/LexText/Lexicon/`, `LexTextControls/` (~132 files) | 4/6a | coexist (entry); rest legacy | +| Grammar / Morphology | `Src/LexText/Morphology/`, `Src/FdoUi/` editors | 6b (→9), 6c (→8) | legacy | +| Texts & Words / Interlinear | `Src/LexText/Interlinear/` (~98 files), Sandbox | 7A (→9) | legacy | +| Discourse / constituent charts | `Src/LexText/Discourse/` | 7B | legacy | +| Notebook / Lists / bulk-edit | `Src/xWorks/`, `Src/LexText/` | 8a | legacy | +| Dictionary configuration | `Src/xWorks/` `DictionaryConfiguration*`, `DictionaryDetailsView/` | 8b | legacy (MVP-ready) | + +## Native UI / rendering (decommission targets) +| Component | Path | Stage | State | +| --- | --- | --- | --- | +| Views engine (VwRootBox/VwSelection/VwTextBoxes) | `Src/views/` | 9 (replace) / 13 (delete) | legacy | +| Managed Views host | `Src/Common/SimpleRootSite/`, `Src/Common/RootSite/` | 9/13 | legacy | +| Buffered draw | `Src/ManagedVwDrawRootBuffered/` | 13 | legacy | +| Gecko/PDF preview | `GeckoWebBrowser`, `GeckofxHtmlToPdf` (dictionary preview/export) | 10A | legacy | +| Graphite engine | `GraphiteEngineClass`, `Src/views/lib/GraphiteEngine.*` | 10B (path) / 13 (delete) | legacy — **removal accepted (doc+notify Awami Nastaliq)** | + +## Dialogs & choosers +| Group | Project | Stage | State | +| --- | --- | --- | --- | +| Core dialogs (~30+) | `Src/FwCoreDlgs/`, `FwCoreDlgControls/`, `BackupRestore/` | 5A/5B | legacy | +| Views-coupled dialogs (Find/Replace, Styles) | `FwFindReplaceDlg`, `FwStylesDlg` (host `IVwRootSite`) | 5C → 9 | legacy | +| Domain dialogs / launchers | `xWorks`, `LexText`, `FdoUi` | with owning area | legacy | + +> ~200 dialogs total (~123 Forms + ~78 UserControls). FwCoreDlgs: ~46 Designer.cs. + +## How to keep this current +- When a surface changes State, update its row and add the proof (manifest/PR link). +- Stage 8's straggler sweep = "every row is `migrated`/`retired`, or has a named owning stage." Any WinForms + surface not in this table is a census gap — add it. diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-01-platform-enablement-kit.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-01-platform-enablement-kit.md new file mode 100644 index 0000000000..70f1a465c9 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-01-platform-enablement-kit.md @@ -0,0 +1,74 @@ +# Stage 1 — Migration platform & developer-enablement kit (Epic — **finished spec**) + +> Status: **implementation-ready** (finished, not draft). Grounded in `reviews/stage-01-platform-enablement-kit.md`, +> the as-built code under `Src/Common/FwAvalonia/`, and the open items in +> `lexical-edit-avalonia-migration/tasks.md`. "Finished" = the spec is complete and code-mapped; the +> code work itself is teed up per sub-epic with explicit done-vs-remaining. + +## Epic +- **Summary:** Turn the one-off lexical-edit migration into a reusable, documented platform two kits + + evidence base + runbook so mid/junior devs (with Claude) can migrate a surface end-to-end. +- **Type:** Epic · **Labels:** `track-foundation`, `lead-senior` · **Size:** L +- **Description:** Generalize the proven lexical-edit assets into (1) a **region/IR scaffolding kit** for + XML-view-definition-driven surfaces and (2) a **dialog MVVM kit** for hand-authored dialogs, plus a + shared parity-evidence base, a migrate-a-surface runbook, and executable conventions. Much is already + reusable; this epic is **generalization + documentation + the net-new dialog toolchain**, not green-field. +- **Acceptance criteria:** + - A junior + Claude migrates **both** a trivial dialog (MVVM kit) **and** a trivial region mini-surface + (region kit) end-to-end using only the kit + runbook, each with a green evidence bundle. + - Conventions are enforced by tests, not prose (one test each). + - Seam catalog is **versioned with an amendment protocol** (it still grows via Stages 3/9). +- **Dependencies:** consumes Stage 2's surface registry; **blocks** all junior work (esp. Stage 5) and + feeds Stages 4/6/8. Region template marked **provisional until Stage 4 closes** (exemplar gates still Partial). +- **Cross-stage note:** resolves the Stage 1/2 double-booking — the **app-wide surface registry is owned by + Stage 2**, not here. + +## Sub-epics / stories + +### 1.1 Region/IR scaffolding kit · Story · L +- **Description:** A generator that emits a new region's composer skeleton, region model, view, manifest + stub, and a **red** evidence-bundle test, extracted from the de-`LexicalEdit`-prefixed region layer. +- **Done:** owned field controls are already surface-agnostic (`FwFieldControls.cs`, `FwOptionPicker.cs`); + `RegionViewingServices.cs` documents the as-built viewing-replacement contract to copy. +- **Remaining:** the region layer is `LexicalEdit`-prefixed throughout (`LexicalEditRegionModel/Mapper/View`, + `LexicalEditSurfaceSelectionService`) — de-prefix into a reusable base; pairs with **task 18.11** (unify + dual projector) and **8.9** (extract per-capability seam on 2nd-region adoption). +- **Acceptance:** generate → red tests → fill-in produces a compiling region; generator emits red stubs by default. + +### 1.2 Dialog MVVM kit · Story · M *(net-new toolchain — DECIDED 2026-06-15: adopt MVVM)* +> **Decision:** adopt Avalonia XAML + CommunityToolkit.Mvvm + compiled bindings, in a **dedicated +> XAML-enabled project** (`FwAvaloniaDialogs`) so the foundation (`FwAvalonia`) stays pure-C# per its +> documented guarantee. The net48 XAML-compiler/MSBuild integration spike is this story's first task +> (and the Stage 5 gate); it is unblocked. +- **Description:** Establish the dialog-authoring stack the repo does not yet have: add the + **CommunityToolkit.Mvvm** package, the **first `.axaml`**, enable **compiled bindings** (`x:CompileBindings`) + on the net48 build, and a dialog scaffolding generator (view + view-model + host-wrapped-body Form wrapper). +- **Status (2026-06-16):** **de-risk DONE** — `FwAvaloniaDialogs` adds CommunityToolkit.Mvvm, the first + `.axaml` (Tools→Options dialog), and compiled bindings on net48, building green through `build.ps1` + (dialog suite 9/9). **Remaining:** the host-modal WinForms wrapper for coexistence, the localization + lane, and the dialog scaffolding generator (view + view-model + host-wrapped Form wrapper). +- **Acceptance:** scaffold a dialog whose body is an Avalonia view hosted in a WinForms-owned modal Form + (per `dialog-ownership.md`); code-behind exception documented so owned controls embed without rewrite. + +### 1.3 Shared parity-evidence base · Story · M +- **Description:** Parameterize `Path3BundleTests.cs` (today a single hardcoded `scenarioId="first-slice"`) + into a reusable base any surface test derives from; define a **dialog-flavored evidence bundle** (no IR + semantic anchor — dialogs have no compiled IR). +- **Acceptance:** two scenarios (one region, one dialog) run through the same base with distinct bundle shapes. + +### 1.4 Migrate-a-surface runbook · Story · S +- **Description:** Map the 10-step migration workflow to concrete repo actions; wire it to the + `fieldworks-winforms-to-avalonia-migration` skill so AI assistants follow it. +- **Acceptance:** runbook drives the validation-gate migration without tribal knowledge. + +### 1.5 Executable conventions + seam-catalog versioning · Story · S +- **Description:** Document + **test-enforce** AutomationId-from-StableId (already implemented, + `FwFieldControls.cs:44/138/361/537/576`), density tokens, the modernized-Fluent ControlTheme baseline, + localization lanes, and `` (already present). Convert "freeze the seam catalog" into a + **versioned catalog + amendment protocol**. +- **Acceptance:** one test per convention; a CI check fails if a new owned control omits an AutomationId. + +## Notes / open questions +- The region template is extracted from a not-yet-green exemplar → keep it **provisional** until Stage 4 + flips the region-manifest §6 rows from Partial. +- Net48 compiled-bindings build is the single biggest unknown — spike it first (gates 1.2 and Stage 5). diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-02-coexistence-shell-spine.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-02-coexistence-shell-spine.md new file mode 100644 index 0000000000..240fb94aec --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-02-coexistence-shell-spine.md @@ -0,0 +1,57 @@ +# Stage 2 — Coexistence shell spine & host contracts (Epic — **finished spec**) + +> Status: **implementation-ready**. Grounded in `reviews/stage-02-coexistence-shell-spine.md`, the +> shipping host code, and `Directory.Packages.props` (Avalonia **11.3.17**, last netstandard2.0 line). + +## Epic +- **Summary:** Generalize the proven lexical-edit host bridge + surface switch into an app-wide coexistence + spine (host, surface registry + census, command bridge, ownership ports, theming) so any surface can run + Avalonia-in-WinForms behind the flag. +- **Type:** Epic · **Labels:** `track-foundation`, `lead-senior` · **Size:** M +- **Description:** Five of six deliverables are **generalizations of existing code**, not net-new: the host + (`LexicalEditHostControl` + `WinFormsAvaloniaControlHost`, in production via `RecordEditView`), the + four-state `HostUiBehavior`, the `UIMode` flag, and `IXCoreCommandBridge` (in `Seams/ISeams.cs`) all + exist. The genuinely new design surface is the **window/dialog ownership ports** pulled forward from + `fieldworks-avalonia-shell-migration`. +- **Acceptance criteria:** any Avalonia view is hostable in the WinForms shell behind the flag; the surface + registry resolves every host to Supported/ExplicitLegacyFallback/Blocked with **no silent Avalonia**; + ownership ports are the single source of truth shared with Stage 11; theming/host code is Av12-delta-localized. +- **Dependencies:** consumes nothing hard; **gates Stage 5 (dialogs) and Stage 11 (shell)**. The riskiest + dependency (net48 + Avalonia 11 host) is **already retired** — it ships. + +## Sub-epics / stories + +### 2.1 Generalize the region host · Story · S +- Extract a reusable region host from `LexicalEditHostControl` (largely extract-interface + parameterize + `ShowRegion`). **Acceptance:** a second (toy) surface hosts through the same control with no LexicalEdit coupling. + +### 2.2 App-wide surface registry + living surface census · Story · M *(owns the Stage 1/2 double-booking)* +- Generalize `LexicalEditSurfaceSelectionService`/`LexicalEditSurfaceResolver` into an app-wide registry; + **fix the null/unregistered-tool path to default to legacy/blocked, never silent Avalonia.** +- Produce a **living surface-census artifact** (the asset Stage 8's straggler-sweep presumes) enumerating + every WinForms surface + its migration state. +- **Acceptance:** registry + census are the single inventory the program tracks burn-down against. + +### 2.3 Shell-scope command/state bridge · Story · S +- Promote `IXCoreCommandBridge`/`IRecordNavigationContext` to shell scope (interfaces exist; this is wiring). +- **Acceptance:** a shell-scope command routes through the bridge in a headless test. + +### 2.4 Window/dialog ownership ports (single source of truth) · Story · M *(genuinely new)* +- Name and extract the ports: app-lifetime, main-window, active-window registry, dialog owner, dispatcher, + shutdown, modal state — reusing `IUiScheduler`/`IRegionLifetime`, not redefining. Declare them shared + with Stage 11. Add a **host-contract test suite** capturing the existing focus/keyboard/undo mitigations + *before* any refactor. +- **Acceptance:** Stage 11 consumes these verbatim; contract tests lock the coexistence behavior. + +### 2.5 Modernized-Fluent ControlTheme baseline + theming pipeline · Story · M +- Stand up the modernized Fluent theme (per decision §11.4 — upgrade the look, keep density). Add an + **Av12-readiness gate** (theming APIs change 11→12) to keep Stage 12 small. +- **Acceptance:** owned controls render under the baseline theme at 100%/150% DPI within density budget. + +### 2.6 Dual-run CI matrix · Story · S +- Make "dual-run" a **CI build matrix** that builds/tests both surfaces, not just the `UIMode` runtime preference. + +## Notes / open questions +- Port definitions must not diverge from `fieldworks-avalonia-shell-migration` design §2/§7 — make Stage 2 + the source of truth and cross-link. +- All Stage-2 code is written **Av12-delta-localized** (confine 11-only APIs to named seams). diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-03-virtualized-grid-tree.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-03-virtualized-grid-tree.md new file mode 100644 index 0000000000..d1710a45c8 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-03-virtualized-grid-tree.md @@ -0,0 +1,63 @@ +# Stage 3 — Shared editable virtualized table + row chrome (Epic — **finished spec**) + +> Status: **implementation-ready spec; the build itself is the program's largest foundation effort and is +> multi-session.** Re-scoped from "grid AND tree" per `reviews/stage-03-virtualized-grid-tree.md`. +> +> **Implementation underway** in change `shared-editable-virtualized-table` (branch `editable-table`). +> Control-layer done + verified (317/317 FwAvalonia tests, 0 regressions): **3a** sortable headers, +> selection, keyboard nav, de-realized programmatic selection; **3b** inline text + chooser cell +> editing through `IEditSession` (Enter commits+advances, Esc cancels); **3c** checkbox-select column, +> column filter, multi-column sort, bulk-edit preview/apply over a managed in-memory model; **3d** the +> table peer synthesizes a peer per row (de-realized rows enumerated); **product wiring (4)** — +> the Lexicon Browse tool now overlays the Avalonia table behind `UIMode=New` via the same +> `WinFormsAvaloniaControlHost` bridge the edit surface uses, with a clerk-backed read-only adapter +> (faithful cells via `BrowseViewer.GetRowCellStrings`) forwarding selection to the clerk; the legacy +> `BrowseViewer` stays the default and functional fallback. Full managed build green, 354/354 headless +> (consolidated branch; the browse gate covers both `lexiconEdit` and `lexiconBrowse`). +> Remaining: in-app/real-DPI manual verification (4.3, 2.7), in-product browse *editing* (6), +> `BulkEditBar` per-op census follow-ups (7.4 done as a doc), desktop UIA2/FlaUI (3.3), typing-latency +> gate (5.5), and the spike measurement record (1.x). + +## Epic +- **Summary:** Build the one owned, editable, virtualized **table** (browse/XMLViews replacement) plus + indented row chrome that Stages 4, 7, and 8 depend on — the #1 off-the-shelf gap. +- **Type:** Epic · **Labels:** `track-foundation`, `lead-senior` · **Size:** XL +- **Description:** The **tree half is already solved** — the detail surface is a flat indented stack + (rendered today by `LexicalEditRegionView`, budget tops at 253 slices, below where virtualization matters) + and the unbounded chooser is **already virtualized** (`FwOptionPicker` over `VirtualizingStackPanel`). The + real, unsolved work is the **editable table**: `LexicalBrowseView` is **read-only**, and the legacy + surface it replaces (`BrowseViewer` 4,332 / `XmlBrowseViewBase` 2,245 / `BulkEditBar` 7,685 / + `FilterBar` 2,835) carries editing, bulk-edit, checkbox, filter, and sort. +- **Acceptance criteria:** the owned table passes 10k-row browse **and** 253-slice detail at 100%/150% DPI + within the measured legacy perf budget, with **scroll/expand measured on production fixtures** (not just + realization count), editing + selection + keyboard, and custom AutomationPeers. +- **Dependencies:** **gates Stage 4 (via 3b), Stage 7B/7C, Stage 8 (via 3c).** No TreeDataGrid (FOSS repo + **archived 2025-10**; editing behind a commercial license — the pivot trigger moved *further* from firing). + +## Sub-epics / stories (ship consumer-gated, in order) + +### 3a Read-only table at scale · Story · L +- Owned virtualized table rendering 10k rows within budget; promote `LexicalBrowseView` from its current + read-only state onto the shared control. **Unblocks Stage 7/8 display.** +- **Acceptance:** 10k-row + scroll/expand on production fixtures within budget at 150% DPI. + +### 3b Editable cells · Story · L *(top priority — unblocks Stage 4)* +- Cell editing, selection model, keyboard navigation, commit/cancel through the existing edit-session seam. +- **Acceptance:** Stage-4 entry tables edit at parity; one global undo stack honored. + +### 3c Bulk-edit / checkbox / filter / sort · Story · XL +- The `BulkEditBar` replacement (6 op tabs + custom column editors), checkbox column, `FilterBar`, sort. + **Unblocks Stage 8.** Can overlap Track II. +- **Acceptance:** bulk operations + filtering at parity on the 10k-row fixture. + +### 3d Custom AutomationPeers (first-class) · Story · M +- Virtualized UIA enumeration of de-realized rows (Avalonia's `ItemContainerGenerator` doesn't retain + containers). **Zero custom AutomationPeers exist in-repo today** — this is net-new and non-trivial. +- **Acceptance:** UIA tree exposes rows/cells with stable AutomationIds under virtualization. + +## Notes / open questions +- Spike first (3a) and **record the `VirtualizingStackPanel` pivot fire/clear with numbers** — its + scroll/GC cost is a known unresolved upstream issue (#18626); escalate to a fully-owned realization + window only if production fixtures fail. +- Confirm with the Stage 7 owner whether the concordance grid needs 3b editing or only 3a display, to + refine the `S3 → S7` edge. diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-04-finish-lexical-entry.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-04-finish-lexical-entry.md new file mode 100644 index 0000000000..9e2be092f0 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-04-finish-lexical-entry.md @@ -0,0 +1,59 @@ +# Stage 4 — Finish the Lexical / Advanced Entry surface (exemplar) (Epic — **finished spec**) + +> Status: **implementation-ready**, mapped to the open `lexical-edit-avalonia-migration/tasks.md` items. +> Grounded in `reviews/stage-04-finish-lexical-entry.md`. Most of the surface is built; this epic closes +> the named residuals so the region becomes the copy-me reference. + +## Epic +- **Summary:** Close the open lexical-edit items so the Advanced Entry region is 100% green and becomes the + reference implementation Stages 6/8 clone. +- **Type:** Epic · **Labels:** `track-surfaces`, `lead-senior` · **Size:** L +- **Description:** Scope is **finish / re-home**, not build — the JSON serializer, 10k-row browse, + custom-field/reference/picture/pronunciation rendering, and 11.x parity all exist (`[x]`). The real heart + is **latency + 150% DPI budgets**, the **exemplar-quality contract**, and flipping the region-manifest §6 + rows from **Partial** (today's verdict: "Default stays Legacy"). +- **Acceptance criteria:** region-manifest §6 all green; **exit gate = "manifest green + enable-able"**, + explicitly **not** "enabled by default" (the latter inherits Stage 10/13 default-path validation). +- **Dependencies:** **3b (editable cells)** for the table re-home; foundation change owns 6.13 residuals. + +## Sub-epics / stories (mapped to tasks.md) + +### 4.1 Re-home entry tables onto the Stage-3 control · Story · S — *task 7.x* +- `LexicalBrowseView` is built + 10k-row-proven (7.1 `[x]`); **remaining = re-home onto the Stage-3 owned + control and prove at 150% DPI**. Blocks on **3b**; the rest of Stage 4 runs in parallel. + +### 4.2 Latency + 150% DPI budgets · Story · M — *tasks 2.13, 7.7* ⚠ hardware-gated +- Remaining 7.7 lanes: **scroll/expand, typing latency, realized-count, memory, cache-invalidation** + (region-manifest §5.4). **Note:** 150% DPI + some latency lanes need **real scaled-display hardware** + (manifest §5.4) — schedule against that constraint; not purely a code task. + +### 4.3 Override migrator + user-override fixtures · Story · M — *tasks 9.2, 9.3* +- JSON serializer **exists** (`ViewDefinitionJsonSerializer.cs`); **remaining = the override migrator** + (sparse `StableId` patches, `canonical-view-definition-design.md` steps 1–3) and **user-override fixtures** + (`override-fixtures.md`). Shipped layouts already proven (136 import). + +### 4.4 Runtime-XML-disable for the gated surface · Story · S — *task 9.4* +- Disable runtime XML for the gated migrated surface while retaining import/audit fallback. + +### 4.5 Exemplar-quality contract · Story · M — *task 18.11 (+ pairs with 8.9)* +- **Unify the dual projector** (`LexicalEditRegionMapper` thin path vs `FullEntryRegionComposer` 2524-line + full path) into one shared structural projector, and **document `RegionViewingServices` + the plugin + burn-down as the copy-me contract** — or Stages 6/8 clone a 2524-line composer. This is the single most + important exemplar deliverable. + +### 4.6 End-to-end IME wiring · Story · S — *task 18.10* +- Wire `RegionImeCompositionState` into the owned editor end-to-end (explicit compose/cancel/commit on a + realized surface, not just Avalonia's native TextBox IME). + +### 4.7 Manifest green + evidence + full build/test · Story · M — *tasks 10.8/10.10/18.12/18.13* +- Flip region-manifest §6 (Layout/Validation/Accessibility/Performance) from Partial; complete Path-3 + bundles per scenario; attach `wiring-review-checklist.md`; **run the full `./build.ps1` + `./test.ps1` + native+managed traversal (18.12)** — the verification gate not yet run this pass. + +## Notes / open questions +- "Rich references" in scope = **reference vectors, not rich text**; StText multi-paragraph + ORC editing + are **Stage 9** (tasks 8.11, and the `RegionViewingServices` deferred-concerns list). +- Graphite/PDF default-path validation (10.4/10.5) blocks the **global default**, not this region (it's + engine-isolated) — do **not** treat as a Stage 4 blocker. +- `architecture-patterns.md` cited JSON/browse code as done while a couple of tasks read open — reconciled + here (serializer done; migrator/fixtures open). diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-05-global-dialogs-choosers.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-05-global-dialogs-choosers.md new file mode 100644 index 0000000000..f930b3576c --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-05-global-dialogs-choosers.md @@ -0,0 +1,172 @@ +# Stage 5 — Global dialogs & choosers (Epic draft) + +> **Dialog-authoring decision (2026-06-15): MVVM is chosen.** Dialogs use Avalonia XAML + +> CommunityToolkit.Mvvm + compiled bindings, authored in a dedicated XAML-enabled project +> (`FwAvaloniaDialogs`); the `FwAvalonia` foundation stays pure-C#. Stage 5 is gated only on the +> one-time net48 XAML-compiler/MSBuild integration spike (Stage 1.2 first task) — no longer on an +> open architectural decision. +> +> JIRA-ready draft for **Stage 5** of the FieldWorks → Avalonia migration program. +> Grounded in `complete-migration-program.md` (§4 row 5, §6 Stage 5 + Post-review, §7 Definition +> of Done, §10 JIRA structure/labels, §11.3 decision), `reviews/stage-05-global-dialogs-choosers.md`, +> and `reviews/00-cross-comparison-synthesis.md` (§3 conflicts, §6 sub-epic map, §7 edges). +> +> **Authoritative structure:** synthesis §6 — `5A junior` / `5B mid` / `5C → Stage 9`. +> **Hard contract (first rule):** host-wrapped Avalonia *body* inside a WinForms-owned `Form`; no +> `Window.ShowDialog` during coexistence (`dialog-ownership.md` rule 4). + +--- + +## Epic + +- **Summary (JIRA title):** Stage 5 — Migrate global dialogs & choosers to host-wrapped Avalonia (MVVM) +- **Type:** Epic +- **Labels:** `track-surfaces`, `lead-junior` (epic skews junior; carries a mid sub-track in 5B), + `parallel-safe`, `parity-blocked-by:dialog-ownership`, `dependency:stage-1-dialog-mvvm-kit`, + `dependency:stage-2-host` +- **Description:** Migrate FieldWorks' ~200 global dialogs and choosers (FwCoreDlgs first, then shared + chooser/launcher infrastructure, then domain dialogs across xWorks/LexText/FdoUi) from WinForms to + Avalonia. Every "migration" replaces the dialog **body** with an Avalonia view authored in + CommunityToolkit.Mvvm + compiled bindings (§11.3), hosted via `WinFormsAvaloniaControlHost` inside a + thin WinForms `Form` shell that still owns `ShowDialog`/`DialogResult`/modality — there are **no + Avalonia modal windows during coexistence**. This is the program's largest single block of work and + its primary junior+Claude reservoir, fanned out across 4–6 parallel file-owning streams. The backlog + is **re-tiered** so juniors only receive small, Views-free dialogs; large/wizard dialogs go to mid + devs; and the two Views-engine-coupled dialogs leave Stage 5 entirely. +- **Acceptance criteria:** + - Host-wrapped-body contract is honored for every migrated dialog: WinForms `Form` owns modality; + Avalonia body hosted via `WinFormsAvaloniaControlHost`; **no** `new Window().ShowDialog(owner)` + (`dialog-ownership.md` rule 4). Focus-return contract (rule 2) inherited from the host template. + - Each migrated dialog satisfies the §7 per-surface Definition of Done (parity bundle captured before + refactor + matched after; AutomationIds nonlocalized / Names localized; seams reused; + `EngineIsolationAuditTests` + `./build.ps1` + `./test.ps1` green; retrospective in the same PR). + - Evidence-language gate enforced: a checked box noted *substitute/placeholder/skipped/future/partial* + is a review blocker; the integration owner spot-checks junior parity bundles. + - Localization lanes correct: field labels via StringTable; product strings via `FwAvaloniaStrings.resx`; + no hardcoded UI strings (AGENTS.md hard rule). `fieldworks-localization-review` run per dialog. + - `FwFindReplaceDlg` and `FwStylesDlg` are **out of scope** here (re-parented to Stage 9 — see 5C). + - One dialog per PR; net-new `.axaml` + view-model + host `Form` files; call-site swaps deferred to the + per-milestone integration step (principles 10/11). +- **Dependencies (epic links):** + - **Blocked by Stage 1** — specifically `1-dialog-mvvm-kit` (CommunityToolkit.Mvvm package ref, + compiled-bindings build setting, first `.axaml`, dialog scaffolding generator distinct from the region + generator, dialog-flavored evidence bundle). The repo today has **zero `.axaml` and no + CommunityToolkit.Mvvm**; this stack must exist before any junior starts. + - **Blocked by Stage 2** — generalized `WinFormsAvaloniaControlHost`-based host + the dialog-ownership + contract layer (`Form` owner, dispatcher, modal-state ports). Reuse, don't redefine. + - **Downstream:** Stage 8 (shared chooser/launcher infra hardened here), Stage 6 (`S5 → S6`: every + MSA/feature launcher opens a Stage-5 dialog body). + - **Forward/finishing edge (NOT a block):** `S5 → S11` — Stage 11 later strips the WinForms wrapper and + promotes hosted bodies to native Avalonia modal windows once the shell owns modality. +- **Rough size:** **XL** (~200 surfaces; largest single block; ~150 Tier-A/B after 5C removal). + +--- + +## Sub-epics / stories + +### 5A — Junior dialog stream (small, Views-free dialogs) *(junior)* + +- **Summary:** Stage 5A — Migrate small Views-free dialogs to host-wrapped Avalonia/MVVM (junior streams) +- **Type:** Story (epic-feature; spawns one issue per dialog) +- **Description:** The genuinely mechanical, junior-safe reservoir: small modal/warning dialogs and simple + choosers under ~600 LOC with **no `IVwRootSite`/`SimpleRootSite` coupling**. Each dialog's body becomes + an Avalonia MVVM view (CommunityToolkit.Mvvm `[ObservableProperty]`/`[RelayCommand]` + compiled bindings) + hosted in a thin WinForms `Form`. Owned controls (`FwMultiWsTextField`, `FwOptionPicker`) embed as + code-behind child elements — the code-behind exception to "compiled bindings everywhere" (review §2c). + Run as 4–6 parallel file-owning streams; one dialog per PR. The **BackupRestore MVP set** (already has + `BackupProjectPresenter`/`RestoreProjectPresenter` + `IBackupProjectView`) is the worked runbook example + and the first junior conversion. +- **Acceptance criteria:** + - Per dialog: full §7 Definition of Done (semantic + visual + workflow + perf parity bundle captured + before refactor and matched; perf ≤ legacy × 1.2 at 100% **and** 150% DPI; AutomationIds nonlocalized, + Names localized; seams reused; audit + build + test green; retrospective same PR). + - Host-wrapped-body contract honored; **no `Window.ShowDialog`**; focus-return inherited from template. + - Evidence-vocabulary gate enforced with mandatory integration-owner spot-check (juniors at volume are + the program's #1 exposure to "hallucinated parity"). +- **Example concrete dialog issues (Tier A):** + 1. `FwChooserDlg.cs` (396 LOC) — simple chooser; exercises the `FwOptionPicker` reuse path end-to-end. + 2. `FwStylesModifiedDlg.cs` (58 LOC) — trivial modal; ideal first/onboarding issue. + 3. `MoveOrCopyFilesDlg` / `AddNewVernLangWarningDlg` / `DeleteWritingSystemWarningDialog` / + `MissingOldFieldWorksDlg` — small warning/confirmation modals (any one is a self-contained issue). +- **Dependencies:** Stage 1 (`1-dialog-mvvm-kit`), Stage 2 (host + ownership contract). Within 5A, + BackupRestore conversion lands first as the runbook exemplar. +- **Labels:** `track-surfaces`, `lead-junior`, `parallel-safe`, `parity-blocked-by:dialog-ownership` +- **Rough size:** **L** (high count, individually small; the bulk of the parallel head-count). + +### 5B — Mid dialog/wizard stream (large structured dialogs + wizard) *(mid)* + +- **Summary:** Stage 5B — Migrate large structured dialogs & the New-Project wizard to Avalonia/MVVM (mid) +- **Type:** Story (epic-feature; one issue per dialog/wizard) +- **Description:** Structured, large, but **not** Views-coupled dialogs that are explicitly **not + junior-mechanical** (review §1) — they have or need a model and substantial bespoke logic. Same + host-wrapped-body + MVVM + compiled-bindings contract as 5A, assigned to mid devs (§8 staffing allows + it). **Wizards are a distinct sub-track**: `FwNewLangProject` is a multi-step state machine + (`FwNewLangProjectModel` + `Load*SetupDelegate` callbacks) and needs its own navigation/step view-model + pattern, not a flat dialog view-model (review §3.9). +- **Acceptance criteria:** + - Per surface: full §7 Definition of Done (parity bundle before/after, 100% + 150% DPI perf, AutomationId + + localization lanes, seam reuse, audit/build/test green, retrospective same PR). + - Existing models reused/extended where present (`FwWritingSystemSetupModel`, `FwNewLangProjectModel`) — + not re-derived. + - Wizard step navigation has an explicit step/navigation view-model; back/next/finish and per-step + validation match legacy workflow parity. + - Host-wrapped-body + focus-return contract honored; no Avalonia modal window. +- **Surfaces (Tier B):** + - `FwNewLangProjectModel` / `FwNewLangProject.cs` (359 LOC) — multi-step **wizard** sub-track. + - `FwWritingSystemSetupDlg.cs` (738 LOC, has `FwWritingSystemSetupModel.cs`). + - `FwProjPropertiesDlg.cs` (885 LOC). + - `ValidCharactersDlg.cs` (1,965 LOC) — the largest Tier-B item; consider its own issue + split tasks. +- **Dependencies:** Stage 1 (`1-dialog-mvvm-kit`), Stage 2 (host + ownership). No Views/Stage-9 dependency. +- **Labels:** `track-surfaces`, `lead-mid`, `parallel-safe`, `parity-blocked-by:dialog-ownership` +- **Rough size:** **L** (few surfaces, each large; wizard + 1,965-LOC ValidChars carry most of the weight). + +### 5C — Views-coupled dialogs → re-parented to Stage 9 *(NOT junior; NOT Stage-5 work)* + +- **Summary:** Stage 5C — Find/Replace + Styles dialogs (Views-coupled; tracked under Stage 9) +- **Type:** Story (tracking/cross-stage; **no migration work executes inside Stage 5**) +- **Description:** `FwFindReplaceDlg.cs` (3,290 LOC, **14** `IVwRootSite`/`SimpleRootSite` references — + search ops manipulate the live rootbox selection) and `FwStylesDlg.cs` (1,335 LOC, hosts + `IVwRootSite m_rootSite` for live style preview) embed the native Views engine that Stage 9 replaces. + They were mislabeled "FwCoreDlgs first" junior targets; per synthesis §3 and review §5 they are + **re-tiered to Stage 9-gated (Tier C)** and removed from Stage 5. This sub-epic exists only as a + tracking/redirect record so the cross-stage conflict stays resolved. +- **Acceptance criteria:** + - These two dialogs are explicitly excluded from 5A/5B and from any junior assignment. + - Their migration is tracked under **Stage 9** (or a Stage-9-gated sub-epic) and follows the Stage-9 + Definition of Done for Views-coupled document/selection surfaces once the managed selection/caret + model exists (9.2+). + - Note: `PicturePropertiesDialog.cs` (620 LOC) has only a cosmetic `SimpleRootSite.ImageNotFoundX` + reference — it is **not** blocking and may stay in 5A/5B after triage. +- **Dependencies:** **Stage 9** (managed document/text engine — `IVwSelection`/`VwRootBox` replacement). + Not started until Stage 9 surfaces the needed selection/preview constructs. +- **Labels:** `track-surfaces`, `lead-senior`, `parity-blocked-by:stage-9-views-engine`, + `cross-stage:moved-to-stage-9` +- **Rough size:** **M** (two dialogs, but Views-coupled and deferred — sized/owned under Stage 9, not here). + +--- + +## Notes / open questions + +- **First rule, repeated for juniors:** the dialog body is Avalonia; the *window* is a WinForms `Form` that + owns `ShowDialog`/`DialogResult`/modality. `Avalonia Window.ShowDialog` against a WinForms owner is "not a + supported combination on 11.x in this app" (`dialog-ownership.md` rule 4). Bake host shell + focus-return + into the Stage-1 template so this is inherited, not re-implemented per dialog. +- **Compiled-bindings hybrid (review §2c):** dialog layout/view-model wiring is XAML + `x:CompileBindings` + (the anti-hallucination build gate); proven owned controls (`FwMultiWsTextField`, `FwOptionPicker`) are + code-behind and embed as plain child elements. State this exception so the rule is neither violated nor + forces a rewrite of proven controls. +- **Open Q — net48 build (review §6):** does CommunityToolkit.Mvvm + compiled bindings + a `.axaml` dialog + build cleanly on **net48 / Avalonia 11.3.17**? Unproven in-repo; must be de-risked in **Stage 1**, before + any junior touches it. This is a hard precondition on the epic. +- **Open Q — host shell ownership (review §6):** when a Stage-5 dialog is launched from *another WinForms + dialog* (not from the Avalonia surface), what owns the host `Form`? `dialog-ownership.md` assumes the + launcher is the Avalonia surface; the WinForms→WinForms chain needs an explicit owner rule. Resolve in + Stage 2's ownership-port work. +- **Risk — shared-control churn (review §6):** `FwOptionPicker`/`FwMultiWsTextField` get exercised hard; + a mid-stage API change touches many dialogs at once (merge contention + re-verification). Freeze the + owned-control API early or funnel changes through one owner. +- **Risk — mis-tiering recurs (review §6):** incoming domain dialogs (esp. LexText, ~45 Forms) include more + Views-coupled dialogs. Each new domain dialog needs the same Views-coupling triage **before** junior + assignment — do not assume "domain dialog" == Tier A. +- **Tier triage rule (recorded for the integration owner):** Tier A = junior, simple, Views-free, <~600 LOC; + Tier B = mid, structured/has-a-model/wizard; Tier C = any `IVwRootSite`/`SimpleRootSite`-coupled → Stage 9. diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-06-lexicon-grammar-morphology.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-06-lexicon-grammar-morphology.md new file mode 100644 index 0000000000..538982834c --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-06-lexicon-grammar-morphology.md @@ -0,0 +1,286 @@ +# Stage 6 — Lexicon completion + Grammar/Morphology detail (Epic draft) + +> JIRA-ready epic + sub-epic draft for **Stage 6** of the FieldWorks → Avalonia migration program. +> Source of truth: `complete-migration-program.md` (Stage 6 detail + §7 Definition of Done + §10 labels), +> `reviews/stage-06-lexicon-grammar-morphology.md`, and `reviews/00-cross-comparison-synthesis.md` (§3, §6, §7). +> +> **Headline from review:** the stage as originally scoped is *mis-bundled across three different substrates* +> (region/composer detail, Views-based document editors, browse bulk-editors). The right cut is **by +> substrate/dependency, not by headcount**. This draft splits Stage 6 into **6a / 6b / 6c**, re-parents **6b +> under Stage 9** (Views-based document surfaces) and routes **6c into Stage 8 / browse** (Stage-3-gated), +> and adds the missing dependency edges `S9 → S6`, `S3 → S6`, `S5 → S6`. + +--- + +## Epic + +**Summary:** Stage 6 — Lexicon completion + Grammar/Morphology detail + +**Type:** Epic + +**Labels:** `track-surfaces`, `lead-mid`, `parallel-safe`, +`parity-blocked-by:region-composer`, `parity-blocked-by:stage-9-document-engine`, +`parity-blocked-by:stage-3-grid` + +**Description** + +Bring the remaining **Lexicon** and **Grammar/Morphology** detail surfaces to parity on the Avalonia +region/composer + plugin-registry machinery proven by Stage 4. As written in the program plan this is +"detail-view-heavy areas that reuse region/composer + plugin registry directly" — but a repo-grounded review +found that is true only for roughly **one third** of the listed work. The work splits cleanly across three +substrates with different dependency profiles, so this epic is decomposed into three sub-epics: + +- **6a — Lexicon detail completion** *(mid; region/composer + plugin registry)* — the part that genuinely + follows the Stage-4 exemplar. Lexical-reference family, MSA / inflection-feature / phonological-feature + launcher rows, examples, `BasicIPASymbolSlice`. +- **6b — Morphology / grammar document editors** *(senior; **re-parented under Stage 9**)* — affix templates, + rule formulas, phonological environment strings, interlinear, reversal-index entry. These are **Views-based + document surfaces**, not detail-field editors, with no owned-control path today; they are blocked on the + Stage-9 managed document engine exactly like Stage 7. +- **6c — FdoUi grammar bulk editors** *(mid; → Stage 8 / browse, Stage-3-gated)* — the four `FdoUi` + `IBulkEditSpecControl` editors (+ `BulkReversalEntryPosEditor`). These are **bulk-edit-bar controls on the + XMLViews browse surface**, not detail/region work; they depend on Stage 3 (editable grid) + a migrated + bulk-edit bar. + +The legacy DetailControls slice bases (`Slice`/`ViewSlice`/`ViewPropertySlice`/`FieldSlice`/`StringSlice` + +the reference launchers in `Src/Common/Controls/DetailControls/`) are **frozen** — they are deleted at cutover +(Stage 13), never extended. New work keys off legacy class identity through the plugin registry +(`IRegionEditorPlugin.LegacyClassName` + `RegionEditorPluginRegistry`), so it touches zero layout XML. + +**Custom-slice census (story-generation guidance, from the Stage 6 review):** + +| Area | Path | Custom slice/launcher classes | +| --- | --- | --- | +| Lexicon | `Src/LexText/Lexicon/`, `Src/LexText/LexTextControls/` | **~32** (21 slices + ~14 launchers) | +| Morphology | `Src/LexText/Morphology/` | **14** | +| FdoUi grammar editors | `Src/FdoUi/` | **4** (+1 sibling, `BulkReversalEntryPosEditor`, in Lexicon) | + +Each census class becomes a candidate story under the appropriate sub-epic, lane-classified by the +burn-down test before work starts (see acceptance criteria). + +**Acceptance criteria** + +- Stage 6 is decomposed into 6a / 6b / 6c (below); 6b is re-parented under Stage 9 and 6c is routed to + Stage 8 / browse — this epic owns **6a** outright and tracks 6b/6c as cross-references. +- Dependency edges `S9 → S6`, `S3 → S6`, `S5 → S6` are recorded in the program graph (§5) and on the relevant + sub-epics. +- `FwDialogLauncherField` is promoted to a first-class `RegionFieldKind` / owned control (platform + prerequisite — see Notes) before launcher rows scale. +- The `LexemeEditorBurnDownTests` census (`Src/xWorks/xWorksTests/`) is **extended to the Morphology and + Grammar layout XML** so every Morphology/FdoUi custom class is lane-classified + (PluginRouted / CompanionDesignated / LauncherRouted / ExplicitlyDeferred / LaneAbsorbed) before its story + starts. "Burn-down tracking" means *this existing test*, not new tracking. +- Every surface-migrating story satisfies the per-surface **Definition of Done** (§7 of the program plan): + census taken; semantic+visual+workflow+performance baselines captured before refactor and matched after; + seams reused from `ISeams.cs`; owned-control choices per `architecture-patterns.md` §4; composer walks + compiled IR with stable AutomationIds; explicit `HostUiBehavior`; Path-3 bundle per scenario with perf + ≤ legacy × 1.2 at **100% + 150% DPI**; localization lanes correct; `EngineIsolationAuditTests` + active-host + contract tests green; `./build.ps1` + `./test.ps1` green; retrospective folded into the skill set in the + **same** PR. +- Evidence-language enforced: any checked task whose evidence says *substitute / placeholder / skipped / + future / partial* is a review blocker. + +**Dependencies** + +- **Blocked by:** Stage 4 (exemplar — region/composer + plugin registry; the reference-vector and launcher + lanes are literally the Stage-0/Stage-4 machinery). +- **Blocked by (newly added):** Stage 9 (managed document engine — for 6b document editors + reversal-index + entry), Stage 3 (editable grid — for 6c bulk editors), Stage 5 (launcher dialog bodies — for 6a launcher + rows). +- **Blocks:** Stage 11 (shell switch consumes migrated Lexicon/Grammar surfaces). + +**Rough size:** Large epic, but only **6a** is mid-owned and closes independently. 6b is Stage-9-class senior +work that tracks Stage 9 timing; 6c is browse/bulk-edit work that lands in Stage 8. Estimate ~32 Lexicon + +14 Morphology + 5 FdoUi = ~51 candidate custom classes across the three sub-epics, of which roughly a third +(the 6a region-pattern work) is the only part schedulable now. + +--- + +## Sub-epics / stories + +### 6a — Lexicon detail completion *(mid; region/composer)* + +**Summary:** 6a — Lexicon detail completion on region/composer + plugin registry + +**Type:** Sub-epic (Story group) + +**Description** + +The part of Stage 6 that genuinely reuses the Stage-4 exemplar. Migrate the remaining Lexicon detail rows to +the Avalonia region/composer using owned controls and launcher plugins: + +- **Lexical-reference family (~9 slices):** `LexReferenceMultiSlice` parent + Pair / Collection / Sequence / + Unidirectional / TreeBranches / TreeRoot variants + `EntrySequenceReferenceSlice` / + `GhostLexRefSlice`. Vector/atomic reference editing the composer already models with + `FwReferenceVectorField` (`RegionFieldKind.ReferenceVector`) + `FwOptionPicker`. These are already + classified **LaneAbsorbed (D3)** by the Stage-0 burn-down. **Highest-complexity region-pattern item:** + `LexReferenceMultiSlice` *dynamically generates child slices by reference type* and owns add/delete/edit + context menus — the composer must reproduce that fan-out (real work, but region-pattern work). Needs a + characterization baseline before refactor. +- **MSA / inflection-feature / phonological-feature launcher rows:** already routed via + `LauncherRegionPlugin` / `DialogLauncherPlugins` (`Src/xWorks/RegionEditorPlugins.cs`, + `DialogLauncherPlugins.cs`), classified **LauncherRouted (D4)**. Remaining work is the missing + `FwDialogLauncherField` owned control (see Notes) plus sequencing the chooser dialog bodies behind Stage 5. +- **`BasicIPASymbolSlice`** (`StringSlice` + auto-population): owned text control + edit-session side-effect + hook. +- **Examples** slices/launchers (may partly overlap the already-migrated Stage-4 entry surface — confirm + during census; medium-confidence item). + +Custom-class census guidance: **~32** Lexicon classes (21 slices + ~14 launchers) — generate one story per +class, lane-classified first. + +**Acceptance criteria (ref §7 DoD)** + +- Census of all ~32 Lexicon custom classes taken and lane-classified via the extended + `LexemeEditorBurnDownTests` before work; plugins exist or explicit "unsupported" rows render (never silent + fallback). +- `LexReferenceMultiSlice` fan-out reproduced by the composer with a captured characterization baseline + (semantic + visual + workflow) matched after refactor. +- Launcher rows render via the promoted `FwDialogLauncherField` `RegionFieldKind`; the launcher *dialog body* + exists (Stage 5) or stays a WinForms-owned modal per `dialog-ownership.md` before the row claims parity. +- Full §7 per-surface DoD per row: Path-3 bundle per scenario, perf ≤ legacy × 1.2 at 100% + 150% DPI, stable + AutomationIds from StableId, localization lanes correct, `EngineIsolationAuditTests` green (catches any + accidental `RootSite`/Views symbol), `./build.ps1` + `./test.ps1` green, retrospective in same PR. + +**Dependencies:** Stage 4 (exemplar) — hard; Stage 5 (launcher dialog bodies) — `S5 → S6`. Platform +prerequisite: `FwDialogLauncherField` promoted to `RegionFieldKind`. + +**Labels:** `track-surfaces`, `lead-mid`, `parallel-safe`, `parity-blocked-by:region-composer`, +`parity-blocked-by:stage-5-dialogs` + +**Rough size:** Medium. The bulk reuses the Stage-4 machinery; `LexReferenceMultiSlice` fan-out is the one +genuinely non-trivial item. Mid-appropriate, the part of Stage 6 schedulable immediately after Stage 4. + +--- + +### 6b — Morphology / grammar document editors *(senior; re-parent under Stage 9)* + +**Summary:** 6b — Morphology + grammar Views-based document editors (gated on Stage 9) + +**Type:** Sub-epic (Story group) — **re-parented under / sequenced after Stage 9** + +**Description** + +These are **not detail-field editors**. They embed a Views **document** surface (a +`RootSiteControl` / `XmlView` / `PatternView` rendering rich structured content) that the region's owned +controls do **not** cover. They are four distinct document-editor families, all hard-blocked on the Stage-9 +managed document engine — effectively Stage-7-class work, and should **not** be "mid": + +- `InflAffixTemplateSlice` (`XmlView`) — affix templates. +- `RuleFormulaSlice` + `AffixRuleFormulaSlice` / `MetaRuleFormulaSlice` / `RegRuleFormulaSlice` + (`PatternView : RootSiteControl`) — phonological/morphological rule formulas (×4). +- `PhEnvStrRepresentationSlice` (`StringRepSliceView : RootSiteControl`, custom VC + inline environment + validation) — phonological environment strings. +- `InterlinearSlice` (`AnalysisInterlinearRs : RootSite`) — interlinear analysis. +- **Lexicon** `ReversalIndexEntrySlice` — already deferred to **gate 6.13** in the Stage-0 burn-down; same + Views-document profile, so it tracks here. + +Until Stage 9 lands the constructs these need, they can only render **explicit "unsupported" rows** — never a +hidden hosted `RootSite` (active-host contract). Lead level **downgraded Mid → Senior** because this is +Views-engine work. + +Custom-class census guidance: **14** Morphology classes — the document-editor families above plus their +companions; lane-classify all 14 first (the ones not in the families likely land in 6a or as `ExplicitlyDeferred`). + +**Acceptance criteria (ref §7 DoD)** + +- All 14 Morphology custom classes lane-classified via the extended `LexemeEditorBurnDownTests` before work; + the four document-editor families + `ReversalIndexEntrySlice` marked **ExplicitlyDeferred** until Stage 9 + proves the required constructs. +- Stage-9 engine seam confirmed to cover affix-template `XmlView`, `PatternView` rule formulas, and the + environment-string custom VC **before** these stories leave "deferred" (verify against the Stage-9.0 spike + output). +- While deferred, surfaces render explicit "unsupported" rows; `EngineIsolationAuditTests` stays green (no + accidental `RootSite`/Views symbol on the Avalonia path). +- When unblocked, full §7 per-surface DoD per family: characterization baseline before refactor, Path-3 + bundle, perf ≤ legacy × 1.2 at 100% + 150% DPI, one global undo stack, retrospective in same PR. +- **Open decision captured:** whether `PhEnvStrRepresentationSlice`'s inline slash/bar/error validation moves + to the `IValidationService` seam or stays inside the document editor — decided during the Stage-9 spike. + +**Dependencies:** **Stage 9** (managed document engine) — hard, `S9 → S6` (the single biggest graph error in +the original plan); Stage 4 (region scaffolding for the non-document companions). + +**Labels:** `track-surfaces`, `lead-senior`, `parity-blocked-by:stage-9-document-engine` + +**Rough size:** Large / senior, but the schedule is **owned by Stage 9 timing**, not by this sub-epic. Risk: +the parent epic can look "mostly done" (6a references migrated) while these linguistically critical editors +are still blocked — track honestly against Stage 9. + +--- + +### 6c — FdoUi grammar bulk editors *(mid; → Stage 8 / browse, Stage-3-gated)* + +**Summary:** 6c — FdoUi grammar bulk-edit-bar editors (route to Stage 8 / browse) + +**Type:** Sub-epic (Story group) — **routed to Stage 8 (browse / bulk-edit reservoir)** + +**Description** + +`BulkPosEditor` / `BulkPosEditorBase`, `InflectionClassEditor`, `InflectionFeatureEditor`, +`PhonologicalFeatureEditor` (+ `BulkReversalEntryPosEditor` in Lexicon) all implement `IBulkEditSpecControl` +and embed a WinForms `TreeCombo` (inline popup tree — **no Views, no chooser dialog → no Stage-9 dependency**). +But they are **bulk-edit-bar controls on the XMLViews browse surface**, not the DataTree/region detail +surface. They have no place in a "detail" epic: they depend on **Stage 3** (the editable virtualized grid) + +a migrated **bulk-edit bar**, not on the region composer. Route to **Stage 8** (or make them the first +browse-bulk-edit slice). + +Custom-class census guidance: **4** FdoUi editors + 1 Lexicon sibling = **5** classes. + +**Acceptance criteria (ref §7 DoD)** + +- 5 `IBulkEditSpecControl` classes lane-classified and confirmed wrong-substrate (browse, not detail); + relocated under Stage 8 with an `S3 → S6`/`S3 → S8` dependency recorded. +- Non-trivial bulk-edit behaviors characterized before grid migration: `InflectionFeatureEditor`'s + "fill-in-the-blanks" value pattern-matching from the current sense; `PhonologicalFeatureEditor`'s + cross-control `EnableTargetFeatureCombo` event. +- `TreeCombo` + `PopupTreeManager` replacement decided: confirm `FwOptionPicker` covers the bounded + popup-tree-combo case, or schedule a new owned control (open item — see Notes). +- Full §7 per-surface DoD once on the Stage-3 grid: Path-3 bundle, perf ≤ legacy × 1.2 at 100% + 150% DPI, + `EngineIsolationAuditTests` green, retrospective in same PR. + +**Dependencies:** **Stage 3** (editable grid) — hard, `S3 → S6`; the Stage-8 bulk-edit-bar migration. + +**Labels:** `track-surfaces`, `lead-mid`, `parity-blocked-by:stage-3-grid` + +**Rough size:** Small / medium (5 classes), but **does not belong in Stage 6** — sized and scheduled under +Stage 8. Listed here only to record the route-out and the dependency edge. + +--- + +## Notes / open questions + +- **6b re-parenting under Stage 9 (the central restructuring).** The original plan graph showed Stage 6 + depending only on Stage 4. The four Morphology document-editor families (`InflAffixTemplateSlice`, + `RuleFormulaSlice` ×4, `PhEnvStrRepresentationSlice`, `InterlinearSlice`) and Lexicon + `ReversalIndexEntrySlice` are Views-document surfaces with **no owned-control path** — blocked on the + Stage-9 managed document engine exactly like Stage 7. `S9 → S6` is *"the single biggest plan error for this + stage."* These should be **re-parented under / sequenced after Stage 9**, lead level **downgraded Mid → + Senior**. Whether 6b lives as a Stage-6 sub-epic that *links to* Stage 9 or is physically moved into the + Stage 9 epic family is a JIRA-structure call for the program owner; this draft keeps it as a Stage-6 + sub-epic with a hard `S9 → S6` block so the Lexicon (6a) work can close independently. +- **Platform prerequisite — `FwDialogLauncherField` → `RegionFieldKind`.** Today it is plugin-delivered + (`DialogLauncherPlugins.cs`) and *not* in the Stage-0 `RegionFieldKind` vocabulary. Stage 6 multiplies + launcher usage (MSA, inflection feature, phonological feature, allomorph/MSA ad-hoc links), so this gap is + hit immediately. Promote it to a first-class owned control / `RegionFieldKind` in **Stage 1 or Stage 4** + before 6a scales launcher rows. +- **Missing dependency edges to add to the program §5 graph:** `S9 → S6` (document editors), `S3 → S6` + (bulk editors), `S5 → S6` (launcher dialogs). The launcher *row* is Stage-6 region work; the launcher + *dialog content* (`MsaCreatorDlg`, `PhonologicalFeatureChooserDlg`, `MsaInflectionFeatureListDlg`, + `LinkAllomorphDlg`, `LinkMSADlg`, `LinkEntryOrSenseDlg`) is Stage-5 work — sequence so the dialog exists + (or stays a WinForms-owned modal) before the launcher row claims parity. +- **Burn-down is an existing hard gate, not new tracking.** Extend `LexemeEditorBurnDownTests` + (`Src/xWorks/xWorksTests/`) — which parses `class=`/`assemblyPath=` from layout XML and forces every custom + slice into one of five lanes — to the Morphology and Grammar layout files. This makes "burn-down tracking" + concrete. +- **Reference launchers are *not* a Stage-9 dependency.** Even though the legacy reference slices render + through Views at the base (`AtomicReferenceLauncher`/`VectorReferenceLauncher` embed `RootSite`-backed + views), Stage 0 already proved the region composer replaces them with the owned `FwReferenceVectorField` — + no hosted RootSite. Do not let the base-class Views coupling pull the reference family into 6b. +- **Open: `TreeCombo` / popup-tree-combo owned control (6c).** All four FdoUi editors depend on WinForms + `TreeCombo` + `PopupTreeManager`; confirm `FwOptionPicker` covers the bounded popup-tree case or schedule a + new owned control before the Stage-8 bulk-edit work. +- **Open: `PhEnvStrRepresentationSlice` validation placement (6b).** Inline environment validation → + `IValidationService` seam vs. inside the document editor — decide during the Stage-9 spike. +- **Confidence (from review):** High on the census counts, the base-class Views facts, the bulk-editor + wrong-substrate finding, and the missing `S9 → S6` / `S3 → S6` edges. Medium on exact `LexReferenceMultiSlice` + fan-out sizing and on whether every "examples" slice is already Stage-4 lane-absorbed. diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-07-interlinear-discourse.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-07-interlinear-discourse.md new file mode 100644 index 0000000000..e1069be9f2 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-07-interlinear-discourse.md @@ -0,0 +1,161 @@ +# Stage 7 — Texts & Words / Interlinear + Discourse (Epic draft) + +> JIRA-ready draft generated from: +> - `complete-migration-program.md` §6 Stage 7 (lines 377–392, incl. post-review callout), §7 Definition of Done, §10 JIRA structure/labels. +> - `reviews/stage-07-interlinear-discourse.md` (repo-grounded coupling evidence). +> - `reviews/00-cross-comparison-synthesis.md` §3 (conflict resolutions), §6 (sub-epic map), §7 (sequencing edges). +> +> Decomposes one over-coarse stage into **four sub-epics (7A–7D)** per synthesis §6/§7. Issues under +> each sub-epic carry the §7 Definition of Done as a checklist and region-manifest fields as acceptance +> criteria. Planning artifact only — no code/behavior change. + +--- + +## Epic + +**Summary:** Stage 7 — Migrate the Texts & Words area (Interlinear document + Sandbox/FocusBox, constituent charts, concordance/statistics, and import wizards) from WinForms + native Views to Avalonia. + +**Type:** Epic (Track II — Surfaces). Parent initiative: "FieldWorks → Avalonia complete migration." + +**Labels:** `track-surfaces`, `lead-senior` (epic-level; per-sub-epic lead labels below), `parity-blocked-by:stage-9-engine` (7A), `parity-blocked-by:stage-3-grid` (7B/7C), `parallel-safe` (7C/7D only). + +**Description:** +Stage 7 covers the Texts & Words domain, the most Views-engine-entangled non-shell area of FieldWorks. The five legacy surfaces span **1–2 orders of magnitude in Views coupling** (`reviews/stage-07` §1), so this epic is *not* a single deliverable: it decomposes into four sub-epics with distinct leads, dependencies, and critical-path positions. + +- **7A — Interlinear doc + Sandbox/FocusBox** (~27K lines; `Src/LexText/Interlinear`). The deepest Views construction in the codebase — `SandboxBase : RootSite` with a private secondary `VwCacheDa` via `CachePair`, hit-test combo editing, and the aligned word/morpheme/gloss grid layout. Hard-blocks on an **extended** Stage 9. +- **7B — Constituent chart** (`Src/LexText/Discourse`). `ConstituentChartLogic` (~5.5K lines) is pure domain logic that ports as-is; rendering (~1.8K lines) rebuilds on the **Stage 3** owned grid, not the Stage 9 document engine. +- **7C — Concordance / ComplexConc / Statistics** (`UserControl` hosts, no `IVwEnv` in the host). Stage-5-class work; off the Stage-9 critical path. +- **7D — Import wizards** (SFM / LinguaLinks / BIRD / Words-SFM). WinForms wizard dialogs + non-UI importers; junior/MVVM per decision §11.3. Off the Stage-9 critical path. + +**Acceptance criteria (epic-level):** +- All four sub-epics (7A–7D) closed with their own parity bundles green. +- Each migrated surface satisfies the §7 per-surface Definition of Done (custom-slice census, semantic+visual+workflow+performance baselines captured before refactor and matched after, seams reused, owned-control choices justified, AutomationIds, localization lanes, `EngineIsolationAuditTests` green, `./build.ps1` + `./test.ps1` green, retrospective folded into the skill set in the same PR). +- `EngineIsolationAuditTests` green on every migrated Texts & Words surface (no residual `IVwRootBox`/`IVwEnv`/`IVwSelection`/`RootSiteControl` on the Avalonia path). +- Performance ≤ legacy × 1.2 (or accepted delta recorded) on the **largest available corpus**, at 100% and 150% DPI — virtualization is mandatory (see Notes). + +**Dependencies:** +- **Stage 9 (extended) → 7A** — the headline cross-stage dependency. Stage 9's written deliverables (StText + selection/caret + structured doc model) do **not** name the Sandbox/interlinear constructs; this epic is gated on the Stage 9.0 spike covering them and on the 9↔7A engine seam being frozen *after* a Sandbox spike (synthesis §3, §7; risk register). +- **Stage 3 → 7B/7C** — owned virtualized editable grid (chart table; concordance results grid). +- **Stage 3 → 7A** (display) for the aligned interlinear grid if grid-owned (undecided — see Notes). +- **Stage 5 pattern / decision §11.3 → 7D** — MVVM + compiled bindings dialog toolchain (delivered by Stage 1's dialog kit). +- **Stage 6 / FdoUi → 7A/7C** — reuse `FwOptionPicker`-style analysis choosers already built; do not rebuild. +- **Stage 10 (print/preview)** — `InterlinPrintView` and chart print/export overlap Stage 10; confirm ownership (see Notes). + +**Rough size:** XXL (largest Track-II epic). Dominated by 7A (~27K lines, extreme coupling, gated on the long pole). 7B medium, 7C small–medium, 7D small. 7C/7D are parallelizable immediately; 7A is the long pole; 7B can start once Stage 3 lands. + +--- + +## Sub-epics / stories + +### 7A — Interlinear document + Sandbox/FocusBox *(senior; hard-blocks on extended Stage 9)* + +**Summary:** Migrate the interlinear document view and the Sandbox/FocusBox analysis builder to the managed Avalonia document engine. + +**Type:** Sub-epic (Track II / Track III seam). + +**Description:** +The single hardest surface in the whole program. Two distinct specialized Views constructions: +- **Sandbox / FocusBox** (`SandboxBase.cs` ~4973, `.ComboHandlers.cs` ~3450, `.MorphemeBreaker.cs` ~1152, `.SandboxVc.cs` ~797): `SandboxBase : RootSite` backed by a **private in-memory `VwCacheDa`** created via `CachePair.CreateSecCache()`, with a bidirectional HVO map between fake SbWord/SbMorph objects and real LCModel objects. Editing is driven by **selection hit-testing** (`ShowComboForSelection(IVwSelection)`, `ScanForIcon`, `m_rootb.MakeSelAt(...)`); re-render via `RootBox.Reconstruct()` after `PropChanged`. `MorphemeBreaker` mutates the secondary cache directly. +- **Interlinear doc** (`InterlinVc.cs` ~3376, `InterlinDocForAnalysis.cs` ~2606, `InterlinDocRootSiteBase.cs` ~1262): `InterlinVc : FwBaseVc` with 287 `IVwEnv` calls, 70+ `kfrag*` fragments, and an **aligned word/morpheme/gloss/POS grid** layout idiom (multiple WS rows under one vernacular word, column alignment across the paragraph) that ordinary StText editing does not produce. Plus `InterlinViewDataCache` and per-pane SDA decorators, the `InterlinTaggingVc`/tagging child, and `RawTextPane`/`TitleContentsPane` hosts (all `RootSite`-derived). + +Approach (per `reviews/stage-07` §3): (a) replace the secondary `VwCacheDa` with a **managed presentation/decorator model**, not a port of `IVwCacheDa`; (b) replace icon/picture-anchored hit-test editing with a managed equivalent; (c) extract Sandbox logic (morpheme-break, approve-and-move, choose-analysis) and interlinear line-choice logic into **characterization-tested** units before touching the renderer; (d) map the aligned grid explicitly to a Stage-3 grid or Stage-9 layout primitive and record the pivot trigger. `RawTextPane` (plain StText) is the only piece clearly covered by Stage 9 as currently scoped. Fold `InterlinTaggingVc` and the `InterlinPrintView` print path in here (or hand the print path to Stage 10 — confirm). + +**Acceptance criteria (ref §7 DoD):** +- §7 DoD met for the interlinear doc and Sandbox/FocusBox surfaces (custom-slice census; semantic+visual+workflow+performance baselines before/after; seams from `ISeams.cs`, new seams recorded with pivot trigger; owned-control choices per `architecture-patterns.md` §4; stable AutomationIds from StableId; explicit `HostUiBehavior`; Path-3 bundle per scenario; localization lanes; `EngineIsolationAuditTests` green; `./build.ps1` + `./test.ps1` green; retrospective in same PR). +- No native Views on the Avalonia path — secondary cache, hit-test editing, and aligned grid all expressed in managed engine primitives. +- Performance ≤ legacy × 1.2 on the **largest available text** at 100%/150% DPI; lazy/virtualized rendering preserved (legacy uses `AddLazyVecItems`/`AddObjVecItems`). +- Sandbox edit flows (morpheme break, approve-and-move, choose analysis, gloss/POS edit) covered by characterization tests passing pre- and post-migration. + +**Dependencies:** +- **Stage 9 (extended) — hard block.** Requires Stage 9 to deliver (a) a secondary in-memory presentation cache / decorator editing model replacing `CachePair`/`VwCacheDa`, (b) icon/picture-anchored hit-test combo editing replacing `IVwSelection` scan + `MakeSelAt`, (c) the aligned multi-line word/morpheme/gloss grid layout. **Engine seam:** no Stage 9 API may be frozen until a Sandbox/interlinear scenario runs in the Stage 9.0 spike (synthesis §3; risk register). +- Stage 3 (display) if the aligned grid is grid-owned (undecided). +- Stage 6 / FdoUi analysis choosers (reuse `FwOptionPicker`). + +**Labels:** `track-surfaces`, `lead-senior`, `parity-blocked-by:stage-9-engine`. + +**Rough size:** XL (long pole of the whole surface track). + +--- + +### 7B — Constituent chart *(mid/senior; depends on Stage 3, light Stage 9)* + +**Summary:** Migrate the discourse constituent chart — port the logic as-is, rebuild rendering on the Stage-3 grid, and rewrite the exporter as direct domain→XML. + +**Type:** Sub-epic (Track II). + +**Description:** +Splits cleanly (`reviews/stage-07` §2c). `ConstituentChartLogic.cs` (~5459 lines, ~49% of Discourse) is **pure domain logic** — `MoveToColumn`, `MakeWordGroup`, `InsertRow`, `ToggleMissingMarker`, drag/move/merge — with essentially no Views references; **port it as-is** behind characterization tests. Rendering is confined to `ConstChartVc` (`Display(IVwEnv)`), `ConstChartBody : RootSite`, `MakeCellsMethod` (table-cell emission), and `ChartRowEnvDecorator : IVwEnv` (RTL buffering — **delete it**, Avalonia handles `FlowDirection`). The chart is fundamentally a **table**, so rebuild rendering on the **Stage-3 owned virtualized grid**, not the Stage-9 document engine. Rewrite `DiscourseExporter : CollectorEnv` (currently a view-walk reusing `ConstChartVc.Display`) as a **direct domain→XML exporter** to drop the Views dependency. Confirm chart print/export ownership vs. Stage 10. + +**Acceptance criteria (ref §7 DoD):** +- §7 DoD met for the chart surface (baselines before/after, seams, owned-control = Stage-3 grid with pivot trigger if deviating, AutomationIds, localization, `EngineIsolationAuditTests` green, build/test green, retrospective in same PR). +- `ConstituentChartLogic` ported with characterization tests passing on legacy and migrated code. +- `DiscourseExporter` rewritten as direct domain→XML with output parity against the legacy view-walk exporter. +- `ChartRowEnvDecorator` removed; RTL handled via Avalonia `FlowDirection`. + +**Dependencies:** +- **Stage 3 → 7B** (owned virtualized editable grid — the chart table). Primary dependency. +- Light Stage 9 (any residual shared text primitives in cell content only). + +**Labels:** `track-surfaces`, `lead-mid` (rendering may need senior review), `parity-blocked-by:stage-3-grid`. + +**Rough size:** M (logic is free; rendering ~1.8K lines on the shared grid). + +--- + +### 7C — Concordance / ComplexConc / Statistics *(mid; Stage-5-class)* + +**Summary:** Migrate the concordance, complex-concordance, and statistics views — `UserControl` hosts with low Views coupling. + +**Type:** Sub-epic (Track II; off the Stage-9 critical path). + +**Description:** +`ConcordanceControl` (~1846), `ComplexConcControl` (~812), `ConcordanceControlBase`, and `StatisticsView` (~301) are `UserControl` hosts with **no `IVwEnv`/`IVwRootBox` in the host itself** — Stage-5-class work. The results/preview pane re-hosts `InterlinVc`, so the embedded interlinear preview depends on 7A/Stage 9; the host chrome, search/filter UI, and results grid do not. The results grid rides the **Stage-3** owned table. `ComplexConcPatternVc` (part of the ~121 `IVwEnv` calls across pattern VCs noted in `reviews/stage-07` §6) is the one specialized VC here — fold it into this sub-epic's scope explicitly. + +**Acceptance criteria (ref §7 DoD):** +- §7 DoD met for concordance, complex-concordance, and statistics surfaces (baselines before/after, seams, AutomationIds, localization, `EngineIsolationAuditTests` green on the host, build/test green, retrospective in same PR). +- Results grid built on the Stage-3 owned table; search/filter/criteria UI at parity. +- Embedded interlinear preview pane wired to the 7A/Stage-9 managed engine (or explicitly gated until 7A lands, never silent legacy fallback). + +**Dependencies:** +- **Stage 3 → 7C** (results grid). +- Stage 9 / 7A — **only** for the embedded interlinear preview pane (not the host). Decouple so the host ships independently. + +**Labels:** `track-surfaces`, `lead-mid`, `parallel-safe`, `parity-blocked-by:stage-3-grid`. + +**Rough size:** M (hosts are small; gated only on the embedded preview). + +--- + +### 7D — Import wizards (SFM / LinguaLinks / BIRD / Words-SFM) *(junior; MVVM)* + +**Summary:** Migrate the interlinear/text import wizards to Avalonia MVVM dialogs. + +**Type:** Sub-epic (Track II; junior/Stage-5 pattern; off the Stage-9 critical path). + +**Description:** +`InterlinearSfmImportWizard`, `LinguaLinksImport`, `BIRDInterlinearImporter`, `WordsSfmImportWizard` are ordinary WinForms wizard dialogs plus non-UI importers (`reviews/stage-07` §2d). **No Views coupling.** Per decision §11.3, build these as **MVVM + compiled bindings** (CommunityToolkit.Mvvm) — no IR/region machinery (they have no XML layout). Reuse owned WS-aware field controls (`FwMultiWsTextField`, `FwOptionPicker`) inside the dialogs where WS-aware text or chooser fields appear. During coexistence (until Stage 11), modality stays a WinForms-owned `Form` wrapping an Avalonia body via `WinFormsAvaloniaControlHost` (Stage-5 host-wrapped-body contract). The non-UI importers (`BIRDInterlinearImporter`) are framework-agnostic — reclassify as non-UI, migrate only the wizard chrome. + +**Acceptance criteria (ref §7 DoD):** +- §7 DoD met per wizard (parity bundle, AutomationIds, localization review; `EngineIsolationAuditTests` green; build/test green; retrospective in same PR). +- MVVM + compiled bindings; no IR/region machinery; no `new Window().ShowDialog()` during coexistence (host-wrapped body). +- Import round-trip parity (sample SFM/LinguaLinks/BIRD inputs → identical LCModel result vs. legacy). + +**Dependencies:** +- **Stage 1 dialog/MVVM kit** (CommunityToolkit.Mvvm + compiled bindings + dialog scaffolding) and the Stage-5 host-wrapped-body contract. +- None on Stage 9 or Stage 3. + +**Labels:** `track-surfaces`, `lead-junior`, `parallel-safe`. + +**Rough size:** S–M (several wizard dialogs; mechanical once the kit exists). + +--- + +## Notes / open questions + +1. **Stage 9 coverage gap for Sandbox/interlinear constructs (TOP RISK).** Stage 9's written deliverables (StText editing, managed selection/caret replacing `VwSelection`, structured doc model replacing `VwRootBox`) **do not name** the three load-bearing 7A constructs: (a) the secondary in-memory presentation cache/decorator model replacing `CachePair`/`VwCacheDa`; (b) icon/picture-anchored hit-test combo editing replacing `IVwSelection` scan + `MakeSelAt`; (c) the aligned multi-line word/morpheme/gloss grid layout. **Resolution (synthesis §3):** expand Stage 9 scope and add a named Sandbox/interlinear sub-spike to **Stage 9.0**; freeze no Stage 9 engine API until that spike runs, or 7A discovers a missing engine capability late and stalls. 7A must depend on the **9.0 spike output**, not merely on Stage 9 completion. +2. **Aligned-grid layout ownership undecided.** Interlinear column alignment crosses both the Stage-3 owned grid and the Stage-9 layout/box model. It is neither a plain table nor a plain paragraph. Record an explicit decision/pivot trigger in `seam-catalog.md` before 7A rendering starts. +3. **Performance on large corpora.** `InterlinVc` uses lazy `AddObjVecItems`/`AddLazyVecItems` precisely because texts are large; a naive non-virtualized managed reimplementation **will** regress. Baseline on the largest available text first; treat virtualization as a hard requirement, not an optimization. +4. **Specialized pattern VCs not separately budgeted.** `InterlinTaggingVc` (tagging) and `ComplexConcPatternVc` (~121 `IVwEnv` calls across the pattern VCs) are extra specialized VCs — folded into 7A (tagging) and 7C (pattern) scope above; confirm sizing. +5. **Print views overlap Stage 10.** `InterlinPrintView` and chart print/export overlap Stage 10 (print/preview). Confirm ownership to avoid a gap — recommend Stage 10 owns the print pipeline, 7A/7B own the on-screen view. +6. **`DiscourseExporter` rewrite vs. port.** Decision taken above (rewrite as direct domain→XML to drop the Views walk). Confirm no downstream consumer depends on the exact `CollectorEnv` walk ordering. +7. **Embedded interlinear preview seam (7C).** The concordance/statistics hosts re-host `InterlinVc`; ensure the host ships independent of 7A by gating only the preview pane (explicit "unsupported/preview-pending" render, never silent legacy fallback). diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-08-notebook-lists-dictconfig.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-08-notebook-lists-dictconfig.md new file mode 100644 index 0000000000..95c304fe82 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-08-notebook-lists-dictconfig.md @@ -0,0 +1,264 @@ +# Stage 8 — Notebook, Lists, Dictionary-config UI, remaining tools (Epic draft) + +> **JIRA-ready draft.** Created from `complete-migration-program.md` §6 Stage 8 (lines 394–406), +> §7 Definition of Done, §10 JIRA structure; `reviews/stage-08-notebook-lists-dictconfig.md`; and +> `reviews/00-cross-comparison-synthesis.md` §3 (preview double-booking), §6 (sub-epic map row 8), +> §7 (dependency edges `S3 → S8`, `S9 → S8`). Planning only — no code/behavior change. +> +> **Headline from review:** Stage 8 as written is a *grab-bag*. It splits into **8a** (Notebook/Lists/ +> bulk-edit — region/composer + shared grid, mid) and **8b** (dictionary-config dialogs — MVVM, Stage-5 +> idiom). "Config preview wiring" is **moved out** to Stage 10 (it is Gecko). The "straggler sweep" +> depends on the **living surface-census artifact owned by Stage 2** — Stage 8 *consumes* it, it does +> not create it. + +--- + +## Epic + +**Summary:** Migrate the Notebook area, Lists/possibility-list editors, the cross-surface bulk-edit +bar, and the Dictionary-configuration dialog family to Avalonia at parity, and reconcile the remaining +WinForms surface stragglers against the surface census. + +**Type:** Epic (Track II — Surfaces). Decomposes into sub-epics **8a**, **8b**, and a +**straggler-reconciliation** story; do not implement as a single mid-level epic. + +**Labels:** `track-surfaces`, `lead-mid` (8a) / `lead-junior` + `lead-mid` (8b), `parallel-safe`, +`parity-blocked-by:stage-3-editable-grid` (bulk-edit, Lists tree-bar), +`parity-blocked-by:stage-9-document-engine` (Notebook Document / XhtmlRecordDocView), +`parity-blocked-by:stage-10-preview` (dictionary preview island). + +**Description.** +This epic finishes the long tail of Track-II surfaces that follow the Stage-4 exemplar but were not +themselves the exemplar. It contains two architecturally distinct bodies of work plus a reconciliation +task: + +- **8a — Notebook / Lists / bulk-edit (region/composer + shared grid).** Notebook is three tools + (`Notebook/{Edit,Browse,Document}`) over `RnGenericRec`, riding the shared `RecordEditView` / + `RecordBrowseView` / `XmlDocView` hosts — no Notebook-specific custom slices exist, so the detail + half is a near-mechanical Stage-4-pattern clone. Lists is ~25–29 possibility-list editors over + `CmPossibility` subclasses, all `RecordEditView` detail, several with hierarchical tree-bar handlers + (`PossibilityTreeBarHandler`, `SemanticDomainRdeTreeBarHandler`) — the **hierarchical tree-bar is a + sub-surface the exemplar never exercised** and depends on the Stage-3 owned virtualized tree. + **`BulkEditBar` is the real engineering item** — a 6-tab editable-grid-plus-operation surface bolted + onto `BrowseViewer` (`bulkEdit="true"`), with custom column editors (`BulkReversalEntryPosEditor`) + that need plugin-registry treatment. It is squarely Stage-3-gated. +- **8b — Dictionary-configuration dialogs (MVVM + compiled bindings).** The `DictionaryConfigurationDlg` + family (~43 `.cs` files: main dialog, `DictionaryConfigurationTreeControl`, the 15-file + `DictionaryDetailsView/` folder, Manager/Import/Rename dialogs). Per decision §11.3 these are + hand-authored UI with **no XML view-definition to compile**, so they use **CommunityToolkit.Mvvm + + compiled bindings (the Stage-5 idiom), NOT the region/composer pattern**. They are unusually + MVVM-ready: the existing `IDictionary*View` + `*Controller` MVP split means the controllers largely + *become* view-models — a good junior+Claude reservoir. +- **Straggler reconciliation.** Burn the remaining non-migrated WinForms surface population + (~23 registered `IUtility` implementations + residual Forms/UserControls) down against the + Stage-2-owned **surface census**. + +**Out of scope (moved elsewhere):** +- **Dictionary config *preview* wiring → Stage 10.** The preview pane hard-requires `GeckoWebBrowser` + (`DictionaryConfigurationDlg.cs` lines 10/45/187/208/228); Gecko replacement is Stage 10's defining + deliverable. Synthesis §3 resolution: preview *rendering replacement* = Stage 10; 8b *consumes* the + replaced preview. +- **XHTML/CSS/PDF generators reclassified as non-UI.** `ConfiguredLcmGenerator`, `LcmXhtmlGenerator`, + `CssGenerator` are framework-agnostic content generation; they move with Stage 10's preview work. +- **Dictionary-config *migrators* reclassified as non-UI.** `DictionaryConfigurationMigrators/` (4 files) + + `DictionaryConfigurationMigrator.cs` are pure `.fwdictconfig` data/format logic — carry over + unchanged, not in any UI epic. + +**Acceptance criteria.** +1. 8a, 8b, and the straggler-reconciliation story are all complete with their own acceptance criteria met. +2. Every migrated surface satisfies the per-surface **Definition of Done (§7)** — its own Path-3 parity + bundle (semantic + visual + workflow + performance), captured before refactor and matched after, + at **100% + 150% DPI**; `EngineIsolationAuditTests` + active-host contract tests green; + `./build.ps1` + `./test.ps1` green; retrospective folded into the skill set in the same PR. +3. No surface claims parity by reasoning "≈ Lexicon detail" — WS-heavy possibility lists and document + views carry distinct typography/density behavior and need their own snapshots. +4. The dictionary preview is **not** asserted at parity in this epic (it is a Stage-10 lane); any + coexisting Gecko preview island is explicitly fenced (see Notes). +5. Surface census shows zero unreconciled Stage-8-owned stragglers (each is migrated, deferred to a + named stage, or marked explicit "unsupported" — never silent fallback). + +**Dependencies.** Stage 4 (exemplar — the detail half clones it), Stage 5 (dialog idiom + tooling that +8b reuses), **Stage 3** (editable grid for bulk-edit + tree control for Lists tree-bar), +**Stage 9** (managed document engine for Notebook Document / `XhtmlRecordDocView`), +Stage 10 (preview replacement that 8b consumes), Stage 2 (surface census the straggler sweep burns down). +*Plan §4 row currently lists only `4,5` — Stage 3 and Stage 9 are added per review §4/§5 and synthesis §7.* + +**Rough size.** Large (mid-level epic split across two sub-epics + a reconciliation tail). 8a is the +larger and higher-risk body (bulk-edit + ~25–29 Lists editors + Notebook); 8b is sizeable but +mechanically tractable (~43 files, MVP-assisted). Bulk-edit alone is its own gated issue. + +--- + +## Sub-epics / stories + +### 8a — Notebook / Lists / bulk-edit *(region/composer + shared grid; mid)* + +**Summary.** Migrate the Notebook area, the Lists/possibility-list editors, and the cross-surface +bulk-edit bar to Avalonia using the Stage-4 region/composer pattern for detail and the Stage-3 shared +editable virtualized grid/tree for browse, bulk-edit, and hierarchical lists. + +**Type.** Sub-epic (Track II). `lead-mid`. + +**Description.** +- **Notebook (3 tools).** `notebookEdit` (`RecordBrowseView` + `RecordEditView` detail), `notebookBrowse` + (`RecordBrowseView`), `notebookDocument` (`XmlDocView`) over `RnGenericRec`. No Notebook-specific + custom slices — rides the shared DetailControls stack. `native-views-audit.md` §8.6 already routes + `notebookEdit` as explicit legacy fallback through `RecordEditView`. The detail half is a near-mechanical + Stage-4 clone; the browse half rides Stage 3; **the document half (`notebookDocument`) depends on + Stage 9** and must not be claimed done before Stage 9 covers it. +- **Lists (~25–29 editors).** All `RecordEditView` detail over `CmPossibility` subclasses. The + **hierarchical tree-bar / record-list sidebar** (`PossibilityTreeBarHandler`, + `SemanticDomainRdeTreeBarHandler`) is an unbounded-tree sub-surface the exemplar never exercised — + maps to `architecture-patterns.md` §4 "owned flattened virtualized list with expander/indent" and + **depends on Stage 3**. Custom-list create/delete dialogs (`CustomListDlg.cs`, `DeleteCustomList.cs`) + are Stage-5-style dialogs. +- **Bulk edit (the real engineering item).** `Src/Common/Controls/XMLViews/BulkEditBar.cs` — 6 tabs + (List Choice, Bulk Copy, Click Copy, Process/Transduce, Find/Replace, Other) bolted onto `BrowseViewer`. + Editable grid + operation UI → **Stage-3-gated**. Custom column editors (`BulkReversalEntryPosEditor`) + → plugin-registry with burn-down tests. **Size as its own gated issue.** + +**Acceptance criteria (ref §7 DoD).** +- Per-surface (each Notebook tool, each Lists editor, the bulk-edit bar): custom-slice/column-editor + census taken with plugins registered or explicit "unsupported" rows (DoD #1); seams reused from + `ISeams.cs` (DoD #3); composer walks compiled IR with stable AutomationIds from StableId (DoD #5); + explicit `HostUiBehavior` per host, no hidden DataTree/Views (DoD #6); Path-3 bundle per surface, + perf ≤ legacy × 1.2, **100% + 150% DPI** (DoD #7); localization lanes correct (DoD #8); + `EngineIsolationAuditTests` + active-host contract + `build`/`test` green (DoD #9); retrospective in + same PR (DoD #10). +- Bulk-edit gated on Stage 3 (editable cells + bulk/checkbox/filter, i.e. 3b + 3c) and on plugin-registry + custom column editors. +- Lists hierarchical tree-bar gated on Stage 3 owned tree control. +- `notebookDocument` / document views deferred until Stage 9 covers them — not claimed done in 8a. + +**Dependencies.** Stage 4 (detail pattern + open exemplar-debt: dual-projector unification 18.11), +**Stage 3** (3b editable cells, 3c bulk/checkbox/filter, owned tree), **Stage 9** (`notebookDocument`), +Stage 5 (custom-list/create/delete dialog bodies). + +**Labels.** `track-surfaces`, `lead-mid`, `parallel-safe`, +`parity-blocked-by:stage-3-editable-grid`, `parity-blocked-by:stage-9-document-engine`. + +**Rough size.** Large. Notebook (small, near-mechanical) + Lists (medium, ~25–29 editors + tree-bar) + +bulk-edit (large, own gated issue). Bulk-edit is the highest-risk item in the epic. + +--- + +### 8b — Dictionary-configuration dialogs *(MVVM + compiled bindings; junior/mid)* + +**Summary.** Migrate the `DictionaryConfigurationDlg` dialog family to Avalonia using CommunityToolkit.Mvvm ++ compiled bindings (Stage-5 idiom), reusing the existing `IDictionary*View`/`*Controller` MVP split, +with the preview pane left as a coexisting Stage-10 lane. + +**Type.** Sub-epic (Track II / Stage-5-pattern). `lead-junior` + `lead-mid`. + +**Description.** +- **~43 `.cs` files in `Src/xWorks/`:** `DictionaryConfigurationDlg.cs` (+Designer), + `DictionaryConfigurationTreeControl.cs`, the 15-file `DictionaryDetailsView/` folder + (`DetailsView`, `ListOptionsView`, `SenseOptionsView`, `GroupingOptionsView`, `PictureOptionsView`, + `ButtonOverPanel`, `LabelOverPanel`, each `.cs` + `.Designer.cs`), and the Manager/Import/Rename + dialogs (`DictionaryConfigurationManagerDlg`, `DictionaryConfigurationImportDlg`, + `DictionaryConfigurationNodeRenameDlg`). +- **Per decision §11.3:** hand-authored UI, no XML layout to compile → **MVVM + compiled bindings, NOT + region/composer.** Reuse the owned WS-aware field controls (`FwMultiWsTextField`, `FwOptionPicker`) + *inside* the dialogs wherever WS-aware text/chooser fields appear. +- **Reuse the existing MVP seam:** the runbook is "`*Controller` → view-model, `IDictionary*View` → + compiled bindings" — lower-risk than greenfield MVVM and a good junior teaching example. +- **Tooling comes from Stage 1's MVVM-dialog kit** (CommunityToolkit.Mvvm, compiled bindings, dialog + scaffolding); code-behind exception allowed so proven owned controls embed without rewrite. +- **Preview is NOT in scope** — the preview pane is Gecko (Stage 10). 8b's exit gate excludes preview + parity; the preview may remain a coexisting WinForms/Gecko island until Stage 10. + +**Acceptance criteria (ref §7 DoD).** +- Each dialog: parity bundle (semantic/visual/workflow/performance) captured and matched at 100% + 150% + DPI (DoD #7, #2); AutomationIds nonlocalized, Names localized (DoD #8); localization review; + `build`/`test` green (DoD #9); retrospective in same PR (DoD #10). +- No `new Window().ShowDialog()` — modality stays WinForms-owned via host-wrapped body until Stage 11 + (the Stage-5 host-wrapped-body rule). +- **Preview parity explicitly excluded** from the exit gate; the Gecko preview island is fenced so that + `EngineIsolationAuditTests` does not flag `GeckoWebBrowser` inside the migrated assembly (see Notes). +- Migrators (`DictionaryConfigurationMigrators/`) and XHTML/CSS generators are *not* part of this + sub-epic (reclassified non-UI / Stage 10 respectively). + +**Dependencies.** Stage 1 (MVVM-dialog kit + scaffolding), Stage 5 (idiom + Tier-A/B streams; staffed as +additional Stage-5 streams), Stage 10 (preview replacement that 8b later consumes), Stage 2 (host-wrapped +modality contract). + +**Labels.** `track-surfaces`, `lead-junior`, `lead-mid`, `parallel-safe`, +`parity-blocked-by:stage-10-preview`. + +**Rough size.** Medium-large (~43 files), mechanically tractable thanks to the MVP split. Suitable as a +multi-stream junior reservoir under mid supervision. + +--- + +### 8c — Straggler reconciliation against the surface census *(mid)* + +**Summary.** Reconcile the remaining non-migrated WinForms surface population owned by Stage 8 — +~23 registered `IUtility` implementations (`UtilityCatalogInclude.xml`) plus residual Forms/UserControls — +against the Stage-2-owned living surface-census artifact, ensuring none are silently missed. + +**Type.** Story (Track II). `lead-mid`. + +**Description.** +- **This story consumes, it does not create, the surface census.** Per synthesis §3 and the Stage-2 + post-review, the app-wide surface registry **and** the living surface-census artifact are **Stage 2 + deliverables** — today only the *lexical-edit-scoped* assets exist + (`LexicalEditSurfaceSelectionService.cs`; `native-views-audit.md` §8.6 / `coverage-map.md` / + `view-inventory.md` / `region-manifest.md`). Stage 8 burns its slice of that census down. +- **Risk:** if Stage 2 has not produced the census by the time Stage 8 runs, "sweep for stragglers" + has nothing to sweep against and silently becomes an unbounded discovery task. This story therefore + **hard-depends on the Stage-2 census existing.** +- Each straggler resolves to exactly one of: migrated here (8a/8b idiom as appropriate), deferred to a + named owning stage (e.g. document/Views-coupled → Stage 9; preview/browser → Stage 10), or rendered as + an explicit "unsupported" row — **never silent legacy fallback** (DoD #1). +- Bulk-edit "Process/Transduce" and "Find/Replace" tabs may embed regex/transform UI with hidden + dialog/chooser dependencies — census these before sizing. + +**Acceptance criteria (ref §7 DoD).** +- Surface census shows zero unreconciled Stage-8-owned surfaces; each carries an explicit disposition. +- No silent fallback (DoD #1, DoD #6 — explicit `HostUiBehavior`, full wiring path traced). +- Any surface migrated here meets the full §7 DoD; any deferred surface links to its owning stage/epic. + +**Dependencies.** **Stage 2 (surface census — hard prerequisite)**, plus 8a/8b for the idioms applied to +any in-scope stragglers. + +**Labels.** `track-surfaces`, `lead-mid`, `parity-blocked-by:stage-2-surface-census`. + +**Rough size.** Small-to-medium, but **unbounded if the Stage-2 census is missing** — gate on it. + +--- + +## Notes / open questions + +- **Preview double-booking — RESOLVED (synthesis §3).** Stage 8 line 287 ("config preview wiring") and + Stage 10 line 307 ("dictionary-preview replacement") previously both claimed the preview. Resolution: + preview *rendering replacement* is **Stage 10 only**; Stage 8 (8b) **consumes** the replaced preview. + "Config preview wiring" is removed from Stage 8 scope. The XHTML/CSS/PDF generators + (`ConfiguredLcmGenerator`, `LcmXhtmlGenerator`, `CssGenerator`) move with Stage 10 and are reclassified + non-UI; the `.fwdictconfig` migrators are reclassified non-UI and carry over unchanged. + +- **Open decision — can 8b ship "at parity" with a coexisting Gecko preview island?** The dictionary + dialog's central preview pane is a `GeckoWebBrowser`, on the forbidden-symbol list + (`parity-evidence.md` §4). Two readings: + - *(island)* ship the dialog body in Avalonia while the preview remains a coexisting WinForms/Gecko + island until Stage 10. Then the surface is **not** a clean Avalonia surface, and the boundary must be + drawn so the Gecko symbol does **not** leak into the migrated assembly — otherwise + `EngineIsolationAuditTests` will flag `GeckoWebBrowser`. The exact assembly/host boundary that keeps + the audit green needs a deliberate program decision. + - *(block)* block 8b's preview-bearing dialog on Stage 10. Cleaner audit story, later delivery. + Recommendation in the review: permit the coexisting island during coexistence with an explicit fence, + and exclude preview parity from 8b's exit gate — **but the precise §11.3-vs-`EngineIsolationAuditTests` + boundary is still an open program decision, not a repo fact.** + +- **Lists hierarchical tree-bar is unexercised by the exemplar** (semantic-domain etc.). Needs the + Stage-3 unbounded-tree control + `*TreeBarHandler` behavior; medium risk it is under-sized. + +- **Notebook Document / `XhtmlRecordDocView` are document surfaces** that quietly depend on Stage 9. If + Stage 8 is read as "Notebook done," it inherits Stage 9 scope it cannot satisfy — keep these explicitly + deferred until Stage 9 covers them. + +- **No app-wide surface registry/census exists yet** — only lexical-edit-scoped assets. 8c hard-depends + on the Stage-2 census; surface this as a blocking link in JIRA so the straggler sweep cannot silently + become unbounded discovery. + +- **Dependency-graph corrections to land in the plan:** add `S3 → S8` and `S9 → S8` to the §5 mermaid + graph and to the §4 dependency row (currently `4,5` only), per review §4/§5 and synthesis §7. diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-09-managed-document-text-engine.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-09-managed-document-text-engine.md new file mode 100644 index 0000000000..4b2f6e8778 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-09-managed-document-text-engine.md @@ -0,0 +1,284 @@ +# Stage 9 — Managed document/text rendering & editing engine (Views replacement) (Epic draft) + +> JIRA-ready draft. Source of truth: `complete-migration-program.md` §6 Stage 9 + §11.1 (Graphite +> decision) + §7 (Definition of Done) + §10 (labels); `reviews/stage-09-managed-document-text-engine.md`; +> `reviews/00-cross-comparison-synthesis.md` §4/§6/§7. Stage 9 is the program's **long pole** and its +> **#1 correctness risk**. Decompose per synthesis §6 into one parent epic + five gated sub-epics +> (9.0 spike → 9.1 StText → 9.2 selection/caret → 9.3 layout/box → 9.4 embedded). **Scope = the DELTA +> over the landed field foundation (`FwMultiWsTextField`)** — single-paragraph multi-WS field editing is +> already done; do not re-litigate it. **Native `Src/views` deletion is Stage 13, not here** — Stage 9 +> only severs the Avalonia path from native Views. + +--- + +## Epic + +**Summary:** Build the managed document/text rendering & editing engine that replaces the native C++ +Views engine (`Src/views/`, `IVwRootBox`/`IVwSelection`/`IVwEnv`/box hierarchy) for FieldWorks' +**document / multi-paragraph / structured-text** surfaces — fully managed, no native Views, no Graphite. + +**Type:** Epic (parent). Track III — long pole. Lead: **Senior** (with Claude). + +**Labels:** `track-longpole`, `lead-senior`, `parity-blocked-by:views-engine`, `xl`. +(Sub-epics carry their own labels; see each below.) + +**Description.** +Field-level multi-writing-system `ITsString` editing is **already managed and done** — `FwMultiWsTextField` +(stock Avalonia `TextBox`-per-WS with bolt-on bidi caret nav, grapheme-cluster selection, and `ITsString` +run write-back via `RegionRichTextEditAlgorithms`). That foundation **explicitly deferred** multi-paragraph +`StText` editing and embedded-object/ORC editing. Stage 9 is everything the field foundation is **not**: + +- **Multi-paragraph `StText`** editing (paragraph model, split/merge, para-level properties). +- **Owned structured selection/caret model** replacing `IVwSelection`/`SelLevInfo`/`SelectionHelper` + (~14.4K LOC in `VwSelection.cpp` + ~2K in `SelectionHelper`/`TextSelInfo`) — selection across nested + objects and levels, the deepest correctness surface. +- **Owned `TextLayout`-based layout/box model** (HarfBuzz/Skia via Avalonia `TextLayout`) replacing the + `VwParagraphBox`/`VwStringBox`/`VwGroupBox`/`VwTableBox`/`VwLazyBox` hierarchy where stock `TextBox`/ + `TextLayout` is insufficient for document surfaces (justified/complex multi-line, RTL paragraphs, + drop-caps, tables, overlays). +- **Editable embedded objects/ORCs, footnotes, pictures, tables, overlays** (`VwTableBox`/`VwPictureBox`/ + ORC anchors/`IVwOverlay`). +- **The Stage 7A interlinear/sandbox constructs** the original scope text omitted: a secondary in-memory + presentation cache/decorator editing model (replacing `CachePair`/`VwCacheDa`), icon/picture-anchored + hit-test combo editing, and aligned multi-line interlinear grid layout. Stage 9 owns the **engine seams**; + Stage 7A owns the interlinear-specific composition over them. + +Fully managed only — **no Graphite, no native Views** on the Avalonia path. Complex-script shaping is +HarfBuzz/managed (Avalonia `TextLayout`). Editing routes through the existing seams (`IEditSession`, +`IUndoRedoCoordinator`) and the **one global LCModel undo stack** — never a parallel Avalonia history. +The forbidden-symbol audit (`EngineIsolationAuditTests`) stays green on every migrated surface — no +`IVwRootBox`/`IVwEnv`/`IVwGraphics`/`IRenderEngine`/`GraphiteEngineClass` in the Avalonia path. + +**Acceptance criteria (epic-level).** +1. Multi-paragraph `StText` + structured editing reach parity on managed surfaces; **no native Views on + the Avalonia path** (forbidden-symbol audit green). +2. **9.0 spike exits with a written go/no-go and a build-vs-extend decision** before 9.1+ open; spike + includes mixed bidi, CJK IME, custom WS, multi-paragraph StText, cross-structure selection, **and a + Sandbox/interlinear scenario**. +3. The **G0–G3 LDML coverage scan** ships from 9.0 with the **exact dropped-script list + affected + projects** (the document/notify obligation input per resolved §11.1). +4. Owned selection/caret model achieves cross-level/cross-object selection parity against real fixtures + (the ~14.4K-LOC `IVwSelection` replacement). +5. Editing routes through `IEditSession` + the single global LCModel undo stack; per-surface Path-3 parity + bundle (semantic + visual + workflow + performance) at **100% and 150% DPI**; typing latency budgets + committed and met (extended from the field foundation to multi-paragraph). +6. Every sub-epic satisfies the §7 per-surface Definition of Done; `./build.ps1` + `./test.ps1` green; + retrospective folds lessons into the skill set in the **same** PR. +7. The **Stage 9 ↔ 7A engine seam** is explicitly stated and frozen before the 9.2/9.3 API is finalized. + +**Dependencies.** +- **Depends on:** Stage 1 (platform/enablement kit). Runs in parallel with Track II. +- **Gates / blocks:** **Stage 7A** (interlinear + sandbox — hard-blocks on extended Stage 9); **Stage 6b** + (morphology/grammar document editors — `S9 → S6`, the plan's single biggest missing graph edge); + **Stage 5 Tier C** (`FwFindReplaceDlg`/`FwStylesDlg`, which host `IVwRootSite`/`SimpleRootSite` — + re-tiered out of junior Stage 5); **Stage 4** only for the one deferred `StText` field (lexicon + comments/notes), which **9.1** unblocks (Stage 4 ships those read-only until then); **Stage 8a** + (Views-coupled list editors); **Stage 10B** (Graphite managed-path removal is gated on 9.0's coverage + proof). Coordinate **9.3 ↔ Stage 3** (`VwLazyBox` lazy/virtualized box realization overlaps Stage 3's + virtualization primitive — share the realization-window approach). +- **Not in scope (Stage 13):** native `Src/views` deletion, `IVwRootBox`/`IVwGraphics`/`IVwEnv` render-COM + retirement, `ViewsInterfaces` split (keep `IVwCacheDa`), native `GraphiteEngineClass` deletion. + +**Rough size:** **XL.** Multi-quarter senior effort; each of 9.1–9.4 is itself a substantial sub-epic +and 9.2 alone replaces ~14.4K LOC of cross-level selection logic. + +--- + +## Sub-epics / stories + +### 9.0 — Spike + G0–G3 coverage scan (GATE) + +**Summary:** De-risk the #1 framework gap; decide build-vs-extend; enumerate the dropped-script list. +Blocking gate — **do not open 9.1+ until 9.0 exits.** + +**Type:** Sub-epic (gate). **Build-vs-extend:** this sub-epic *produces* that decision. + +**Description.** Run the named spike scenarios, each producing a Path-3 bundle or a written go/no-go (not +"looks fine"): (1) mixed bidi (Arabic/Hebrew + Latin), (2) CJK IME, (3) custom writing systems, +(4) multi-paragraph `StText` editing, (5) selection across structured content, **(6) a Sandbox/interlinear +scenario** exercising the Stage 7A constructs — in-memory presentation cache/`CachePair`/`VwCacheDa` +replacement, icon/picture-anchored hit-test combo editing, and aligned multi-line interlinear grid layout. +Also run the **G0–G3 LDML coverage scan** (salvage the classifier + `km.ldml` fixture from +`graphite-transition-support` tasks 1.3/1.4/3.2): scan project LDML for `IsGraphiteEnabled` + Graphite-only +feature strings and classify each WS as **G0** (dual-engine, OT-safe), **G2** (dual-engine but renders +differently under OT), or **G3** (Graphite-only, no OT path — e.g. Awami Nastaliq). The expected/recorded +build-vs-extend outcome for the spike to validate: **extend** the field foundation for 9.1; **build** an +owned `TextLayout`-based selection/layout layer for 9.2+ (the open Avalonia bidi/caret `TextBox` defects do +not scale to documents). IME/RTL evidence requires realized-window lanes, not headless alone. + +**Acceptance criteria.** +- All six spike scenarios produce a Path-3 bundle or a written go/no-go; the Sandbox/interlinear scenario + is included and passes (or its risks are explicitly recorded). +- A **build-vs-extend decision is recorded** (extend 9.1 / build owned layer 9.2+), with the spike either + validating or revising the predicted outcome. +- The **G0–G3 coverage scan output** lists the exact G3 (Graphite-only) projects/scripts and affected + writing systems — this is the **document-and-notify obligation input** for §11.1 (consumed by Stage 10B/13). +- Forbidden-symbol audit green on all spike artifacts. + +**Dependencies.** Depends on Stage 1. **Gates all of 9.1–9.4.** Its outputs gate **Stage 10B** (coverage +proof) and de-risk **Stage 7A** (interlinear/sandbox scenario). The Stage 9↔7A seam is drafted here. + +**Build-vs-extend callout:** N/A — this sub-epic decides it. + +**Labels:** `track-longpole`, `lead-senior`, `gate`, `parity-blocked-by:views-engine`, `spike`. **Size: L.** + +--- + +### 9.1 — Multi-paragraph `StText` editing + +**Summary:** Close the one deferred string blocker — multi-paragraph `StText` editing (paragraph model, +split/merge, para-level properties). The smallest document-model step. + +**Type:** Sub-epic. **Build-vs-extend: EXTEND.** Model a paragraph as a stack of the existing per-WS +editors over `FwMultiWsTextField` + `RegionRichTextEditAlgorithms` + `RegionImeCompositionState`. This is +the only sub-epic where extending the field foundation is viable. + +**Description.** Add a managed paragraph/`StText` model with paragraph splitting/merging and paragraph-level +properties, layered on the existing single-paragraph field editing. StText edits are multi-paragraph LCModel +mutations — keep them inside the fenced `IEditSession` and route through the single global undo stack. Unblocks +lexicon comment/note fields (the Stage 4 deferred `StText` field) and Notebook detail. + +**Acceptance criteria.** +- Multi-paragraph `StText` create/edit/split/merge with para-level props at parity on a target surface. +- Edits route through `IEditSession` + the single global LCModel undo/redo stack (no parallel history). +- Path-3 parity bundle (semantic + visual + workflow + performance) at 100% + 150% DPI; typing latency + budget met. Forbidden-symbol audit green. + +**Dependencies.** Gated on **9.0** exit. Unblocks the Stage 4 deferred `StText` field and Notebook detail. + +**Build-vs-extend callout:** **EXTEND** the field foundation. + +**Labels:** `track-longpole`, `lead-senior`, `parity-blocked-by:views-engine`. **Size: L.** + +--- + +### 9.2 — Owned structured selection/caret model + +**Summary:** Build the FieldWorks-owned managed selection/caret model replacing `IVwSelection`/`SelLevInfo`/ +`SelectionHelper` — selection across nested objects and structural levels. The ~14.4K-LOC core and the +deepest correctness surface in the program. + +**Type:** Sub-epic. **Build-vs-extend: BUILD.** Stock `TextBox` owns selection/caret and cannot express +cross-object selection or FieldWorks box semantics; the open Avalonia bidi/caret defects (reversed-selection +caret, Shift+Arrow, Home/End, word-jump, caret-under-selection) do not scale to documents. Use the native +`VwSelection`/`SelLevInfo` model as the **reference spec, not the implementation** — a clean C# rewrite. + +**Description.** Own a managed caret/selection model above the rendering layer: insertion points, ranges, +and structured selections that span paragraph and embedded-object boundaries and nested structural levels. +Hit-testing, anchor/end, normalization, and selection-driven commands. Exercise selection across structured +content with real fixtures (the 9.0 spike's cross-structure scenario is the prototype). IME/RTL caret +behavior requires realized-window evidence. + +**Acceptance criteria.** +- Cross-level / cross-object selection parity against real fixtures; insertion-point and range semantics + match the legacy `IVwSelection` behavior captured in baselines. +- No reliance on stock `TextBox` selection/caret for document surfaces. +- Realized-window evidence for IME/RTL caret behavior; Path-3 bundle at 100% + 150% DPI. Forbidden-symbol + audit green. + +**Dependencies.** Gated on **9.0** exit. Consumed by **9.3**, **9.4**, **Stage 7A**, **Stage 6b**. + +**Build-vs-extend callout:** **BUILD** an owned selection/caret layer. + +**Labels:** `track-longpole`, `lead-senior`, `parity-blocked-by:views-engine`. **Size: XL.** + +--- + +### 9.3 — Owned `TextLayout`-based layout/box model + +**Summary:** Build the FieldWorks-owned layout/box model over Avalonia `TextLayout` (HarfBuzz/Skia) +replacing the `VwParagraphBox`/`VwStringBox`/`VwGroupBox`/`VwTableBox`/`VwLazyBox` hierarchy — only the +editable/selection/box layer, **not** re-implementing shaping. + +**Type:** Sub-epic. **Build-vs-extend: BUILD.** `TextLayout` supplies shaping + line-breaking + hit-testing +as a managed primitive; FieldWorks owns the box/line model and editable layer above it. The native `VwBox` +hierarchy is the **reference spec**. + +**Description.** Implement an owned box/line model (paragraph/string/group/table/lazy/picture analogues) +over `TextLayout` for document surfaces where stock `TextBox`/`TextLayout` is insufficient — justified/complex +multi-line StText, RTL paragraphs, drop-caps, tables, overlays. `VwLazyBox` flags that **lazy/virtualized box +realization** is a document-engine concern: **coordinate with Stage 3** so the engine shares the +realization-window approach rather than reinventing virtualization (large StTexts/interlinear regress without it). + +**Acceptance criteria.** +- Document layout parity (justification, complex line-breaking, RTL paragraphs, drop-caps) on target + surfaces; visual lane permits the intentional Fluent restyle but asserts functional/density fidelity. +- Lazy/virtualized box realization for large documents; coordinated with the Stage 3 virtualization primitive + (no duplicate realization engine). Performance ≤ legacy × 1.2 or accepted delta recorded. +- Path-3 bundle at 100% + 150% DPI. Forbidden-symbol audit green. + +**Dependencies.** Gated on **9.0**; consumes **9.2**. **Coordinate with Stage 3** (`VwLazyBox` ↔ +virtualization). Consumed by **Stage 7A** (aligned interlinear grid). + +**Build-vs-extend callout:** **BUILD** an owned `TextLayout`-based box/layout layer. + +**Labels:** `track-longpole`, `lead-senior`, `parity-blocked-by:views-engine`, `parity-blocked-by:stage-3-virtualization`. **Size: XL.** + +--- + +### 9.4 — Embedded objects / tables / footnotes / overlays + +**Summary:** Editable embedded objects/ORCs, footnotes, pictures, tables, and overlays — the +`VwTableBox`/`VwPictureBox`/ORC-anchor/`IVwOverlay` long tail. The difference between "lexicon notes work" +and "Scripture/structured documents work." + +**Type:** Sub-epic (can trail). **Build-vs-extend: BUILD** owned managed equivalents. + +**Description.** Editable embedded content: ORC anchors and editing, footnotes, pictures, tables, and +overlays. Owned managed equivalents — the Avalonia editor draws its own squiggles and queries `ISpellEngine` +directly; it must **not** call `SetSpellingRepository`/`IGetSpellChecker` (those exist only for `VwRootBox`). +Footnotes/overlays/ORC editing get owned managed equivalents, not native bridges. Easy to under-scope — +budget the long tail explicitly. + +**Acceptance criteria.** +- Editable ORC/footnote/picture/table/overlay parity on target surfaces; spell-check via `ISpellEngine` + with owned squiggle rendering (no `VwRootBox` spelling APIs). +- Path-3 bundle at 100% + 150% DPI. Forbidden-symbol audit green; edits through `IEditSession` + global undo. + +**Dependencies.** Gated on **9.0**; consumes **9.2** and **9.3**. May trail 9.1–9.3. + +**Build-vs-extend callout:** **BUILD** owned managed equivalents for embedded/overlay content. + +**Labels:** `track-longpole`, `lead-senior`, `parity-blocked-by:views-engine`. **Size: L–XL.** + +--- + +## Notes / open questions + +- **Graphite / Awami Nastaliq doc-and-notify obligation (resolved §11.1).** Graphite is removed entirely — + no escape-hatch, no gating on a Nastaliq solution. HarfBuzz covers the large majority of formerly-Graphite + scripts (SIL dropped Graphite from Charis/Doulos v7 in 2025), **but Awami Nastaliq (Urdu/Arabic Nastaliq) + is Graphite-ONLY by design** — OpenType lacks the collision avoidance Nastaliq needs and SIL has no + OpenType replacement. These are exactly FieldWorks' minority-language users. The **9.0 G0–G3 scan is not a + go/no-go gate** but is **required** to enumerate the exact dropped-script list + affected projects, so the + program can **document the loss and notify affected users with migration guidance** before removal ships + (a **Stage 10B / Stage 13** deliverable). Native `GraphiteEngineClass` deletion stays in **Stage 13** + (legacy surfaces use it during coexistence). HarfBuzz does **not** implement Graphite — `hb-graphite2` + only delegates to external `libgraphite2` and is off by default — so "managed only" genuinely means *no + Graphite shaping at all*. **G3 has no in-app fidelity path after removal** (open product-comms risk, but + the engineering decision is settled). + +- **The explicit Stage 9 ↔ 7A engine seam.** Stage 9 owns the **engine seams** — selection, layout, box + model, editable structured content, the in-memory presentation cache/`CachePair`/`VwCacheDa` replacement, + hit-test combo editing, and aligned interlinear grid layout primitives. **Stage 7A owns the + interlinear-specific composition** (`InterlinDocRootSiteBase`, `InterlinDocForAnalysis`, `SandboxBase`, + `ConstChartBody`, `InterlinRibbon`) over those seams. State this split before freezing the 9.2/9.3 API so + Stage 7A does not inherit unscoped engine work and Stage 9 does not absorb interlinear UI. **Action:** + 9.0's spike *must* include the Sandbox/interlinear scenario or Stage 7A inherits an unvalidated dependency + and stalls late. + +- **Open question — is the owned selection model (9.2) buildable to parity?** `VwSelection` is ~14.4K LOC of + cross-level/cross-object logic; this is the single largest correctness surface and the deepest unknown. + The 9.0 spike must exercise selection across structured content with real fixtures before committing the + build. (High risk.) + +- **Open question — does stock `TextLayout` give enough for FieldWorks document layout?** (justification, + complex line-breaking, drop-caps, inverted/RTL paragraphs, overlays, tables). If not, 9.3 grows toward + re-implementing more of the box engine than budgeted. (Med-High.) + +- **Coordinate 9.3 with Stage 3.** `VwLazyBox` lazy realization is load-bearing for large StTexts and + interlinear; the owned layout must virtualize box realization or large documents regress — share the + realization-window approach with Stage 3 rather than reinventing it. (Med.) + +- **IME at document scale** across structured boundaries (composition spanning paragraph/object edges) + needs realized-window evidence — environment-sensitive, not headless-provable. (Med.) diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-10-browser-pdf-graphite-removal.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-10-browser-pdf-graphite-removal.md new file mode 100644 index 0000000000..9905d708e0 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-10-browser-pdf-graphite-removal.md @@ -0,0 +1,234 @@ +# Stage 10 — Browser/PDF & dictionary-preview replacement; Graphite (managed-path) removal (Epic draft) + +> JIRA-ready draft derived from `complete-migration-program.md` §4 Stage 10 + Post-review callout, +> §7 Definition of Done, §10 JIRA structure/labels, §11.1 resolved decision, and +> `reviews/stage-10-browser-pdf-graphite-removal.md` + `reviews/00-cross-comparison-synthesis.md` +> §3/§4/§6/§7. Per synthesis §10, epics follow the §6 sub-epic map, not the one-row-per-stage table. + +--- + +## Epic + +**Summary:** Stage 10 — Retire native-bound rendering: replace Gecko/XULRunner dictionary preview +and PDF export with managed Avalonia/WebView2, and remove Graphite from the managed/Avalonia +rendering path. + +**Type:** Epic (parent). Decomposes into child epics **10A** (Gecko/PDF/preview) and **10B** +(Graphite classify + managed-path removal), each with its own gate. + +**Labels:** `track-surfaces`, `lead-senior`, `parity-blocked-by:rendering-engine`. + +**Description:** +Stage 10 bundles two genuinely distinct workstreams that share the theme "retire native-bound +rendering" but have different blast radius, gates, and critical-path positions, so they are split +into two child epics under this parent (synthesis §3, review §1): + +- **10A — Browser/PDF/dictionary-preview replacement (Gecko/XULRunner retirement).** A + surface-migration + packaging problem. The dictionary preview/doc-view content is generated as + managed XHTML/CSS (`Src/xWorks/LcmXhtmlGenerator.cs`, `ConfiguredLcmGenerator.cs`, + `DictionaryExportService.cs`, `CssGenerator.cs`); **Gecko only *displays* it**, so the + replacement is "render this HTML string in a managed view," not "rebuild the dictionary + renderer." The dominant cost is the **process-wide XULRunner bootstrap** in + `Src/Common/FieldWorks/FieldWorks.cs:164-192` (hard `ApplicationException` if the Firefox folder + is missing) and the shutdown double-free hack at `:399-409` — these, not the leaf browser + controls, are the true "Gecko removed" gate. PDF export is a single call site + (`Src/xWorks/XhtmlDocView.cs:849-925`, invoking the renamed `FieldWorksPdfMaker.exe`) → + `CoreWebView2.PrintToPdfAsync`. +- **10B — Graphite removal (managed/Avalonia path only).** A rendering-core + data-policy problem + gated on Stage 9 proving HarfBuzz/managed shaping covers the formerly-Graphite scripts. The single + selection point is `Src/Common/SimpleRootSite/RenderEngineFactory.cs:107-126` + (`GetRenderingEngine`). **Native `GraphiteEngineClass` deletion is Stage 13, not here** — the + `RenderEngineFactory` Graphite branch is shared by **legacy** WinForms+Views surfaces, so deleting + it at Stage 10 breaks legacy rendering mid-program (review §6 Q1; synthesis §3/§7). 10B only removes + Graphite from the managed/Avalonia path, runs the **G0–G3 classifier** as pre-removal evidence, and + discharges the document/notify obligation from resolved §11.1. **`DefaultFontFeatures` is kept** + (LCModel-owned, reused for OpenType export at `CssGenerator.cs:1562` and + `WordStylesGenerator.cs:479,488` — do not delete). + +**Acceptance criteria:** +- Both child epics (10A, 10B) closed against their independent gates. +- Per-region §7 Definition of Done satisfied for every migrated surface (semantic + visual + + workflow + performance baselines, 100% + 150% DPI, `EngineIsolationAuditTests` green, retrospective + folded into the skill set in the same PR). +- `EngineIsolationAuditTests` (`Src/Common/FwAvalonia/FwAvaloniaTests/EngineIsolationAuditTests.cs`) + pass for the Avalonia path; Gecko/`GeckofxHtmlToPdf` and `GraphiteEngineClass` are absent from the + Avalonia rendering path. +- `./build.ps1` + `./test.ps1` green. +- Superseded banners added to all four `graphite-transition-support` files (see Notes). + +**Dependencies:** +- **Stage 9 (hard gate for 10B):** HarfBuzz coverage proof + the Stage 9.0 G0–G3 fixture-scan evidence. +- **Stage 12 (Avalonia 12) — 10A ordering tension:** the clean managed preview control + (`Avalonia.Controls.WebView`) ships in Av12; see Notes / open questions. +- **Stages 6/7/8 (per-surface Gecko consumers):** the DOM-interaction surfaces land *with their + owning surface stage*; 10A owns startup/shutdown decoupling, the PDF path, the shared + `HtmlControl` wrapper, and packaging/installer. +- **liblcm/LCModel owners (10B):** deprecation coordination for `IsGraphiteEnabled` / + `DefaultFontFeatures` (external repo cadence). +- **Stage 13:** native `GraphiteEngine` deletion + RegFree/build cleanup + Gecko harvest removal land + there, not here. + +**Rough size:** Large (parent). Two senior-led child epics; cross-cutting sweep across Stages 6/7/8 +plus shell startup, a rendering-core change, and packaging/installer work. + +--- + +## Sub-epics / stories + +### 10A — Gecko / PDF / dictionary-preview replacement + +**Summary:** Decouple the XULRunner bootstrap, replace the Gecko-bound preview/doc views with a +managed Avalonia/WebView2 HTML renderer, and replace PDF export with `CoreWebView2.PrintToPdfAsync`. + +**Type:** Epic (child of Stage 10). + +**Description:** +The preview is managed XHTML/CSS — Gecko only displays it — so the architecturally favorable case is +"render this HTML." **First task: make XULRunner init lazy/conditional** in `FieldWorks.cs` so +"Gecko absent" stops being a hard process-fail; this is the keystone that lets the rest of 10A proceed +incrementally. Gecko-bound display call sites: `XhtmlRecordDocView.cs:67` +(`new XWebBrowser(BrowserType.GeckoFx)`, the Lexical Edit preview pane, on by default), +`XhtmlDocView.cs:59` (Dictionary/Reversal/Classified doc views), `DictionaryConfigurationDlg.cs:44-49` +(configure-dictionary live preview, with `GeckoElement` DOM walking at `:228`). DOM-interaction +surfaces (config highlight; interlinear config via `AutoJSContext`/`GeckoInputElement`; parser trace +`WebPageInteractor.cs`) are the real engineering cost and are re-expressed as WebView2 +`ExecuteScriptAsync`/message-channel interop or reworked so interaction happens in Avalonia — these +pieces land with their owning surface stages (6/7/8). PDF: single call site → `PrintToPdfAsync`, +collapsing the PDF-maker exe, the binding-redirect patch (`Build/PackageRestore.targets:477-520`), and +the ICE30 suppression (`Build/Installer.legacy.targets`) into in-box managed code. + +**Acceptance criteria:** +- XULRunner bootstrap is lazy/optional; the app no longer hard-fails when the Firefox/XULRunner folder + is absent; the shutdown double-free hack is removed. +- Dictionary preview + doc views render via the managed Avalonia HTML renderer with semantic/visual + parity (§7 DoD, 100% + 150% DPI). +- PDF export uses `CoreWebView2.PrintToPdfAsync`; `FieldWorksPdfMaker.exe`/`GeckofxHtmlToPdf` harvest, + binding-redirect patch, and ICE30 suppression are removed from the build/installer. +- Print parity (large-dictionary >10k-entry path and the small-print `Window.Print()` fallback) is met. +- No archived/CVE-bearing HTML→PDF lib (wkhtmltopdf/DinkToPdf) and no bundled-Chromium path + (Puppeteer/Playwright) is introduced; QuestPDF rejected (not an HTML renderer). +- `EngineIsolationAuditTests` confirm Gecko symbols / `GeckofxHtmlToPdf` absent from the Avalonia path. + +**Dependencies:** Stage 12 (Av12 WebView — see Notes; interim WebView2-direct on the 11.x line is the +alternative); per-surface DOM-interop pieces gated with Stages 6/7/8. Final Gecko harvest removal → +Stage 13. + +**Labels:** `track-surfaces`, `lead-senior`, `parity-blocked-by:html-renderer`. + +**Rough size:** Large. Startup decoupling + per-surface DOM-interop rewrites + packaging/installer +changes; cross-cutting across Stages 6/7/8 + shell startup. + +--- + +### 10B — Graphite classify + managed-path removal + +**Summary:** Run the G0–G3 coverage classifier as pre-removal evidence, then remove Graphite engine +selection from the managed/Avalonia rendering path (always returning Uniscribe/OpenType), keeping +`DefaultFontFeatures` for OpenType export. Native engine deletion is deferred to Stage 13. + +**Type:** Epic (child of Stage 10). + +**Description:** +Gated on Stage 9 proving HarfBuzz coverage. Graphite is well-isolated at the decision point: the single +selection branch is `RenderEngineFactory.cs:107-126` — on the managed/Avalonia path this becomes a +one-branch change that always returns the OpenType engine. The managed COM factory +(`Src/Common/ViewsInterfaces/Views.cs:2605-2767`, `GraphiteEngineClass`), the native engine +(`Src/views/lib/GraphiteEngine.{h,cpp}`, `GraphiteSegment.{h,cpp}`, `Src/views/views.vcxproj`, +`Lib/src/graphite2/`, `Build/Windows.targets`, `Build/RegFree.targets:50-51`), and WS-setup UI +(`DefaultFontsControl.cs`, `FontFeaturesButton.cs:421`, `FwWritingSystemSetupModel.cs:514-523`, +`GraphiteFontFeatures.cs`) are **not deleted here** — that wholesale native decommission + RegFree/build +cleanup lands in **Stage 13** because the shared `RenderEngineFactory` branch is still used by legacy +WinForms+Views surfaces during coexistence (review §6 Q1; synthesis §3/§7). **Keep +`DefaultFontFeatures`**: it is LCModel-owned, not Graphite-specific, and drives `CssGenerator.cs:1562` / +`WordStylesGenerator.cs:479,488` for OpenType export — only the Graphite *engine selection* and the +`GraphiteFontFeatures.ConvertFontFeatureCodesToIds` ID-conversion path retire. `IsGraphiteEnabled` is +read-as-false pending an LCModel-owner deprecation (coordinate with liblcm). Per resolved §11.1, the +G0–G3 scan is salvaged from `graphite-transition-support` as pre-removal *impact evidence* (which +projects break when Graphite is gone), feeding the document/notify obligation — see the dedicated story +below. + +**Acceptance criteria:** +- G0–G3 classifier / fixture-LDML scan run; output enumerates the exact dropped-script list + affected + projects as dropped-script evidence (not a go/no-go gate — removal proceeds per resolved §11.1). +- Graphite engine selection removed from the managed/Avalonia `RenderEngineFactory` path; the OpenType + (Uniscribe/HarfBuzz) engine is always returned on that path. +- `GraphiteFontFeatures.ConvertFontFeatureCodesToIds` ID-conversion path retired. +- `DefaultFontFeatures` retained and verified still functioning for OpenType/Word-styles export. +- `IsGraphiteEnabled` treated as false on the managed path; **no native deletion**, no RegFree/build + changes, no WS-setup UI removal in this epic (all deferred to Stage 13). +- `EngineIsolationAuditTests` confirm `GraphiteEngineClass` absent from the Avalonia path (audit + graduates to whole-codebase absence only after Stage 13). +- Stage 9 HarfBuzz coverage proof is the entry gate; document/notify story (below) closed. + +**Dependencies:** **Stage 9** (hard gate — HarfBuzz coverage proof + G0–G3 evidence); liblcm/LCModel +owners (property deprecation); **Stage 13** consumes the native deletion + RegFree/build cleanup. + +**Labels:** `track-surfaces`, `lead-senior`, `parity-blocked-by:harfbuzz-coverage`. + +**Rough size:** Medium. The managed-path change is small (one-branch); the weight is the classifier +evidence run, the keep/retire boundary discipline, and LCModel coordination. + +--- + +### 10B-1 — Dropped-script documentation & user notification + +**Summary:** Using the G0–G3 scan output, document the exact dropped (Graphite-only) scripts/projects +and notify affected users with migration guidance before removal ships. + +**Type:** Story (under 10B). + +**Description:** +Discharges the obligation incurred by resolved decision §11.1 ("accept the loss, document + notify"). +HarfBuzz covers the large majority of formerly-Graphite scripts, **but Awami Nastaliq (Urdu/Arabic +Nastaliq) is Graphite-only with no OpenType/HarfBuzz path** and SIL has no OpenType replacement planned — +these are minority-language FieldWorks users. The Stage 9.0 LDML G0–G3 scan enumerates the exact dropped +scripts + affected projects; this story turns that into (a) published documentation of the loss and +(b) user notification with migration guidance. Salvaged from `graphite-transition-support`'s +font-replacement-policy + outreach tasks (its outreach obligation is *greater*, not lesser, under full +removal). The settings-preservation rule survives: read stored WS settings, never silently rewrite; +migrate only on explicit user action with undo. + +**Acceptance criteria:** +- Dropped-script list (incl. Awami Nastaliq / Graphite-only) and affected-project inventory documented + from the G0–G3 scan output. +- User-facing migration guidance published; affected users notified **before** removal ships. +- Settings-preservation rule honored (no silent rewrite of stored WS Graphite settings). +- Product-owner sign-off recorded on the accepted loss for Graphite-only projects. + +**Dependencies:** Stage 9.0 G0–G3 scan output (source evidence); 10B managed-path removal (this story +must close before removal ships per §11.1). + +**Labels:** `track-surfaces`, `lead-senior`, `user-comms`, `parity-blocked-by:harfbuzz-coverage`. + +**Rough size:** Small–Medium. Non-engineering-heavy but a hard release gate; product-owner + outreach +coordination. + +--- + +## Notes / open questions + +- **S10 ↔ S12 WebView ordering tension (10A).** The clean managed preview/browser control, + `Avalonia.Controls.WebView`, ships with **Avalonia 12 (Stage 12)** — yet the §5 dependency graph shows + `S10 → S12`, implying 10 precedes 12. Resolve one of two ways: (a) 10A uses an **interim + WebView2-direct integration** on the Avalonia 11.x line, or (b) 10A's managed-preview piece is + **sequenced with/after Stage 12**. Either way the §5 mermaid edges need updating — 10A's preview work + is not strictly before 12. (Review §4; synthesis §6/§7.) +- **Av12 WebView reintroduces a native runtime (Windows).** "Remove Gecko" trades XULRunner for the + WebView2 runtime (OS-serviced, cross-platform, not bundled). Acceptable but not "zero native" — + acknowledge rather than imply pure managed rendering. (Review §6 Q3.) +- **Native Graphite deletion deferred to Stage 13.** The `RenderEngineFactory` Graphite branch is shared + by legacy WinForms+Views surfaces, so the native `GraphiteEngine`/`GraphiteSegment` deletion, + `views.vcxproj`/`graphite2` build removal, RegFree manifest cleanup, and WS-setup UI removal land with + the native Views decommission in **Stage 13** — deleting at Stage 10 would break legacy rendering + mid-program. 10B removes Graphite only from the managed/Avalonia path. (Review §6 Q1; synthesis §3/§7.) +- **DOM-interaction interop strategy (10A).** Config-highlight, interlinear config, and parser-trace each + use a different Gecko DOM-interop pattern; a per-surface decision (WebView2 `ExecuteScriptAsync` vs + rework-in-Avalonia) is needed and is owned by the respective surface stage (6/7/8). (Review §6 Q4.) +- **`graphite-transition-support` superseded — already done.** Per synthesis §8 and review §5, superseded + banners are added to all four files (`proposal.md`, `design.md`, `tasks.md`, spec delta); its core + premise (keep Graphite, warn on Avalonia, sunset at M2) is reversed by "remove, not warn." Cross-refs + in `lexical-edit-avalonia-migration` (§5 re-homing + `graphite-decommissioning.md` banner) point at + Stage 10B. The G0–G3 classifier, font-outreach obligation, and settings-preservation rule are salvaged + into 10B (above). *Banner work noted as already complete; tracked here for traceability only.* +- **liblcm coordination (10B).** `IsGraphiteEnabled` / `DefaultFontFeatures` are LCModel-owned (external); + property deprecation depends on the liblcm repo + its release cadence. (Review §6 Q5.) diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-11-application-shell-replacement.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-11-application-shell-replacement.md new file mode 100644 index 0000000000..518372ec3a --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-11-application-shell-replacement.md @@ -0,0 +1,410 @@ +# Stage 11 — Application shell replacement (Epic draft) + +> JIRA-ready epic + sub-epic draft for **Stage 11** of the FieldWorks → Avalonia complete +> migration program. Source of truth: `complete-migration-program.md` (§6 Stage 11 + Track IV +> callout; §7 Definition of Done; §10 JIRA structure/labels), the Stage 11 review +> (`reviews/stage-11-application-shell-replacement.md`), the cross-comparison synthesis +> (`reviews/00-cross-comparison-synthesis.md` §3/§6/§7), and the existing OpenSpec change whose +> body **is** this stage: `fieldworks-avalonia-shell-migration` (proposal/design/tasks/specs). +> +> **This epic does not contradict `fieldworks-avalonia-shell-migration`** — it is the JIRA +> projection of that change. Where this draft and the change appear to differ, the change wins; +> open questions are flagged in the final section. + +--- + +## Epic + +**Summary:** Replace the WinForms/XCore application shell with an Avalonia default shell — +application lifetime, windowing, navigation, menus/toolbars/status, typed shell composition from +`Main.xml`, command/state bridging, an area-by-area main-screen registry, and the final +default-switch + WinForms/FlexUIAdapter decommission from the default path. + +**Type:** Epic + +**Labels:** `track-shell`, `lead-senior`, `parity-blocked-by:IXCoreCommandBridge`, +`parity-blocked-by:dialog-owner-port`, `av12-delta-localized` +*(label vocabulary per program §10: `track-foundation|track-surfaces|track-longpole|track-shell`, +`lead-junior|mid|senior`, `parallel-safe`, `parity-blocked-by:`)* + +**Description:** + +Stage 11 is the IV-Shell stage and the critical path for making FieldWorks a true Avalonia +application rather than an Avalonia island inside the legacy WinForms host. Its body is the +existing `fieldworks-avalonia-shell-migration` change (10 task groups, ~20 ADDED requirements). +That change is a second migration program in its own right, so this epic decomposes it into six +independently gateable sub-epics (**11a–11f**) per synthesis §6, matching the existing change's +natural task-group seams. + +The shell coupling is real and deep, and the work is senior-only / multi-quarter: + +- The process entry point is WinForms-bound: `Src/Common/FieldWorks/FieldWorks.cs` runs + `[STAThread] static int Main` → `Application.Run()`, threading `Form.ActiveForm` / + `Form dialogOwner` through ~15 project-lifecycle methods (`OpenExistingProject`, + `ChooseLangProject`, `CreateNewProject`, `BackupProject`, `RestoreProject`, + `ArchiveProjectWithRamp`, `HandleRestoreRequest`, …). +- `Src/XCore/xWindow.cs` is a **2,498-line `: Form`** holding `CollapsingSplitContainer`, + `RecordBar`, sidebar, `StatusBar`, the `IUIAdapter` rebar/sidebar/menubar adapters, and a + `Mediator`/`PropertyTable`. `FwXWindow` (`Src/xWorks/FwXWindow.cs`, 2,588 lines) derives from it. + Replacement cannot be incremental within one window instance — a **parallel Avalonia main window + + area-by-area screen migration** is the only viable path (Strangler Fig at the area/tool + boundary). +- Because `XWindow : Form` and `Application.Run()` pin the whole process to **net48 / Avalonia + 11.x** through all of Stage 11 (one CLR per process), every line of Stage 11 Avalonia code must + be written **Av12-delta-localized** (confine unavoidable Avalonia-11-only APIs — clipboard, + binding, focus, theming — to named seams) so the 11 → 12 runtime jump ports surviving code, not + rework. + +**Stage 2 / Stage 11 split (state explicitly):** Stage 2 = *contract / port design + seam +stand-up* (the lifetime, main-window, active-window-registry, dialog-owner, dispatcher, shutdown, +modal-state ports, plus `IXCoreCommandBridge`); Stage 11 = *implementation + shell-scoping + +default switch*. Stage 11 **consumes and shell-scopes** those ports — it does **not** redefine +`IUiScheduler` / `IRegionLifetime` / the lifetime/command contracts (already in +`Src/Common/FwAvalonia/Seams/ISeams.cs`). + +**Stage 5 → 11 is a *finishing* edge, not a block.** On the coexistence path (Avalonia 11.x + .NET +FW 4.8) Avalonia modal windows are **not** supported; the only supported modal pattern is a +WinForms `Form` owning embedded Avalonia content. Stage 5 therefore ships Avalonia dialog *content* +inside WinForms-owned modal forms and is **not** blocked by Stage 11. True Avalonia +`Window.ShowDialog` modality only becomes available once the Avalonia desktop lifetime owns the +main window — i.e. at the Stage 11 default switch — so flipping Stage-5 content from WinForms-owned +to `Window`-owned modality is a **distinct 11a task**, not free. + +**Acceptance criteria (epic-level):** + +- All six sub-epics (11a–11f) closed with their own gates green. +- All `fieldworks-avalonia-shell-migration` task groups (1–10) and ADDED requirements satisfied; + spec assertions hold: *Windowing uses framework-neutral lifetime services*; *Avalonia owns final + desktop lifetime*; *Hidden WinForms startup is disallowed*; *Shutdown is deterministic*; *Shell + XML imports into a typed shell definition*; *Unsupported constructs are diagnostic, not silently + omitted*; *Typed definition is the runtime target*; *Commands are typed descriptors*; *XCore + mediator bridges*; *Command parity validated*; *Screen registry + per-screen manifest*; *legacy + content explicit and temporary*; *Full-app smoke gates protect the default switch*. +- **Command-target / shortcut parity tests** pass (not merely a launch-and-click smoke test — + guard against "hallucinated parity at shell scale", §7 DoD + `parity-evidence.md`). +- Avalonia shell is the **default** only after hard gates pass; WinForms shell default path, + FlexUIAdapter default dependency (~3.2k lines), WinForms dynamic content host, retired dialogs, + and obsolete shell-XML runtime pieces removed from the default path. +- Per-surface §7 Definition of Done satisfied for each migrated screen (semantic + visual + + workflow + performance bundle; AutomationIds; localization lanes; `EngineIsolationAuditTests` + green; `./build.ps1` + `./test.ps1` green); evidence-language enforcement honored. +- All new shell code is Av12-delta-localized; an exit gate confirms Avalonia-11-only APIs are + confined to named seams. + +**Dependencies:** Stage 2 (ownership ports + `IXCoreCommandBridge` seam — single source of truth); +"enough of 5–8" Avalonia (areas enumerated in 11e); reuses the `Src/Common/FwAvalonia/ViewDefinition/` +compiler pipeline (Stage 0/4); blocks **Stage 12** (runtime jump cannot start until the WinForms +host is gone). Stage 5 → 11 is a finishing edge. Texts & Words / Interlinear (Stage 7, +Views-engine-heavy, depends on Stage 9) is the most likely **legacy island** at first switch. + +**Rough size:** **XL** (a second migration program; multi-quarter, senior-led). + +--- + +## Sub-epics / stories + +> Internal dependency shape (synthesis §6/§5, review §5.1): **11a (lifetime) + 11b (compiler) + +> 11c (command bridge) are foundational and can run in parallel**; **11d (composition) depends on +> 11b/11c**; **11e (screens) depends on 11d** + per-area Track-II completion; **11f (default switch +> + FlexUIAdapter removal) is last**. So: `11a ∥ 11b ∥ 11c → 11d → 11e → 11f`. + +### 11a — App lifetime & windowing *(critical path)* + +**Summary:** Stand up the Avalonia desktop lifetime, main-window ownership, active-window registry, +multi-window behavior, modal owner, and deterministic shutdown/disposal; re-home the ~15 `Form +dialogOwner` lifecycle seams behind the framework-neutral ports; flip already-migrated Stage-5 +dialog content from WinForms-owned to `Window`-owned modality at the switch. + +**Type:** Sub-epic / Story + +**Description:** This is the **long pole inside Stage 11**. Every `Form`-typed lifecycle seam in +`FieldWorks.cs` (`OpenExistingProject`, `ChooseLangProject`, `CreateNewProject`, `BackupProject`, +`RestoreProject`, `ArchiveProjectWithRamp`, `HandleRestoreRequest`, …) must move behind the Stage-2 +dialog-owner / lifetime ports before an Avalonia `Main` can exist. Implement classic Avalonia +desktop lifetime + explicit `Window` ownership over those ports; preserve `FieldWorks.cs` +`s_activeMainWnd` / `Form.ActiveForm as IFwMainWnd` multi-project / multi-window semantics in an +Avalonia active-window registry. Add the explicit **dialog-modality re-host** task: migrate the +Stage-5 WinForms-owned Avalonia dialog *content* to Avalonia `Window`-owned modality — the single +point where true Avalonia modal windows appear — tied to the full-app smoke gate. + +**Acceptance criteria:** +- Framework-neutral lifetime services own windowing; no `Form`/`Control` requirement remains in the + default shell path; *hidden WinForms startup is disallowed*; *Avalonia owns final desktop + lifetime*. +- Active-window registry preserves multi-project / multi-window semantics; shutdown/disposal of + windows, caches, dialogs, background services, and retained native services is deterministic. +- Stage-5 dialog content re-hosted to `Window`-owned modality and passing owner/modal, + cancellation, focus-return, accessibility, and localization tests behind the full-app smoke gate. +- Avalonia.Headless tests for dialog ownership / focus traversal; contract tests for startup, + active-window tracking, dialog ownership, shutdown, UI dispatch. + +**Dependencies:** Stage 2 ownership ports (consumes, does not redefine). Parallelizable with +11b/11c. The dialog-modality re-host depends on Stage 5 content existing (finishing edge 5 → 11). + +**Cross-reference (`fieldworks-avalonia-shell-migration` tasks):** **2** (Shell Contracts — 2.1–2.4, +the port consumption side), **5.2/5.4** (main window, app lifetime, dialog-ownership headless), +**10.1–10.2** (Avalonia app startup path; shutdown/disposal tests). Spec: lifetime/windowing/shutdown/ +hidden-startup requirements + dialog re-host (review §1, §4 action 3). + +**Labels:** `track-shell`, `lead-senior`, `parity-blocked-by:dialog-owner-port`, `av12-delta-localized` + +**Rough size:** **L–XL** (review confidence Medium on sizing — warrants its own spike). + +--- + +### 11b — Main.xml typed-shell compiler + +**Summary:** Build the typed shell-definition importer/compiler for `Main.xml` + area/tool XML +includes, with diagnostics for unsupported constructs and deterministic snapshot tests; reuse the +proven `ViewDefinition/` pipeline. + +**Type:** Sub-epic / Story + +**Description:** The *tractable* part of Stage 11. `DistFiles/Language Explorer/Configuration/Main.xml` +is ~989 lines with 118 ``s pulling 51 config XMLs (~15.7k lines). The command/choice model +already lives in typed C# (`xCoreInterfaces/Command.cs`, `ChoiceGroup.cs`, `Inventory.cs`), so the +compiler imports XML into types that **already have a runtime peer**. Reuse — do not reinvent — the +cache-keyed, off-thread, deterministic-snapshot machinery in `Src/Common/FwAvalonia/ViewDefinition/` +(same bet proven by the Lexical-Edit view-definition compiler). Represent commands, lists, areas/ +tools, menus, context menus, toolbars, status panes, shortcuts, icons, localization metadata, and +screen registrations. Keep XML as **import/audit input only** — the typed definition is the runtime +target. Diagnostics, not silent omission, for unsupported commands/listeners/dynamic-loaders/ +toolbar-widgets/status-panels/extension constructs. + +**Acceptance criteria:** +- `Main.xml` + includes import into a typed shell definition; unsupported constructs raise + diagnostics (never silently dropped); typed definition is the runtime target; localization + survives the import. +- Deterministic shell-definition snapshot tests pass; compiler shares the `ViewDefinition/` + cache-key / off-thread / snapshot pipeline. + +**Dependencies:** Reuses Stage 0/4 `ViewDefinition/` pipeline. Parallelizable with 11a/11c; **blocks +11d**. + +**Cross-reference (`fieldworks-avalonia-shell-migration` tasks):** **3** (Typed Shell Composition — +3.1 importer, 3.2 representation, 3.3 diagnostics, 3.4 snapshot tests). Spec: *Shell XML imports into +typed shell definition*; *Unsupported constructs are diagnostic*; *Typed definition is the runtime +target*; *localization survives*. + +**Labels:** `track-shell`, `lead-senior`, `parallel-safe`, `av12-delta-localized` + +**Rough size:** **M–L** (bounded, high transfer confidence; the "easy" sub-epic). + +--- + +### 11c — Command / state bridge + +**Summary:** Define typed command descriptors and bridge XCore mediator/property-table behavior into +Avalonia commands + active-target routing, with command-parity validation. Promotion of an existing +seam, not green-field. + +**Type:** Sub-epic / Story + +**Description:** `IXCoreCommandBridge` and `IRecordNavigationContext` already exist in +`Src/Common/FwAvalonia/Seams/ISeams.cs` and are consumed at the region edge +(`Src/xWorks/RecordEditView.cs`, `RecordClerkNavigationContext.cs`); `seam-catalog.md` §1 reserves +*"shell-scope wiring happens in the shell phase, not per region."* This sub-epic is the documented +**promotion** of that seam to shell-global scope. Define typed command descriptors with stable IDs, +labels, gestures, icons, visibility, enabled state, target resolution, and diagnostics; bridge XCore +mediator handlers + property-table state into Avalonia commands and active-target routing; preserve +command IDs/shortcuts as the stable contract so menus/toolbars/context-menus can be re-skinned +(Fluent restyle, program decision §11.4) while behavior parity is asserted by **command-target +tests, not pixels**. + +**Acceptance criteria:** +- Typed command descriptors expose stable IDs/labels/gestures/icons/visibility/enabled/target + resolution; XCore mediator/property-table bridged; one-at-a-time ("one-shot") commands honored. +- **Command parity validated** by command-enable/visible/shortcut/target-selection/mediator-bridge + tests (the shell-scale anti-hallucination gate). +- Menu / context-menu automation metadata + localization checks pass. + +**Dependencies:** Stage 2 `IXCoreCommandBridge` seam (promotes, does not redefine). Parallelizable +with 11a/11b; **feeds 11d**. + +**Cross-reference (`fieldworks-avalonia-shell-migration` tasks):** **4** (Command Routing and State — +4.1 descriptors, 4.2 bridge, 4.3 parity tests, 4.4 automation/localization). Spec: *Commands are +typed descriptors*; *XCore mediator bridges*; *Command parity validated*. + +**Labels:** `track-shell`, `lead-senior`, `parallel-safe`, `parity-blocked-by:IXCoreCommandBridge`, +`av12-delta-localized` + +**Rough size:** **M** (seams already seeded). + +--- + +### 11d — Shell composition / navigation & panes + +**Summary:** Build owned Avalonia controls replacing `SilSidePane`/OutlookBar, +`CollapsingSplitContainer`/`MultiPane`, `PaneBarContainer`, `RecordBar`; render menus/context-menus/ +toolbars/status panels; implement split/side panes, record-list region, collapse/restore, and layout +persistence. + +**Type:** Sub-epic / Story + +**Description:** Owned-control work comparable in weight to the Stage-3 grid build — none of these +WinForms controls (`SilSidePane/` OutlookBar + ~20 files, `MultiPane.cs` 674 lines, +`CollapsingSplitContainer.cs` 667 lines, `PaneBarContainer`, `RecordBar`) have stock Avalonia +peers. Implement the Avalonia shell skeleton: main-window navigation regions, content host, +status/progress region, diagnostics hooks, theme resources, accessibility root metadata. Render menu +and context-menu structures (labels, shortcuts, icons, separators, extension items, visibility, +enablement); standard/format/insert/view toolbars (including writing-system + style selectors); +status panels (message, progress, area, sort, filter, parsing, record number); split/side panes + +record-list region with collapse/restore + layout persistence. **Spike the build-vs-docking-library +decision alongside Stage 3** and record it with a pivot trigger (a docking library only if owned +controls cannot meet documented FieldWorks workflows — risk of under-scoping). Run the shell in +preview/sample mode before LCModel project startup. + +**Acceptance criteria:** +- Owned controls replace SilSidePane/MultiPane/CollapsingSplitContainer at parity; menus/toolbars/ + status/layout render with full label/shortcut/icon/visibility/enablement fidelity. +- Layout persistence + collapse/restore behavior preserved; theme resources + accessibility root + metadata present; AutomationIds on every control (mandatory, day one). +- Avalonia.Headless tests for shell creation, navigation host swapping, command dispatch, status + updates, focus traversal, pane state. +- Build-vs-library decision recorded with a fired pivot trigger if a docking library is adopted. + +**Dependencies:** **Depends on 11b (typed definition) + 11c (command bridge).** Spike alongside +Stage 3 owned-control work. Feeds 11e. + +**Cross-reference (`fieldworks-avalonia-shell-migration` tasks):** **5** (Avalonia Shell Skeleton — +5.1–5.4), **6** (Navigation — partial, 6.1–6.2 navigation host), **7** (Menus, Toolbars, Status, +Layout — 7.1–7.5, incl. the docking-library evaluation in 7.5). + +**Labels:** `track-shell`, `lead-senior`, `av12-delta-localized` + +**Rough size:** **L–XL** (custom-control build; review confidence Medium on sizing — spike). + +--- + +### 11e — Screen registry & area-by-area migration + +**Summary:** Map area/tool IDs from the typed shell definition to an Avalonia screen registry; +implement area/tool navigation + persisted `areaChoice`/`currentContentControl` compatibility; add a +per-screen manifest; migrate main screens area by area with explicit, temporary legacy islands for +the rest. + +**Type:** Sub-epic / Story + +**Description:** Strangle the shell at the area/tool boundary, running two main windows side by side. +Each migrated screen gets a manifest (entry points, shell commands, state, native-boundary status, +accessibility, performance, rollback, default-switch gates); non-migrated screens render as +**explicit, temporary legacy islands** (never silent fallback). **Enumerate the area gate +concretely:** which areas must be Avalonia before the default switch (Lexicon first, then Words/ +Grammar/Notebook/Lists) and which may remain explicit legacy islands. Texts & Words / Interlinear +(Stage 7, Views-engine-heavy, depends on Stage 9) is the most likely first-switch legacy island — +call it out in the manifest. Add memory-project + sample-project navigation tests. Confirm no +migrated Avalonia screen transitively pulls a `BarAdapterBase`/`MenuAdapter` during coexistence +(active-host contract should catch this). + +**Acceptance criteria:** +- Area/tool IDs map to an Avalonia screen registry; `areaChoice`/`currentContentControl` + persistence compatible; each migrated screen has a manifest with all required gate rows. +- Legacy content for non-migrated areas is explicit and temporary (manifested), never a silent + fallback; the first-switch legacy-island list (incl. Interlinear) is enumerated. +- Memory-project + sample-project navigation tests pass; active-host contract confirms no hidden + DataTree/Views/FlexUIAdapter pull on migrated screens. + +**Dependencies:** **Depends on 11d** + per-area Track-II completion ("enough of 5–8"). Feeds 11f. + +**Cross-reference (`fieldworks-avalonia-shell-migration` tasks):** **6** (Navigation and Screen +Registry — 6.1–6.4), **9** (Main Screen Migration — 9.1 Lexicon, 9.2 Words/Interlinear, 9.3 Grammar/ +Morphology, 9.4 Notebook, 9.5 Lists, 9.6 preview/print/browser-PDF isolation). Spec: *screen +registry*; *each migrated screen has a manifest*; *legacy content explicit and temporary*. + +**Labels:** `track-shell`, `lead-senior`, `av12-delta-localized` + +**Rough size:** **L** (per-area; absorbs Track-II head-count). + +--- + +### 11f — Startup / installer / default-switch + FlexUIAdapter removal + +**Summary:** Land the Avalonia startup path, installer/runtime packaging + dependency harvest, the +feature-flag/default selector, full-app smoke gates; flip the default to Avalonia only after hard +gates pass; remove the WinForms shell default path, FlexUIAdapter default dependency (~3.2k lines), +WinForms dynamic content host, retired dialogs, and obsolete shell XML from the default path. + +**Type:** Sub-epic / Story + +**Description:** The last sub-epic — the irreversible-feeling default flip and the decommission of +the legacy default path. Add the Avalonia app startup path (project selection, cache creation, +splash/safe-mode, remote-request listener, no-UI/app-server modes, update checks accounted for); +update installer/runtime packaging + dependency harvest for Avalonia shell assets; add the feature- +flag / default selector for the Avalonia shell; run full local build/test + app smoke gates; +**make Avalonia default only after hard gates pass.** Then remove the WinForms shell default path, +FlexUIAdapter default dependency (`Src/XCore/FlexUIAdapter/`, ~3.2k lines across Menu/Bar/Sidebar/ +Toolbar/PaneBar adapters), WinForms dynamic content host, retired dialogs, and obsolete shell-XML +runtime pieces. Deferring FlexUIAdapter removal until **after** the default switch is correct; before +removal, confirm no migrated Avalonia screen transitively pulls `IUIAdapter` during coexistence. +Revisit heavier reactive/region-framework alternatives only if pivot triggers in +`lexical-edit-avalonia-migration/seam-recommendations.md` are met. + +**Acceptance criteria:** +- Avalonia startup path covers project selection / cache / splash / safe-mode / remote listener / + no-UI / app-server / update checks; installer + dependency harvest updated for Avalonia assets; + feature-flag/default selector present. +- Full local build/test + **full-app smoke gates pass before** the default switch; command-parity + evidence (not just launch-and-click) is the gate. +- Avalonia is default; WinForms shell default path, FlexUIAdapter default dependency, WinForms + dynamic content host, retired dialogs, and obsolete shell-XML runtime pieces removed from the + default path (WinForms may remain only as explicit, manifested legacy islands per 11e until their + areas migrate). + +**Dependencies:** **Last — depends on 11a–11e** and "enough of 5–8". Blocks **Stage 12** (the +WinForms host must be gone before the .NET 10 + Avalonia 12 jump). + +**Cross-reference (`fieldworks-avalonia-shell-migration` tasks):** **10** (Startup, Shutdown, +Installer, and Default Switch — 10.3 packaging, 10.4 feature flag, 10.5 smoke gates, 10.6 default +switch, 10.7 WinForms/FlexUIAdapter removal, 10.8 pivot-trigger revisit). Spec: *Full-app smoke gates +protect the default switch*. + +**Labels:** `track-shell`, `lead-senior`, `av12-delta-localized` + +**Rough size:** **L** (default switch + ~3.2k-line decommission; gated, last). + +--- + +## Notes / open questions + +**Relationship to `fieldworks-avalonia-shell-migration`.** This epic is the JIRA projection of that +OpenSpec change; the change's `proposal.md` / `design.md` / `tasks.md` / `specs/` remain the +authoritative body. The 11a–11f decomposition maps onto the change's 10 task groups (11a→2/5/10.1–2, +11b→3, 11c→4, 11d→5/6/7, 11e→6/9, 11f→10). Per program §10, existing OpenSpec changes map onto +epics — `fieldworks-avalonia-shell-migration → Stage 11`. Do not let this draft drift from the change; +if the change updates, re-sync this file. + +**Stage 2 / Stage 11 split (restated for backlog hygiene).** Stage 2 = ports/contracts (lifetime, +main-window, active-window registry, dialog owner, dispatcher, shutdown, modal state + +`IXCoreCommandBridge`) and seam stand-up. Stage 11 = implement + shell-scope + default switch. Stage +11 must not be read as re-creating the lifetime/command contracts. Reuse `IUiScheduler` / +`IRegionLifetime` from `ISeams.cs`. + +**Av12-delta-localized constraint (program §1a / §5; synthesis §2; Stage 12 review).** "Av12-ready +during coexistence" is **not literally achievable** — code running on Avalonia 11.x / net48 cannot +avoid all 11-only APIs. The achievable posture, and the one this epic adopts, is **confining +Avalonia-11-only APIs (clipboard `IDataObject`→`IAsyncDataTransfer`, binding, focus, theming) to +named seams**, enforced by a Stage 2/11 exit gate. `XWindow : Form` + `Application.Run()` pin the +process to net48/Av11 through all of Stage 11; Stage 12 (the .NET 10 + Avalonia 12 jump) cannot start +until 11f removes the WinForms host. Every sub-epic carries the `av12-delta-localized` label. + +**Dialog ordering (5 → 11) — verified, not inverted.** Stage 5 dialogs ship as WinForms-owned +Avalonia *content* first; Avalonia-native modality is a Stage 11 *output* (the 11a re-host task), used +after the shell exists. The dependency arrow is **5 → 11** (a finishing edge), not 11 → 5. + +**Open questions (each gates a sub-epic — assign resolution owners):** +- **OQ1 runtime target** → 11a / Stage 12 (Av12-delta-localized seams; net48 pin until 11f). +- **OQ-B docking library vs owned controls** → 11d (spike alongside Stage 3; record build-vs-library + decision + pivot trigger; risk of under-scoping 11d). +- **OQ-C partner / third-party `Main.xml` extension hooks** → 11b (diagnostics-not-silent-omission is + the safety net, but a partner extension with no Avalonia equivalent is a hard blocker for *their* + default switch — needs a policy). +- **OQ-D multi-window / active-window registry parity** → 11a (preserve `s_activeMainWnd` / + `Form.ActiveForm as IFwMainWnd` multi-project semantics; under-tested in current tasks — only 6.4 + nav tests; review confidence Medium-low). +- **OQ5 first-switch blocking screens** → 11e gate (enumerate Avalonia-required areas vs legacy + islands; Interlinear/Stage 7 likely the first island). + +**Risks (program §9 + review §6):** shell pulled too early (mitigation: hard, enumerated "enough of +5–8" gate in 11e); "hallucinated parity" at shell scale (mitigation: command-target/shortcut parity +tests as the gate, not launch-and-click); FlexUIAdapter half-life during coexistence (mitigation: +active-host contract catches transitive `IUIAdapter` pulls). diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-12-runtime-modernization.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-12-runtime-modernization.md new file mode 100644 index 0000000000..3fa6b9fbfd --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-12-runtime-modernization.md @@ -0,0 +1,234 @@ +# Stage 12 — Runtime & toolchain modernization (.NET 10 + Avalonia 12) (Epic draft) + +> **Status:** JIRA-ready draft. Source-grounded in +> `complete-migration-program.md` (§2 principle 14, §4 row 12, §5 sequencing note, §6 Stage 12 +> detail + post-review callout, §9 risk register, §10 labels), `reviews/stage-12-runtime-modernization.md`, +> and `reviews/00-cross-comparison-synthesis.md` (§6 sub-epic map, §7 edges). Planning only — no +> code/behavior change. Create JIRA from this draft; do not split net10/Av12 into separate epics +> (they are coupled by the one-CLR rule), but model the four internal work-streams as gated stories. + +--- + +## Epic — Stage 12: Runtime & toolchain modernization (.NET 10 + Avalonia 12) + +**Summary.** Port the surviving managed FieldWorks codebase from .NET Framework 4.8 to .NET 10 and +bump Avalonia 11.3.17 → 12 as one coordinated, whole-process cutover, then land the coupled NUnit +3 → 4 upgrade that Avalonia 12 headless testing requires. This is the chartered "move to modern +tools" jump, sequenced deliberately late so it ports surviving managed code rather than WinForms +that Stage 13 is about to delete. + +**Type.** Epic (Track IV — Shell, runtime modernization & cutover). Senior-led. + +**Labels.** `track-shell`, `lead-senior`, `parity-blocked-by:stage-11-shell`, *(not `parallel-safe` +— this is a coordinated whole-process bump, single active stream)*. + +**Description.** +FieldWorks is uniformly **.NET Framework 4.8** today: a full sweep of `Src/` found **130 projects, +all `net48`, zero net8/9/10, and no multi-targeting anywhere** +(`Directory.Build.props` defaults C# `LangVersion` to 8.0, not 7.3). So this is a **single-target +port of the whole managed tree**, not a multi-targeting consolidation — there is no net8 lane to +retarget from, and no dual-TFM bookkeeping to unwind. + +The **timing is forced, not merely wise.** `Directory.Packages.props` pins **Avalonia 11.3.17** with +an in-repo comment: 11.3.x still ships `netstandard2.0` assemblies and can load on net48; **Avalonia +12 dropped `netstandard2.0` (net8+ only) and cannot host in-process on net48.** Because the one-CLR +rule means a single in-process `WinFormsAvaloniaControlHost` spans the boundary, the Av11→12 bump is +*physically impossible* until the process leaves net48 — which cannot happen until the WinForms host +(Stage 11) is gone. Hence the late, coordinated, one-CLR shape. + +The epic decomposes into four sequenced internal work-streams (synthesis §6): +**12-net10-port** (net48 → .NET 10 across all ~130 projects, leaf-first) → **intermediate green +checkpoint** (net10 + Avalonia 11.3.17 — a legal, testable state because 11.3.x is netstandard2.0; +decouples the two biggest risk sources) → **12-av12-bump** (resolve Av12 breaking changes: +`UseHarfBuzz()`, clipboard `IDataObject`→`IAsyncDataTransfer`, binding/focus/gesture API migration) +→ **12-nunit4** (NUnit 3.14.0 → 4 across ~40 test projects, required by Av12 headless, coupled to the +SIL.TestUtilities NUnit-4 upgrade). Land it behind the green `./build.ps1` / `./test.ps1` / CI gate; +apply `fieldworks-managed-netfx-review` (refresh the skill's stale net48-vs-net8 premise in the same PR). + +**Acceptance criteria.** +- Whole process runs on **.NET 10 + Avalonia 12**; `./build.ps1` and `./test.ps1` green on Windows/x64. +- The **intermediate net10 + Avalonia 11.3.17 checkpoint** was reached, built, and tested green before + the Av12 bump (verified, not skipped). +- All ~130 product + test projects target net10; **no project targets a different TFM or Avalonia major** + — enforced as a **CI invariant** (one TFM, one Avalonia major), not just a convention. +- NUnit 4 across all ~40 test projects; `ClassicAssert` aliasing per the in-repo TODO + (`Src/Directory.Build.props`); SIL.TestUtilities on NUnit 4. +- Av12 breaking-change checklist closed: `UseHarfBuzz()` wired after `UseSkia()`; clipboard/drag-drop + seams migrated to `IAsyncDataTransfer`/`DoDragDropAsync()`; programmatic-binding audit done + (`FwFieldControls`, `FwMultiWsTextField`); focus/gesture APIs migrated (`FocusManager`, + `FocusChangedEventArgs`); DevTools package swapped; `Screen`-abstract usage resolved. +- API-compat hazards discharged: BinaryFormatter, `System.Drawing.Common` (Windows-only package), + `System.Configuration`/app.config model, `AppDomain`/remoting, CodeDom — searched and ported. +- `fieldworks-managed-netfx-review` skill refreshed to the net48→net10 single-target, C#8→latest, + NUnit 3→4 reality in the same PR. + +**Dependencies.** +- **Hard entry gate: "Stage 11 shell retired."** Stage 12 must not start over a still-WinForms shell — + if Stage 11 slips, the residual-WinForms-on-net10 port surface balloons (and the surviving C++/COM + interop with it). Also depends on **most of Stages 5–10** (so the port covers surviving code, not + soon-to-be-deleted WinForms). +- **Enables Stage 13** (final cutover + native decommission + **cross-platform enablement**). net48 is + Windows-only; .NET 10 is the capability that unblocks Linux/macOS. Stage 12 delivers the *capability*; + Stage 13 spends the *validation cost* (cross-platform held to Stage 13 by decision §11.2). + +**Rough size.** Large epic, senior-led, single coordinated stream (not parallelizable). Effort +**medium-to-high** but materially de-risked by prior stages: Gecko/XULRunner and Graphite (worst +net48-coupling offenders) are already removed (Stage 10), and the native UI/render COM surface is +largely gone (Stage 9 replaced Views; Stage 13 finishes the rest), so surviving interop is mostly +*non-UI* linguistics services (Kernel/Generic/ICU/XAmple) behind service seams. The residual-WinForms +and surviving-COM census (only Stage 11 close can size it) is the chief effort uncertainty. + +--- + +## Sub-epics / stories + +### 12-net10-port — Port the surviving managed tree net48 → .NET 10 + +**Summary.** Retarget all ~130 net48 projects (product + test) to net10, leaf-first, and get the +whole tree compiling and testing green. + +**Type.** Story (gating). Senior. + +**Description.** Single-target port: the repo is uniformly net48 (verified), so there is no net8 lane +to consolidate. Port leaf-first (Utils/Kernel-wrapper/test-utilities → up the graph to xWorks/LexText/ +shell); CPM + central `Directory.Build.props` make the global TFM switch a single chokepoint, but +per-project compat fixes must go leaf-first to keep the tree buildable. Run **`.NET Upgrade Assistant` +for analysis only** (blocker report) — hand-port the fixes across 130 projects under +`TreatWarningsAsErrors`. Pin API-compat hazards as **pre-port discovery tasks**: BinaryFormatter removal, +`System.Drawing.Common` Windows-only package + runtime config, `System.Configuration`/app.config model, +`AppDomain`/remoting, CodeDom, binding-redirect removal. Residual WinForms moves to WinForms-on-.NET 10 +(a supported workload, Windows-only). Native-first build order (`FieldWorks.proj` `BuildNativeFirst`) +already enforces the COM/codegen prerequisite; spike the Kernel/Generic/ICU reg-free COM interop early. + +**Acceptance criteria.** All ~130 projects target net10 and build green leaf-first; `.NET Upgrade +Assistant` blocker report triaged and discharged; API-compat hazard list closed; reg-free COM / +P/Invoke marshalling verified on net10 (CharSet defaults, etc.); `./build.ps1` green. + +**Dependencies.** Entry gate: **Stage 11 shell retired** + most of Stages 5–10. Gates the +intermediate checkpoint and everything downstream in this epic. + +**Labels.** `track-shell`, `lead-senior`. + +**Rough size.** Large (the mechanical + API-compat bulk of the epic). + +--- + +### 12-green-checkpoint — Intermediate green state: net10 + Avalonia 11.3.17 + +**Summary.** Reach and verify a legal, testable **net10 + Avalonia 11.3.17** state — build/test green — +*before* touching Avalonia 12, to decouple the two biggest risk sources. + +**Type.** Story (gate / milestone). Senior. + +**Description.** 11.3.x is netstandard2.0, so it loads on net10 — making net10 + Av11 a valid +intermediate checkpoint. This is the single most useful tactic available: land net10 with Avalonia +unchanged and get fully green, *then* bump Avalonia, instead of debugging the runtime port and the UI- +framework bump simultaneously. **OQ-1:** verify in a spike that 11.3.17 actually loads/restores on net10 +before committing to this as a hard gate. + +**Acceptance criteria.** Whole solution builds and tests green on net10 + Avalonia 11.3.17 via +`./build.ps1` / `./test.ps1`; spike confirms 11.3.17 loads on net10; this state is a recorded, gated +milestone (not skipped straight to Av12). + +**Dependencies.** Gated by **12-net10-port**. Gates **12-av12-bump**. + +**Labels.** `track-shell`, `lead-senior`. + +**Rough size.** Small-to-medium (a verification/stabilization gate, not new feature work). + +--- + +### 12-av12-bump — Bump Avalonia 11.3.17 → 12 (breaking changes) + +**Summary.** Bump Avalonia to 12 from the green net10 checkpoint and resolve the repo-specific +breaking changes, most confined to named seams by the upstream "Av12-delta-localized" discipline. + +**Type.** Story (gating). Senior. + +**Description.** Av12 minimum is net8 (net10 recommended), so this is only possible from the net10 +checkpoint. Repo-specific breaking changes to resolve (Av12 breaking-changes doc): +- **`UseSkia()` must now also call `UseHarfBuzz()`** — aligns with principle 13 (HarfBuzz-only managed + shaping; Graphite removed in Stage 10). A one-line wiring change, but text shaping silently breaks if + omitted — must be on the checklist. +- **Clipboard/drag-drop rewrite:** `IDataObject` → `IAsyncDataTransfer`/`DataTransfer`, + `DoDragDrop()` → `DoDragDropAsync()`. The clipboard/drag-drop **seam signatures in `ISeams.cs` change here.** +- **Binding internals:** `IBinding`/`InstancedBinding` removed → `BindingBase`/`BindingExpressionBase`; + binding-plugin system removed. Audit owned controls that construct bindings programmatically + (`FwFieldControls`, `FwMultiWsTextField`). Compiled bindings on by default — aligns with Decision §11.3. +- **Focus/gestures:** `KeyboardNavigationHandler` → `FocusManager`; `Gestures` no longer public; + `GotFocusEventArgs` → `FocusChangedEventArgs`. Touches the host focus memento / directional-key bypass. +- **Shell-relevant (Stage 11 output):** DevTools package swap, TopLevel interfaces, `Screen` now abstract, + `TitleBar`/`CaptionButtons` removed. Direct2D1 removed (Skia only) — no impact (repo already on SkiaSharp). + +The delta is real but **bounded and mostly seam-localized**, *provided* the Av12-delta-localized +discipline (the Stage 2/11 exit gate confining 11-only-removed APIs to named seams) actually held +through Stages 1–11. + +**Acceptance criteria.** Whole solution on Avalonia 12, build/test green; full Av12 breaking-change +checklist closed (see Epic ACs); `UseHarfBuzz()` verified by a text-shaping check. + +**Dependencies.** Gated by **12-green-checkpoint**. Theming coupling with the Stage 2 "Av12-ready +theming" gate (upstream control). Runs with / gates **12-nunit4** (Av12 headless needs NUnit 4). + +**Labels.** `track-shell`, `lead-senior`. + +**Rough size.** Medium (bounded if the delta-localized discipline held; larger if it leaked). + +--- + +### 12-nunit4 — NUnit 3.14.0 → 4 across the test projects + +**Summary.** Upgrade NUnit 3 → 4 across ~40 test projects — a breaking API change required by Avalonia +12 headless testing — as its own reviewed work-stream, not riding silently on the Av12 bump PR. + +**Type.** Story (gating, coupled). Senior. + +**Description.** Avalonia 12 headless test packages require **NUnit 4** (Avalonia docs). The repo pins +**NUnit 3.14.0** (`Directory.Packages.props`) across ~40 test projects, and `Src/Directory.Build.props` +carries the explicit TODO: "When SIL packages upgrade to NUnit 4, update this … and add global using +aliases for `ClassicAssert`." NUnit 3→4 is a breaking change (`Assert.That` model, `ClassicAssert`) +touching every test assembly. Add the global `ClassicAssert` using-aliases per the in-repo TODO. +**OQ-2:** this is **gated on SIL.TestUtilities shipping NUnit 4** — track now as an external dependency; +if it lags Stage 12, the epic is blocked on it (or must carry a temporary NUnit-version split). + +**Acceptance criteria.** All ~40 test projects on NUnit 4; `ClassicAssert` aliasing in place; +SIL.TestUtilities on NUnit 4; `./test.ps1` green on net10 + Av12; the in-repo TODO is removed. + +**Dependencies.** Coupled to **12-av12-bump** (Av12 headless requirement) and to the external +SIL.TestUtilities NUnit-4 upgrade. Final green gate of the epic. + +**Labels.** `track-shell`, `lead-senior`. + +**Rough size.** Medium (mechanical but broad — every test assembly). + +--- + +## Notes / open questions + +- **Av12 TFM constraint forces the timing (verified).** Avalonia 12 dropped `netstandard2.0`/net48 + (`Directory.Packages.props` comment + Avalonia discussion #18606, ~<4% telemetry rationale). The + Av11→12 bump is therefore *physically blocked* until the process leaves net48, which can't happen + until the WinForms host (Stage 11) is gone. The late, coordinated, one-CLR sequencing is **forced, not + just tidy** — this is the earliest point the bump is even possible. Confidence: **High**. +- **Do NOT split net10 / Av12 into separate epics.** They are coupled by the one-CLR fact: Av12 requires + net10 (so the bump can't precede the port), and there's no reason to bump Avalonia while still on net48 + (12 won't load). One epic, four sequenced internal stories, single CI gate. +- **"Av12-delta-localized," not "Av12-ready."** Code running on Avalonia 11 / net48 through Stages 1–11 + *cannot* avoid 11-only APIs (clipboard `IDataObject`, programmatic `IBinding`, + `KeyboardNavigationHandler`/`Gestures`, `GotFocusEventArgs`) — it must use them on 11. The achievable + posture is **confining 11-only-removed APIs to named seams** (clipboard, drag-drop, binding + construction, focus/gestures, theming), enforced by a **Stage 2/11 exit gate**. This converts the vague + "Av12-ready" promise into a testable invariant and is the chief mitigation for the "breaking changes + ripple late" risk. If the discipline held, 12-av12-bump is small; if it leaked, the churn surfaces here. +- **Stale `fieldworks-managed-netfx-review` premise.** The skill (and the original Stage 12 wording) is + built around a "net48 vs SDK-style net8 / C#7.3" boundary that **does not match the repo**: it is + uniformly net48 (130 projects, zero net8/9/10) and defaults C#8. The real boundary at Stage 12 is + **net48 → net10 (single target), C#8 → latest, NUnit 3 → 4.** Refresh the skill in the same PR (its own + "Keep This Skill Current" clause requires it). +- **OQ-3 (residual-WinForms size):** how much WinForms genuinely survives into Stage 12 vs. is deleted in + Stage 13 determines the WinForms-on-net10 port cost and the surviving C++/COM-interop surface. Needs a + census at Stage 11 close. +- **CI invariant:** assert one TFM (net10) and one Avalonia major (12) across the whole solution after the + bump — encode the one-CLR rule as a test, not a convention. +- **Verify with repo scripts only** (`./build.ps1`, `./test.ps1`) on *both* the intermediate net10+Av11 + checkpoint and the final net10+Av12 state — never bare `dotnet build`. diff --git a/openspec/changes/avalonia-migration-roadmap/epics/stage-13-cutover-cross-platform.md b/openspec/changes/avalonia-migration-roadmap/epics/stage-13-cutover-cross-platform.md new file mode 100644 index 0000000000..a450336737 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/epics/stage-13-cutover-cross-platform.md @@ -0,0 +1,320 @@ +# Stage 13 — Final cutover, native decommission & cross-platform enablement (Epic draft) + +> JIRA-ready epic + sub-epic draft for **Stage 13** of the FieldWorks → Avalonia complete +> migration program. Source of truth: `complete-migration-program.md` §6 (Stage 13 + post-review +> callout), §7 (Definition of Done), §10 (JIRA structure/labels), §11.1 (Graphite), +> §11.2 (cross-platform deferral); `reviews/stage-13-cutover-cross-platform.md`; +> `reviews/00-cross-comparison-synthesis.md` §3, §6, §7. +> +> Stage 13 is the **terminal** stage. It splits into three sequenced sub-epics whose defining +> property is **reversibility**: 13a is a reversible runtime-config flip with WinForms kept as the +> live rollback; 13b is the irreversible deletion, gated on 13a's field-bake metric; 13c is +> additive cross-platform validation gated on 13b being genuinely complete. The most dangerous +> action (deletion) must **never** share a stage with the reversible action (the flip) it rolls +> back to. + +--- + +## Epic — Stage 13: Final cutover, native decommission & cross-platform enablement + +**Summary.** Flip the global default UI to Avalonia, decommission the WinForms surface layer and +the native C++ Views render engine + render COM surface, and enable Linux/macOS build + headless + +smoke — closing the migration program. + +**Type.** Epic. + +**Labels.** `track-shell`, `lead-senior`, `parity-blocked-by:all-prior-stages`. +(Sub-epics carry finer labels; see each story. Per program §10: `track-foundation | track-surfaces +| track-longpole | track-shell`, `lead-junior|mid|senior`, `parallel-safe`, +`parity-blocked-by:`.) + +**Description.** +FieldWorks ships on Windows on .NET Framework 4.8 with two coexisting UI surfaces (WinForms + +Avalonia) selected per-tool by `LexicalEditSurfaceResolver` reading the `UIMode` setting (default +`Legacy`). Stage 13 ends the coexistence: it makes Avalonia the default, removes the strangled +WinForms + native UI/render code once the new default has proven itself in the field, and unlocks +the cross-platform reach the managed-only architecture was chosen to enable. + +The stage is decomposed into three sequenced sub-epics so that the **irreversible** deletion (13b) +is gated on a **field-bake metric** from the **reversible** flip (13a), and the **additive** +cross-platform work (13c) is gated on the deletion being genuinely done (any residual Windows-only +reference fails the Linux/macOS build). Two cross-cutting stories are also tracked: the +**ViewsInterfaces split** (a hard prerequisite inside 13b — `ViewsInterfaces.cs` co-defines render +interfaces and the data-access `IVwCacheDa` used by 45+ projects, so only the render half may be +deleted) and the **dropped-script user-notification verification** (closing the Graphite/Awami +Nastaliq obligation from program §11.1). + +Scope boundaries: +- **Graphite** is removed from the managed/Avalonia path in Stage 10B; **native + `GraphiteEngineClass` deletion lands here in 13b** (deleting earlier breaks legacy surfaces still + using it during coexistence). 13 *verifies and completes* the removal; it does not redo Stage 10's + managed-path work. +- `retire-linux-era-view-shims` is a **narrow prerequisite that lands before** the `Src/views` + decommission (it preserves `VwTextStore`/`IViewInputMgr`/`ManagedVwDrawRootBuffered`, which 13b + later deletes once their consumers are gone). +- Non-UI native/linguistics services (Kernel, Generic, ICU, XAmple, encoding converters, parsers) + and `ITsString`/`TsString` (in `Src/Kernel`, not Views) are **kept behind service seams**. + +**Acceptance criteria.** +1. `UIMode` default is `New`; a mechanical gate asserts every registered tool/surface returns + `Supported` with a green region manifest (no human checklist) before the default flip. +2. The 13a **bake metric** (duration + crash-free-session / parity-incident thresholds) is defined, + measured, and green before any deletion begins; WinForms remains a working live rollback through + 13a. +3. `EngineIsolationAuditTests` is promoted from a single-assembly scan to a **whole-shipping-assembly + audit** proving no surviving production assembly references `SimpleRootSite`/`RootSite`/ + `XMLViews`/`DetailControls`/Views render symbols — and it gates the start of 13b. +4. WinForms surface layer (shell, WinForms-only dialogs, DataTree/Slice, SimpleRootSite/RootSite, + XMLViews, WinForms↔Avalonia interop spine) and native UI/render (`Src/views`, + `ManagedVwDrawRootBuffered`, render COM `IVwRootBox`/`IVwGraphics`/`IVwEnv`) are deleted + **leaf-first**, each deletion an independently build-green, bisectable commit. +5. `ViewsInterfaces` is **split**: data-access (`IVwCacheDa` + non-render contracts the 45+ + consumers/`CacheLight` use) is preserved behind a seam; only render interfaces are deleted. +6. Native `GraphiteEngineClass` is removed; the dropped-script list (from the Stage 9.0 G0–G3 scan) + is documented and affected users notified with migration guidance **before** removal ships + (program §11.1 obligation verified). +7. Linux + macOS **build** (compile-only achieved at end of Stage 12), **headless**, and **smoke** + are green; Linux/macOS packaging exists (net-new; WiX6 does not port). +8. Final cross-cutting gates pass: accessibility (Narrator/NVDA spot-checks), localization parity, + performance — per program §7 Definition of Done. +9. `./build.ps1` + `./test.ps1` green on Windows throughout; CI matrix green on Windows + Linux + (+ macOS smoke) at close. + +**Dependencies.** Depends on **ALL** prior stages (terminal stage). Concretely, 13b cannot start +deletion until Stages 7 (Interlinear/Discourse) and 9 (document engine) have removed the last +`SimpleRootSite`/`IVwRootBox` consumers (the 18+ project references are the live burn-down). **13c +hard-depends on Stage 12** (.NET 10; net48 is Windows-only). `retire-linux-era-view-shims` lands +before the `Src/views` decommission. + +**Rough size.** XL. + +--- + +## Sub-epics / stories + +### 13a — Cutover & bake *(senior; reversible)* + +**Summary.** Flip the global default to Avalonia via the `UIMode` setting using a staged/ringed +rollout; keep WinForms in the tree as the live rollback for a defined bake period. + +**Type.** Sub-epic. + +**Description.** +The flip is a one-property change: `LexicalEditSurfaceResolver` reads `UIMode` +(`Src/Common/FwUtils/Properties/Settings.Designer.cs`, default `[DefaultSettingValue("Legacy")]`); +flipping the default to `New` *is* the cutover. Per-tool granularity already exists +(`SupportsAvaloniaForTool`), so the rollout flips tool-by-tool, not all-at-once: opt-in → +default-on-with-easy-revert → (later) remove legacy. WinForms code stays in the tree as a +runtime-revertible rollback throughout; **no code is deleted in 13a.** A mechanical gate — a test +asserting every registered tool returns `Supported` and has a green region manifest — must block the +flip (hallucinated parity is the program's top AI risk). The bake window defines the metric that +authorizes 13b. + +**Acceptance criteria.** +- `UIMode` default flipped to `New`; per-tool ringed rollout in place with instant revert. +- Mechanical "all manifests pass" gate (every tool `Supported` + green manifest) blocks the flip; no + human checklist substitutes for it. +- Bake duration + closing metric (crash-free-session rate / parity-incident count) defined up front + and measured; bake-green is a published, objective signal that authorizes 13b. +- WinForms remains a working live rollback for the full bake period; default-revert verified. + +**Dependencies.** All prior surface/shell stages (every tool must be `Supported`). Reversible — does +not depend on 13b/13c. + +**Labels.** `track-shell`, `lead-senior`, `parity-blocked-by:all-manifests`. + +**Rough size.** L. + +--- + +### 13b — Decommission (WinForms + native UI/render) *(senior; irreversible)* + +**Summary.** Delete the WinForms surface layer and native C++ Views render engine + render COM +surface in dependency order, **after** 13a's bake metric is green. + +**Type.** Sub-epic. + +**Description.** +This is the **irreversible** step and gets its own epic with its own ordered deletion runbook. Entry +gate: 13a bake-green **and** the promoted whole-shipping-assembly `EngineIsolationAuditTests` proving +no surviving production assembly references the legacy UI/render symbols. Deletion proceeds +**leaf-first**, each step an independently build-green, bisectable commit: + +`consumers' WinForms surfaces → DetailControls/XMLViews → RootSite → SimpleRootSite → +ManagedVwDrawRootBuffered → ViewsInterfaces SPLIT (see story below) → Src/views (native) → +render COM interfaces`. + +Repo-grounded notes: `Src/views` (44 C++ files) is the last native component built and is only a +*consumer* of Kernel/Generic — deletable once its hosts are gone. `ManagedVwDrawRootBuffered` is +referenced only by `SimpleRootSite.csproj`. `SimpleRootSite`/`RootSite` are referenced by 18+ +projects (xWorks, LexEdDll, ITextDll, Discourse, MorphologyEditorDll, FdoUi, Framework, FieldWorks, +Widgets…) — all must be migrated first (Stages 7/9). **Native `GraphiteEngineClass` deletes here** +(legacy surfaces use it during coexistence; Stage 10B only removed it from the managed path). Keep +Kernel/Generic/ICU/XAmple/converters/parsers and `ITsString` (Kernel). + +**Acceptance criteria.** +- Entry gate met: 13a bake-green + whole-assembly engine-isolation audit shows zero legacy UI/render + references in surviving production assemblies. +- WinForms surface layer + native UI/render deleted in the leaf-first order; each deletion an + independently build-green, bisectable commit. +- `ViewsInterfaces` split completed (render half deleted, data-access half preserved — see story). +- Native `GraphiteEngineClass` removed; Stage 10's managed-path Graphite removal verified still green. +- Non-UI native/linguistics services and `ITsString` preserved behind seams; `./build.ps1` + + `./test.ps1` green on Windows after the final deletion. +- `retire-linux-era-view-shims` confirmed landed before the `Src/views` deletion. + +**Dependencies.** 13a (bake-green gate). Stages 7 & 9 (last `SimpleRootSite`/`IVwRootBox` consumers +removed). Stage 5 (WinForms-only dialogs replaced by MVVM content before they can be deleted). +`retire-linux-era-view-shims` (lands first). + +**Labels.** `track-shell`, `lead-senior`, `parity-blocked-by:simplerootsite-consumers`. + +**Rough size.** XL. + +--- + +### ViewsInterfaces split *(senior; sub-task of 13b — hard prerequisite for render-COM deletion)* + +**Summary.** Split `Src/Common/ViewsInterfaces/Views.cs` so the data-access contracts survive and +only the render interfaces are deleted. + +**Type.** Story. + +**Description.** +The program's original "retire the `IVwRootBox`/`IVwGraphics`/`IVwEnv` COM surface" line is **too +broad as written and would break non-UI code.** `Views.cs` co-defines the render interfaces +(`IVwRootBox` ~6627, `IVwEnv` ~10200, `IVwGraphics`) **and** `IVwCacheDa` (~4122), which is a +**data-access** cache contract implemented by `Src/CacheLight/RealDataCache.cs` and referenced by +**45+ projects**. (`ISilDataAccess` lives in `Src/Kernel/FwKernel.idh`; `ITsString`/`TsString` live +in `Src/Kernel/TextServ.idh` — both correctly preserved by "keep Kernel/Generic.") Deleting +`ViewsInterfaces` wholesale, or the render COM surface without surgically separating it from +`IVwCacheDa`, breaks the data cache and ~45 projects. This story relocates/keeps the data-access +contracts behind a service seam (a Kernel-adjacent assembly) before the render half is removed. It +is the named prerequisite for AC #5 / the render-COM deletion in 13b. + +**Acceptance criteria.** +- Focused audit confirms whether render interfaces reference data-access types (decides clean-cut vs. + refactor); result recorded. +- `IVwCacheDa` (and any non-render contracts `CacheLight`/the 45+ consumers use) relocated/kept + behind a seam; all consumers compile against the surviving surface. +- Only render interfaces (`IVwRootBox`/`IVwEnv`/`IVwGraphics` family) remain to be deleted by 13b's + render-COM step; `CacheLight` + the 45 projects build green. + +**Dependencies.** Sequenced inside 13b, immediately before the `Src/views`/render-COM deletion. + +**Labels.** `track-shell`, `lead-senior`, `parity-blocked-by:viewsinterfaces-split`. + +**Rough size.** L. + +--- + +### 13c — Cross-platform enablement & final gates *(senior; additive)* + +**Summary.** Stand up Linux/macOS build + headless + smoke and Linux/macOS packaging, then pass the +final accessibility/localization/performance gates. + +**Type.** Sub-epic. + +**Description.** +Held to the final stage by decision (program §11.2): no Linux/macOS *validation* cost is incurred +earlier in the program. Unblocked by the managed-only path + .NET 10 (Stage 12; net48 is +Windows-only). 13c is **additive net-new validation** that shares no code with the deletion but +requires it to be done — any residual Windows-only reference from a surviving surface fails the +Linux/macOS build. + +De-risking the late deferral **within** the decision (not relitigating it): +- The OS-portable `Avalonia.Headless` lane runs on **Linux CI from Stage 1** (catches + logic/binding/layout regressions OS-early; OS smoke/UIA stays deferred). +- Windows-only APIs (path separators, registry, P/Invoke, `\`) are linted as code is written so + surviving managed code is cross-platform-clean before the OS build is attempted. +- A **compile-only Linux/macOS build** is stood up at the **end of Stage 12** so build-break + surprises (SDK, runtime IDs, native-dep resolution) are found before the terminal stage. +- An explicit integration-and-verification sub-phase is budgeted for post-green OS smoke debugging + (clean cross-platform builds ≠ working software). + +**Packaging is net-new and unscoped today:** `FLExInstaller/wix6/` is WiX6/Windows-only and does not +port; Linux (`.deb`/`.rpm`) and macOS bundle packaging is new work. Gecko removal is an upstream +build-output/harvest-exclude change (Gecko is harvested, not named in the `.wxs`), tied to Stage 10A. + +**Acceptance criteria.** +- Linux + macOS build green (built on the compile-only build standing from end of Stage 12). +- Linux + macOS headless lane green; Linux + macOS smoke green (with budgeted integration-debug + sub-phase). +- Linux/macOS packaging produced (net-new; not a WiX edit); Windows installer unaffected. +- HarfBuzz/managed shaping coverage (Stages 9/10) verified on Linux/macOS text-rendering smoke — no + Graphite fallback exists. +- Final gates pass: accessibility (Narrator/NVDA spot-checks), localization parity, performance — per + program §7. + +**Dependencies.** **Stage 12 (hard — .NET 10).** 13b complete (no Windows-only/native references +left). Stages 9/10 (text-shaping coverage proven). + +**Labels.** `track-shell`, `lead-senior`, `parity-blocked-by:net10-runtime`. + +**Rough size.** L. + +--- + +### Dropped-script user notification — verification *(senior/PM; verification story)* + +**Summary.** Verify the program §11.1 obligation is met before native Graphite removal ships: the +exact dropped-script list is documented and affected users are notified with migration guidance. + +**Type.** Story. + +**Description.** +Decision §11.1 accepts the loss of Graphite-only scripts (notably **Awami Nastaliq**, Urdu/Arabic +Nastaliq — Graphite-only by design, no OpenType/HarfBuzz path, SIL has no OpenType replacement +planned) but incurs a **user-comms obligation**: the program must (a) run the Stage 9.0 LDML G0–G3 +coverage scan to enumerate the **exact** dropped-script list + affected projects, (b) **document** +the dropped scripts, and (c) **notify affected users with migration guidance** — all **before** the +removal ships. This is a Stage 10B / Stage 13 deliverable, not optional. Since native +`GraphiteEngineClass` deletion lands in 13b, this verification story gates that deletion: the comms +must be out before the irreversible removal. + +**Acceptance criteria.** +- Stage 9.0 G0–G3 scan output (exact dropped-script list + affected projects) is on file and current. +- Dropped-script loss is documented in user-facing release/migration notes. +- Affected users notified with migration guidance, on record, **before** native Graphite removal + ships in 13b. +- Sign-off recorded that the §11.1 comms obligation is closed; this gates the 13b + `GraphiteEngineClass` deletion step. + +**Dependencies.** Stage 9.0 (G0–G3 scan). Gates the native Graphite deletion in 13b. + +**Labels.** `track-shell`, `lead-senior`, `parity-blocked-by:graphite-comms`. + +**Rough size.** S. + +--- + +## Notes / open questions + +**Reversible-vs-irreversible separation (the structural reason for the split).** 13a (flip) is a +runtime-config change reversible in minutes with no code deleted; 13b (delete) is destructive and +irreversible by design. Deleting code while a freshly-flipped default is still proving itself removes +the very rollback path the cutover relies on — therefore the flip and the deletion are **separate +epics**, and 13b is gated on a **field-bake metric** from 13a, not on a green CI run. 13c is additive +and gated on 13b being genuinely complete. + +**Open questions / risks (from the Stage 13 review):** +- **ViewsInterfaces split feasibility (Medium confidence):** can `IVwCacheDa` separate cleanly from + the render interfaces, or do render interfaces reference data-access types (coupling the split)? + This decides whether the render-COM deletion is a clean cut or a refactor — needs a focused audit + **before** 13b commits its deletion sequence. +- **Bake metric undefined until 13a:** duration + crash-free-rate / parity-incident thresholds must + be set before 13b can claim "safe to delete." Without it, the trigger is subjective. +- **Tail-risk concentration:** Stage 13 inherits slippage from all of Stages 5–12. If any surface + still uses `SimpleRootSite`/`IVwRootBox`, 13b stalls. The 18+ project reference list is a + program-wide burn-down to track continuously, not at Stage 13. +- **Big-flip blast radius:** even with per-tool granularity, the *default* flip touches every user; + the ringed rollout and instant `UIMode` revert must be real, not nominal. +- **HarfBuzz/Graphite coverage must be proven (Stages 9/10), not assumed** — else cross-platform + text-rendering smoke fails late with no Graphite fallback. + +**Cross-platform late-deferral de-risking (within the §11.2 decision, not relitigating it):** +Linux-CI headless from Stage 1 + Windows-only-API linting as code is written + a compile-only +Linux/macOS build standing at the end of Stage 12 + a budgeted integration-debug sub-phase in 13c. +This honors "no *validation* cost earlier" while moving build-break discovery off the terminal stage. +Linux/macOS **packaging remains net-new and unscoped** (WiX6 is Windows-only) — flagged as a possible +multi-week surprise to size early. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/00-cross-comparison-synthesis.md b/openspec/changes/avalonia-migration-roadmap/reviews/00-cross-comparison-synthesis.md new file mode 100644 index 0000000000..9921cbe3de --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/00-cross-comparison-synthesis.md @@ -0,0 +1,126 @@ +# Cross-comparison synthesis — 13-stage review + +> Synthesis of the 13 per-stage reviews under `reviews/stage-NN-*.md` (all written 2026-06-15 +> by independent subagents, each grounded in repo inspection + targeted web research). This doc +> cross-compares their findings, resolves conflicts, and drives the consolidated update to +> `complete-migration-program.md`. Read the per-stage files for the full evidence + citations. + +## 1. Headline outcomes + +1. **The 13 stages and their sequencing are sound; the sizing is not.** Eight stages + (3, 6, 7, 8, 9, 10, 11, 13) are too coarse to be single JIRA epics and need decomposition into + gated sub-epics. The *order* the plan chose — including the contested ones (dialogs before shell; + runtime jump late) — is repeatedly confirmed as correct, often *more* correct than the plan argued. +2. **Much of the "to build" scope is already built.** Reviewers found the JSON view-def serializer, + the read-only browse view (10k-row-proven), custom-field rendering, the host bridge, the command-bridge + seam, the AutomationId convention, and the chooser virtualization already exist. Several stages are + *finish/re-home/generalize*, not *build*. The plan's verbs overstate remaining work in 1, 2, 3, 4. +3. **One decision the user already made now collides with a hard reality** (Graphite → Awami Nastaliq; + see §4) and must be re-opened. Everything else is plan-mechanics. + +## 2. Recurring cross-cutting themes (appeared in ≥3 reviews) + +- **Decompose into consumer-gated sub-milestones.** The most common recommendation. Stages should ship + in slices that unblock specific downstream stages, not as monoliths (esp. 3→4/7/8, 9→6/7, 11→all). +- **Stage 9 is the gravity well.** Reviews of 6, 7, 9, 10 all pull work *into* Stage 9 (morphology + document editors, interlinear/sandbox constructs, Graphite-coverage proof) and warn its scope text is + too narrow for what depends on it. Stage 9 is the single biggest correctness risk in the plan. +- **The as-built stack is C# code-behind on net48, with zero `.axaml` and no CommunityToolkit.Mvvm.** + Decision §11.3 (dialogs use MVVM + compiled bindings) introduces a *new toolchain* that is unscoped in + Stage 1 and lands on juniors in Stage 5. Flagged by Stage 1, 5, and 6 reviews. +- **Stage 1 must deliver two kits, not one** — region/IR scaffolding *and* MVVM-dialog scaffolding — and + its validation gate (migrate a trivial dialog) currently exercises only the missing one. +- **Modernized-Fluent look (§11.4) conflicts with `architecture-patterns.md` §12** ("mimic legacy + density"). The skill reference needs a one-line correction (follow-up, not blocking). +- **"Av12-ready during coexistence" is not literally achievable** — code running on Avalonia 11/net48 + can't avoid 11-only APIs; the achievable posture is *confining 11-only APIs (clipboard, binding, focus, + theming) to named seams*, enforced by a Stage 2/11 exit gate. (Stages 2, 11, 12.) + +## 3. Conflicts & double-bookings found → resolutions + +| Conflict | Reviews | Resolution | +| --- | --- | --- | +| "Generalize `LexicalEditSurfaceSelectionService` → app-wide registry" claimed by **both** Stage 1 and Stage 2 | 1, 2 | Owns **Stage 2**. Stage 1 consumes it. | +| Dictionary **preview** double-booked: Stage 8 ("config preview wiring") vs Stage 10 ("preview replacement") | 8, 10 | Preview *rendering replacement* = **Stage 10**; Stage 8 (8b) consumes the replaced preview. Remove "preview wiring" from Stage 8 scope. | +| **Find/Replace + Styles dialogs** assigned to junior Stage 5, but both host `IVwRootSite`/`SimpleRootSite` | 5, 9 | Re-tier to **Stage 9-dependent** (Tier C), not junior Stage 5. | +| Stage 13 "retire IVwRootBox/IVwGraphics/IVwEnv COM surface" too broad — `ViewsInterfaces.cs` also defines `IVwCacheDa` (data-access) used by 45+ projects | 13 | **Split ViewsInterfaces**: keep data-access (`IVwCacheDa`) behind a seam; delete only render interfaces. | +| Graphite branch deletion in **Stage 10** (`RenderEngineFactory`) breaks **legacy** WinForms+Views surfaces too | 9, 10, 13 | **Native Graphite deletion → Stage 13** (with native Views). Stage 10 only removes Graphite from the *Avalonia/managed* path + classifies coverage. | +| Stage 6 "Grammar/Morphology" mixes detail-fields, **Views document editors**, and **browse bulk-editors** | 6 | Split 6a (lexicon detail), 6b (morph/grammar doc editors → gated on **Stage 9**), 6c (FdoUi bulk → **Stage 8/browse**, gated on **Stage 3**). | +| Stage 9 scope omits the constructs **Stage 7** needs (in-memory presentation cache/`CachePair`, hit-test combo editing, aligned interlinear grid) | 7, 9 | **Expand Stage 9** scope + add a named Sandbox/interlinear sub-spike to 9.0. | + +## 4. NEW product decision required — Graphite / Awami Nastaliq (raise to user) + +Decision §11.1 ("Graphite fully removed; HarfBuzz/managed only") was made before the engine review. +The Stage 9 review establishes (with sources): HarfBuzz covers the large majority of formerly-Graphite +scripts (SIL even dropped Graphite from Charis/Doulos v7 in 2025), **but Awami Nastaliq (Urdu/Arabic +Nastaliq) is Graphite-only by design** — OpenType lacks the collision-avoidance Nastaliq needs and SIL +has *no* OpenType replacement planned. HarfBuzz does not implement Graphite (`hb-graphite2` only delegates +to external `libgraphite2`). These are exactly FieldWorks' minority-language users. + +**Implication:** "Graphite fully removed" may strand a real user population. Options: +(a) accept the loss (document, notify, provide migration guidance); +(b) retain a narrow Graphite shaping escape-hatch behind a seam for G3 scripts only, contradicting +"fully managed only"; +(c) gate the *final* removal on an external OpenType-Nastaliq solution existing. + +This is a **product/values call, not an engineering one.** + +**RESOLVED 2026-06-15 — accept the loss, document + notify.** Graphite is removed entirely; no escape-hatch, +no gating on a Nastaliq solution. The Stage 9.0 LDML **G0–G3 coverage scan** (salvaged from +`graphite-transition-support`) is still required — not as a go/no-go gate, but to **enumerate the exact +dropped-script list + affected projects** so the program can **document the loss and notify affected users +with migration guidance** before removal ships (a Stage 10B / Stage 13 deliverable). Native +`GraphiteEngineClass` deletion stays in Stage 13 (legacy surfaces use it during coexistence). + +## 5. Per-stage verdicts (one line each) + +| # | Verdict | Key action | +| --- | --- | --- | +| 1 | Feasible; under-scoped for the dialog/MVVM kit | Two kits; redefine gate to prove both; "version" not "freeze" the seam catalog; mark region template provisional until 4 closes | +| 2 | Feasible; mostly generalization of existing assets | Own the app-wide surface registry + surface-census; name the ownership ports as single source of truth for 11; dual-run = CI matrix | +| 3 | **Mis-sized** — tree mostly solved, editable **table** is the real work (read-only today) | Re-scope to editable table + row chrome; ship 3a (read@scale) → 3b (editable, unblocks 4) → 3c (bulk/filter, unblocks 8); promote custom AutomationPeer to first-class | +| 4 | Scope **stale** — much already built | Reword 1&4 "re-home/finish"; anchor to manifest §6 Partial rows; add exemplar-quality exit (unify dual projector, document copy-me contract) | +| 5 | Modal tension **resolved** (host-wrapped body in WinForms-owned Form); not blocked | Make host-wrapped-body the first rule; re-tier A/B/C (Find/Replace+Styles→C/Stage 9); add MVVM tooling to Stage 1 gate | +| 6 | **Mis-bundled** across 3 substrates | Split 6a/6b/6c; add edges S9→6, S3→6, S5→6; promote `FwDialogLauncherField` to a `RegionFieldKind` | +| 7 | **Mis-sized** — 5 surfaces, 1–2 orders of magnitude apart in Views coupling | Split 7A (interlinear+sandbox, hard-blocks on extended 9), 7B (chart: port logic as-is + Stage 3 grid), 7C (concordance/stats), 7D (import → Stage 5/MVVM) | +| 8 | **Grab-bag** | Split 8a (notebook/lists/bulk-edit, needs Stage 3) + 8b (dict-config dialogs, MVVM); move preview to Stage 10; bulk-edit is the real engineering item | +| 9 | **Long pole, not one stage**; scope too narrow | Decompose 9.0 spike→9.1 StText→9.2 selection/caret→9.3 layout/box→9.4 embedded; reframe as DELTA over field foundation; add interlinear/sandbox + G0–G3 scan to 9.0 | +| 10 | **Two stages in one**; legacy-breakage risk | Split 10A (Gecko/PDF/preview) + 10B (Graphite classify/managed-path removal); native Graphite deletion → Stage 13; keep `DefaultFontFeatures`; decouple XULRunner startup first | +| 11 | **Second program in one row** | Decompose 11a–11f; state Stage 2/11 split (2=ports/contracts, 11=implement+shell-scope+switch); add dialog-modality re-host task; 5→11 is a finishing edge (ordering correct) | +| 12 | Late sequencing **forced** (Av12 dropped net48) — correct | Don't split net10/Av12 but add intermediate "green net10 + Avalonia 11.3.17" checkpoint; fix stale "net48/net8 multi-target" wording (repo is uniformly net48); add NUnit 3→4; "Av12-delta-localized" | +| 13 | **Five workstreams in one** | Split 13a (flip+bake, reversible) / 13b (delete, irreversible, gated on 13a) / 13c (cross-platform+gates); split ViewsInterfaces; leaf-first deletion runbook; `retire-linux-era-view-shims` lands first | + +## 6. Revised stage/sub-epic map (for JIRA) + +``` +1 Platform & enablement kit → 1-region-kit, 1-dialog-mvvm-kit, 1-shared-evidence-base, 1-runbook +2 Coexistence spine & contracts → 2-generalize-host, 2-surface-registry+census, 2-command-bridge, 2-theming, 2-ownership-ports +3 Editable table + row chrome → 3a-read@scale, 3b-editable-cells, 3c-bulk/checkbox/filter, 3-automationpeer +4 Finish Lexical entry (exemplar) → 4-rehome-table(3b), 4-latency+150dpi, 4-override-migrator/xml-disable, 4-exemplar-contract +5 Dialogs & choosers → 5A-junior(small,Views-free), 5B-mid(wizards/WS/props), 5C→Stage9(Find/Replace,Styles) +6 Lexicon + grammar/morphology → 6a-lexicon-detail, 6b-morph-doc-editors→S9, 6c-fdoui-bulk→S8 +7 Texts & Words / Interlinear → 7A-interlinear+sandbox(S9), 7B-chart(S3+port), 7C-concordance/stats, 7D-import(S5) +8 Notebook/Lists/Dict-config → 8a-notebook/lists/bulk(S3), 8b-dictconfig-dialogs(MVVM) +9 Managed document/text engine → 9.0-spike+G0–G3, 9.1-StText, 9.2-selection/caret, 9.3-layout/box, 9.4-embedded +10 Browser/PDF + Graphite path → 10A-gecko/pdf/preview, 10B-graphite-classify+managed-path +11 Application shell → 11a-lifetime/windowing, 11b-mainxml-compiler, 11c-command/state, 11d-nav/panes, 11e-screen-registry, 11f-startup/installer/switch +12 Runtime modernization → 12-net10-port → (green net10+Av11.3.17 checkpoint) → 12-av12-bump → 12-nunit4 +13 Cutover + decommission + xplat → 13a-flip+bake, 13b-delete(leaf-first,+ViewsInterfaces split,+native Graphite), 13c-xplat+final-gates +``` + +## 7. Sequencing corrections (edges to add) + +- `S9 → S6` (morphology document editors) — **missing, biggest graph error.** +- `S3 → S6` (FdoUi bulk editors), `S5 → S6` (launcher dialogs). +- `S3 → S8`, `S9 → S8` (bulk-edit + any Views-coupled list editors). +- `S9 (extended) → S7A` make explicit; `S3 → S7B/7C` (chart + concordance grids). +- Native Graphite + native Views deletion both **→ S13**, not S10/S9. +- `retire-linux-era-view-shims → S13` (narrow prerequisite, preserves VwTextStore/IViewInputMgr/ManagedVwDrawRootBuffered). + +## 8. Required follow-ups outside the plan doc + +- Correct `architecture-patterns.md` §12 wording (legacy-mimic → modernized-look-allowed) to match §11.4. +- Add superseded banners to all four files of `graphite-transition-support`; salvage its G0–G3 classifier + + font-outreach obligation into Stage 9.0 / Stage 10B. +- `fieldworks-managed-netfx-review` skill premise (net48-vs-net8, C#7.3) is stale (repo defaults C#8, + uniformly net48) — refresh when Stage 12 starts. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/dialog-mvvm-spike-review.md b/openspec/changes/avalonia-migration-roadmap/reviews/dialog-mvvm-spike-review.md new file mode 100644 index 0000000000..2187adda1f --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/dialog-mvvm-spike-review.md @@ -0,0 +1,106 @@ +# Dialog-MVVM spike — review of the approach & scalability to more dialogs + +> Spike branch: `dialog-mvvm-spike`. Purpose: prove the Stage 1.2 decision (hand-authored Avalonia +> dialogs use XAML + CommunityToolkit.Mvvm + compiled bindings, in a dedicated XAML-enabled project) is +> buildable and verifiable on the FieldWorks net48 toolchain, and assess how cheaply more dialogs can be +> created from it. First real dialog: **Tools → Options** (4 tabs, checkboxes, combos). + +## 1. Verdict + +**The approach is sound and the toolchain works.** The first XAML/MVVM dialog in the repo builds green +through the real `build.ps1`, and the foundation is a clean, repeatable template for the ~200-dialog +Stage-5 reservoir. The cost per new dialog is low and well-suited to mixed-experience devs + AI, with +two small, one-time foundation pieces still to add (a host-modal wrapper for coexistence, and the +localization lane). Details below. + +## 2. What was built (the spike artifacts) + +- **`Src/Common/FwAvaloniaDialogs/`** — the dedicated XAML-enabled project (net48, `EnableDefaultAvaloniaItems`, + `AvaloniaUseCompiledBindingsByDefault`, references CommunityToolkit.Mvvm + the foundation `FwAvalonia`). + - `OptionsDialogView.axaml` / `.axaml.cs` — XAML `UserControl`: `TabControl` (4 tabs) + `CheckBox`es + + `ComboBox`es + OK/Cancel, **compiled-bound** (`x:DataType` + compiled bindings), stable `AutomationId`s. + - `OptionsDialogViewModel.cs` — CommunityToolkit.Mvvm view-model: `[ObservableProperty]` settings, + `[RelayCommand]` Ok/Cancel, an `Accepted` result. +- **`Src/Common/FwAvaloniaDialogs/FwAvaloniaDialogsTests/`** — headless runtime tests (compiled-binding + both-directions, command firing, 4-tab load). +- Build wiring: `CommunityToolkit.Mvvm` pinned in `Directory.Packages.props`; the project + test added to + `build.ps1`'s net48 Avalonia loop; both excluded from the main `FieldWorks.proj` sln-restore traversal. + +## 3. Validation + +- **Build / compile — PROVEN.** `build.ps1` completed **exit 0** and the Avalonia loop produced + `Output\Debug\FwAvaloniaDialogs.dll`. So Avalonia XAML compilation + CommunityToolkit.Mvvm source + generators + compiled bindings all build on **net48** through the customized FieldWorks build. +- **Runtime (headless) — PROVEN, 4/4 passed** (`test.ps1 -TestProject FwAvaloniaDialogsTests`, exit 0): + compiled bindings propagate **both directions** (VM→control and control→VM); the generated + `OkCommand`/`CancelCommand` are bound and fire (`Accepted` true/false); the compiled XAML loads all four + tabs. So the dialog-authoring toolchain is validated **end to end** — XAML compiles *and* the MVVM + bindings/commands work on a realized headless surface, on net48. + +## 4. The integration finding that matters (and how it was solved) + +The one real wrinkle was **not** the XAML compiler itself but the repo's build wiring: + +- FieldWorks restores packages via **`dotnet restore FieldWorks.sln`** (solution-scoped), but the managed + build is a **glob traversal** (`FieldWorks.proj` includes `Src\**\*.csproj`). A new project that's in the + glob but **not in the `.sln`** has no `project.assets.json` → `NETSDK1004`. +- **Fix used:** exclude the dialog project (and its test) from the `FieldWorks.proj` traversal, and build + them in **`build.ps1`'s dedicated net48 Avalonia loop**, which runs each Avalonia project through its own + `MSBuild /t:Restore;Build` — *isolated from the main traversal's ILRepack/manifest steps.* This is why + the "XAML compiler vs. customized MSBuild" worry didn't bite: the Avalonia projects already build on a + separate, plain-MSBuild path. +- **Clean follow-up (not blocking):** add `FwAvaloniaDialogs` (+ test) to `FieldWorks.sln` so they also + restore/build via the main path and open in Visual Studio — then the traversal exclusions can be removed. + Until then, the Avalonia loop builds + restores them correctly. + +## 5. Ability to create more dialogs from this foundation — assessment + +**High. The pattern is small, repeatable, and largely mechanical.** A new dialog is three artifacts: + +1. `XyzDialogView.axaml` — declarative layout (tabs/groups/fields/buttons), `x:DataType` set, `AutomationId`s. +2. `XyzDialogViewModel.cs` — `ObservableObject` with `[ObservableProperty]` state + `[RelayCommand]` actions. +3. `XyzDialogTests.cs` — headless: bindings + commands. + +What makes scaling cheap: +- **Compiled bindings catch binding mistakes at *build time*** — the single biggest de-risker for AI- and + junior-authored dialogs (a wrong property name fails the build, not silently at runtime). +- **Source generators remove boilerplate** — observable properties + commands are one attribute each. +- **Owned WS-aware controls are reusable inside XAML** — `FwMultiWsTextField`/`FwOptionPicker` from the + foundation drop into dialogs wherever a writing-system field or a "select from a list" surface appears, + so dialogs don't re-implement linguistic input. +- **View-models are unit-testable headlessly** — dialog logic gets real regression coverage without UI, + which is exactly the parity evidence the migration program requires. + +Turn-key foundation — **the host-modal wrapper is now DELIVERED**: +- **`AvaloniaDialogHost.ShowModal(owner, dialogBody, viewModel, title)`** (FwAvalonia) shows any dialog + `UserControl` inside a **WinForms-owned modal `Form`** during coexistence (per `dialog-ownership.md` — no + Avalonia `Window.ShowDialog`), returns the accepted result, and restores owner focus. The view-model + implements **`IDialogViewModel`** (raises `CloseRequested(bool)`); OK/Cancel close the window with no + windowing code in the VM. Avalonia init funnels through the single shared `FwAvaloniaRuntime.EnsureInitialized()`. + So a new dialog really is **"view + VM + `ShowModal`."** Verified by the `WireClose` + VM-close-contract + tests (7/7); the modal `ShowDialog` itself is desktop-verified (it blocks the WinForms loop, not headless). +- Still to add (one-time): **a scaffolding generator** (Stage 1.1) that emits the three files + a red test. +- **The localization lane**: the spike hardcodes English (flagged in the XAML). Real dialogs route strings + through `FwAvaloniaStrings.resx` / the StringTable lane and add localized `AutomationProperties.Name`. + +Suitability by author: **simple settings/confirmation dialogs → junior + AI** (mechanical, build-checked); +**wizards / WS-setup / project-properties → mid**; **Views-coupled dialogs (Find/Replace, Styles) → not +here** — they belong with the document-engine work (Stage 9), not the MVVM dialog kit. + +## 6. Gaps / remaining before this is production-ready (not blockers to the *spike*) + +| Gap | Why / where | +| --- | --- | +| ~~Host-modal wrapper~~ — **DONE** | `AvaloniaDialogHost.ShowModal` + `IDialogViewModel` + shared `FwAvaloniaRuntime` init (FwAvalonia). Verified 7/7. | +| ~~`FwAvaloniaDialogs` in `FieldWorks.sln`~~ — **DONE** | Added via `dotnet sln add`; restores + opens in VS. Kept on the Avalonia-loop build path by design. | +| Localization | Spike strings are hardcoded English; move to `.resx`/StringTable + add localized automation `Name`. | +| Real PropertyTable wiring (for Options specifically) | The Options VM is self-contained; binding it to the app-settings bus is the actual Stage-5 migration of this dialog. | +| Modernized-Fluent theme baseline | Apply the chosen Fluent look (decision §11.4) so dialogs match the upgraded chrome. | + +## 7. Recommendation + +Adopt this as the **Stage-5 dialog template**. Next concrete steps, in order: (1) build the reusable +**host-modal wrapper**; (2) add the project to `FieldWorks.sln`; (3) wire the **localization lane** + a +scaffolding generator. After that, the ~200-dialog reservoir is genuine parallel hand-off work — start +with Tier-A (small, Views-free) dialogs, keep Find/Replace + Styles out (Stage 9), and let compiled +bindings + headless VM tests be the per-dialog definition-of-done. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-01-platform-enablement-kit.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-01-platform-enablement-kit.md new file mode 100644 index 0000000000..5da8dc0b82 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-01-platform-enablement-kit.md @@ -0,0 +1,178 @@ +# Stage 1 Review — Migration platform & developer-enablement kit + +> Reviewer pass over Stage 1 of `complete-migration-program.md` (§6 Track I, §4 table row 1). +> Grounded in the as-built `Src/Common/FwAvalonia/` tree and the frozen skill references. +> Status: planning review only; no code/behavior change proposed here. + +## Scope assessment + +The five deliverables are the right ones and are well-chosen: (1) reusable region +scaffolding/generator, (2) Path-3 harness promoted to a shared test base, (3) frozen+documented +seam catalog & plugin-registry onboarding, (4) a "migrate-a-surface" runbook wired to the skill, +(5) locked conventions (AutomationId-from-StableId, density tokens, ControlTheme baseline, +localization lanes, RootNamespace). The validation gate (junior+Claude migrates a trivial surface +end-to-end using only the kit) is exactly the right exit criterion — it tests the *kit*, not the +author. + +Three scope gaps and one over-scope concern: + +- **Gap — the trivial validation surface is a *dialog*, but dialogs were ruled OUT of the + region/IR pattern** (Decision 3, 2026-06-15: dialogs/wizards use CommunityToolkit.Mvvm + + compiled bindings, *not* the composer/IR). The §6 Stage-1 text says "e.g. a single simple + dialog." That means the kit's headline deliverable (region scaffolding/generator) is NOT what + the validation surface exercises. **Stage 1 actually has two kits**: (a) the region/IR + scaffolding for XML-driven surfaces, and (b) an MVVM-dialog scaffolding + runbook for the + Stage-5 reservoir. The plan currently funds (a) explicitly and leaves (b) implicit, yet the + validation gate and the largest hand-off reservoir (Stage 5, ~200 dialogs) both ride on (b). + This is the single most important correction. + +- **Gap — naming/de-`LexicalEdit` generalization is unscoped work.** Nearly every region-layer + type is `LexicalEdit`-prefixed and single-scenario hardcoded (see Feasibility). "Extract a + reusable region scaffolding" implicitly requires renaming/parameterizing these, which is real + senior effort, not a template-copy. Call it out as an explicit sub-task with a compatibility + plan (Stage 4 still consumes these symbols). + +- **Gap — a generator needs a *test harness for the generator itself*** (golden output, "does the + generated skeleton compile + its stub tests run red-then-green"). Otherwise the generator rots. + +- **Over-scope risk — "freeze the seam catalog."** The catalog is demonstrably *not* frozen: + `IXCoreCommandBridge` is explicitly shell-phase/deferred, document-engine seams (Stage 9) do not + exist, and the grid/tree control (Stage 3) will likely add virtualization seams. Stage 1 should + **document and version** the catalog and define the *amendment protocol* (already half-specified + in seam-catalog.md §"add the new seam here in the same PR"), not declare it frozen. Freezing a + catalog that Stages 3/9 will provably extend sets up a false gate. + +## Feasibility (repo-grounded) + +Feasible and well-advanced, but "turn the one-off into a platform" is more generalization than the +plan's verb "extract" implies. What I found: + +**Already reusable, near as-is:** +- Owned field controls — `Src/Common/FwAvalonia/Region/FwFieldControls.cs` + (`FwMultiWsTextField`, `FwChooserField`, `FwReferenceVectorField`), `FwOptionPicker.cs`, + `RegionMenuFlyout.cs`, `HoverReveal.cs`, `RegionFocusMemory.cs`. These take an `automationId` + + model and are surface-agnostic. The AutomationId-from-StableId convention is *implemented* + (`AutomationProperties.SetAutomationId(this, automationId)` then `+ "." + wsKey`, + `+ ".Settings"`, `+ ".Add"`, `+ ".Item." + item.Key` — FwFieldControls.cs:44,138,361,537,576) + but **not documented** as a contract — Stage 1 must lift it into a written convention + a peer + test, because juniors will otherwise invent their own ids. +- Typed IR pipeline — `Src/Common/FwAvalonia/ViewDefinition/` (importer, compiler, JSON + serializer, coverage). Genuinely surface-generic already. +- Seams — `Src/Common/FwAvalonia/Seams/ISeams.cs`, `SeamImplementations.cs`, + `ActiveHostContract.cs`. Reusable; well-documented in seam-catalog.md. +- Forbidden-symbol audit — `FwAvaloniaTests/EngineIsolationAuditTests.cs`. Reusable as-is. +- `` is **already present** in `Src/Common/FwAvalonia/FwAvalonia.csproj` and + `Src/xWorks/xWorks.csproj` — so this convention is locked for existing projects; Stage 1's job is + to make it a *checklist item / project template default* for the many new csprojs Stage 5 spawns. + +**Needs real generalization (senior work, not copy-paste):** +- The region layer is single-scenario and `LexicalEdit`-named: + `LexicalEditRegionModel.cs`, `LexicalEditRegionMapper.cs`, `LexicalEditRegionView.cs`, + `LexicalEditSurfaceSelectionService.cs` (`SurfaceDecision` returns a `LexicalEditSurface` enum), + `LexicalEditFirstSlice.cs`. The composer lives outside FwAvalonia in + `Src/xWorks/FullEntryRegionComposer.cs`. `RegionViewingServices.cs` is a static, hardcoded + lexical map. A "new-surface generator" must abstract a `RegionId`/surface key out of these and + rename the switch service to an app-wide registry — which **overlaps Stage 2** (the plan + already assigns "generalize `LexicalEditSurfaceSelectionService` → app-wide surface registry" + to Stage 2). Decide the boundary: I recommend the *generic switch contract/registry shape* lands + in Stage 1 (the kit needs it to scaffold), with Stage 2 doing the host-bridge wiring. +- The Path-3 harness — `FwAvaloniaTests/Path3BundleTests.cs` — is a single hardcoded + `scenarioId = "first-slice"` test that points at one committed WinForms baseline PNG. Promoting + it to a "shared test base any surface test derives from" means extracting a parameterized base + class (scenarioId, IR factory, region view factory, baseline path, lane manifest writer) — a + worthwhile but non-trivial refactor. The lane-manifest pattern (proven/pending per lane, never + silently omitted) is the valuable reusable kernel and is already correct. + +**Validation-gate feasibility caveat:** the gate says junior+Claude migrates a surface "with a +green parity bundle." For an MVVM dialog there is no IR semantic snapshot, so the Path-3 bundle +shape (semantic.json anchor) doesn't map cleanly. Stage 1 must define a **dialog-flavored evidence +bundle** (visual + workflow/UIA + localization + AutomationId audit; no IR semantic lane) or the +gate is unsatisfiable as written. + +## Best practices (enablement kits / scaffolding) + +- **Generator output must be red-green-test-ready.** The scaffold should emit stub tests that fail + meaningfully until parity is captured — aligns with the program's anti-"hallucinated-parity" + stance (§2 principle 4). A generator that emits green-by-default stubs is an anti-pattern here. +- **"Golden path" + guardrails over freedom.** Junior+Claude productivity comes from one blessed + path with build-time enforcement (compiled bindings catch binding errors — Decision 3; the + symbol audit catches engine leakage). Lock these as defaults in the template, not as docs. +- **Executable conventions beat prose.** Each locked convention should have a *test* (an + AutomationId-shape test, a RootNamespace-present test, a localization-lane lint), not just a + runbook paragraph. The repo already does this for symbols/host-contract; extend the pattern. +- **Runbook should be a checklist the skill drives, not a tutorial.** The 10-step workflow in + `SKILL.md` and `migration-checklist.md` already exist; Stage 1's runbook should *map each step + to a concrete repo action/command/generator invocation* and live next to (or inside) the skill, + avoiding a third parallel copy of the checklist that will drift. +- **Version the kit.** Stamp generated surfaces with the kit/template version so a later kit change + can find surfaces built on the old template. + +## Interactions & dependencies + +- **Depends on Stage 0 only** (per the table) — correct. It does **not** depend on Stage 2/3, and + it must not: making it wait for the grid (Stage 3) would stall all junior hand-off. The kit + should scaffold a *placeholder/seam* for the shared grid so Stage-3 delivery slots in without + re-scaffolding. +- **Blocks all junior work** and is the explicit prerequisite for Stage 5 (§8 hand-off model). + Concretely Stage 5 needs from Stage 1: the **MVVM-dialog scaffolding + dialog evidence bundle + + dialog-ownership runbook** (modality stays WinForms-owned per dialog-ownership.md / Decision 3), + not the region generator. **This is currently under-served by the Stage-1 deliverable list.** +- **Stage 6 (mid, detail surfaces)** is the true consumer of the region/composer generator + plugin + onboarding ("how to add a custom slice"). Stage 1 must deliver the plugin-registry runbook + (`Src/xWorks/RegionEditorPlugins.cs`, burn-down tracking) aimed at Stage 6. +- **Stage 8** consumes both kits (dialogs + detail). No new Stage-1 obligation beyond 5/6. +- **Overlap with Stage 2** on the surface-registry generalization (noted above) — needs an explicit + seam-line so the two stages don't both rewrite `LexicalEditSurfaceSelectionService`. +- **Tension with Stage 4:** the plan sequences hand-off "begins after Stage 1, accelerates after + Stage 4 (worked exemplar)." But the manifest (`region-manifest.md` §6) shows the exemplar still + has **Partial** gates (layout-semantic, validation, accessibility, performance). Stage 1's + *region* generator is therefore being extracted from an exemplar that is not yet fully green. The + conventions/seams/harness are stable enough to extract now; the *region composer template* should + be marked provisional until Stage 4 closes, or Stage 1 risks freezing patterns Stage 4 changes. + +## Recommended plan changes (concrete) + +1. **Split the kit into two explicit tracks** in the §6 Stage-1 text: (a) region/IR scaffolding for + XML-driven surfaces (feeds Stage 4/6); (b) **MVVM-dialog scaffolding (CommunityToolkit.Mvvm + + compiled bindings) + dialog-ownership runbook + dialog evidence bundle** (feeds Stage 5 and the + validation gate). Make (b) a named deliverable, not implied. +2. **Redefine the validation gate** to use a dialog (track b) AND add a second mini-validation: a + trivial *detail/region* surface through track (a), so both kits are proven before hand-off. + Define the **dialog evidence bundle** shape (visual + UIA/workflow + localization + AutomationId + audit; no IR semantic lane) since the Path-3 semantic anchor doesn't apply to dialogs. +3. **Reword "freeze the seam catalog" → "document, version, and define the amendment protocol."** + The catalog is provably still growing (Stage 3 grid seams, Stage 9 doc-engine seams, + `IXCoreCommandBridge` shell-phase). Freezing it is a false gate. +4. **Add an explicit "de-`LexicalEdit` / parameterize the region layer" sub-task** with a named + compatibility plan for Stage-4 consumers, and **draw the Stage-1/Stage-2 boundary** on the + surface-registry generalization (generic switch *shape* in 1; host-bridge wiring in 2). +5. **Make conventions executable:** ship a test per locked convention (AutomationId shape from + StableId, `` present, localization-lane placement, density-token usage). The + AutomationId derivation is already in code (FwFieldControls.cs) — lift it to a documented + + tested contract. +6. **Promote Path-3 to a parameterized base class** (`Path3BundleTests` → abstract base taking + scenarioId/IR-factory/view-factory/baseline-path/lane-writer) and ship one derived sample. +7. **Require the generator to emit red stub tests** (fail until parity captured) and add a + generator-golden-output test so the generator itself is covered. +8. **Mark the region composer template "provisional until Stage 4 green"**; ship seams/harness/ + conventions as stable now. + +## Open questions / risks + +- Is the validation surface a dialog (track b) or a region (track a)? The plan says dialog, but the + headline deliverable is the region generator — they must be reconciled (Rec. 1–2). +- Where does the surface-registry generalization live — Stage 1 or Stage 2? Currently double-booked. +- Risk: extracting the region template from a not-yet-green exemplar (Stage 4 Partial gates) bakes + in patterns that Stage 4 later changes; mitigate by marking the region template provisional. +- Risk: a generator/runbook that drifts from the skill set. Mitigate by wiring the runbook *into* + the skill (single source) and versioning the template. +- Risk: dialog evidence bundle undefined → Stage-5 juniors produce inconsistent/weak evidence at + ~200-dialog scale (the highest-volume, lowest-supervision reservoir). Define it in Stage 1. + +## Confidence + +**High** on the feasibility read (the as-built code is present and inspected; conventions and seams +are stable enough to extract now). **Medium** on the scope correction — the dialog-vs-region split +hinges on Decision 3's interaction with the validation gate, which the plan text has not yet +reconciled; if the program intends the validation surface to be a region after all, Recs. 1–2 change +shape. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-02-coexistence-shell-spine.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-02-coexistence-shell-spine.md new file mode 100644 index 0000000000..ca40cbee9f --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-02-coexistence-shell-spine.md @@ -0,0 +1,203 @@ +# Stage 2 Review — Coexistence shell spine & host contracts + +Reviewer: senior migration review (Claude, Opus 4.8). Date: 2026-06-15. +Scope reviewed: `complete-migration-program.md` §4/§6 Stage 2 + §11 decisions; +architecture-patterns §3/§7; seam-catalog; parity-evidence; the as-built host/seam +code in `Src/Common/FwAvalonia/` and `Src/xWorks/RecordEditView.cs`; the +`fieldworks-avalonia-shell-migration` change. + +--- + +## 1. Scope assessment + +Stage 2 bundles six deliverables (generalized host, generalized surface registry, +`IXCoreCommandBridge`, feature-flag/dual-run build, ControlTheme baseline, pulled-forward +shell contract layer). This is **right-sized as one epic but internally heterogeneous**: +five of the six are pure generalizations of code that already exists for Lexical Edit +(`LexicalEditHostControl`, `LexicalEditSurfaceSelectionService`, `IXCoreCommandBridge` +stub in `Seams/ISeams.cs:72`, the `UIMode` flag), while the sixth — pulling forward the +shell contract layer (window/dialog ownership ports) — is genuinely new design surface +imported from `fieldworks-avalonia-shell-migration`. + +Findings: + +- **The host and surface-switch generalization is mostly a rename + parameterize, not + net-new work.** `LexicalEditHostControl` is already a thin `WinFormsAvaloniaControlHost` + wrapper (companion strip, focus memento, keyboard-interop overrides) that is + region-agnostic except for the `LexicalEditRegionModel`/`LexicalEditRegionView` types in + its `ShowRegion` signature. Generalizing it means extracting an `IRegionView`/`IRegionModel` + abstraction and making `ShowRegion` generic — low risk. Scope is correct; effort is + smaller than its sibling stages. +- **`HostUiBehavior` is already the four-state enum** (`LegacyActive / SupportedAvalonia / + ExplicitLegacyFallback / Blocked`) the master plan describes — Stage 2's job is to move + the *tool list* (`LexicalEditSurfaceResolver.SupportedAvaloniaToolNames`, currently a + hard-coded `{"lexiconEdit","lexiconEditPopup"}` array) into an app-wide registry keyed by + area/tool id, not to invent the model. Correctly scoped. +- **The ControlTheme baseline is under-specified for the new charter.** §11 decision 4 + ("upgrade the look; modernized Fluent, not legacy mimicry") changed the contract since + `architecture-patterns.md §12` was written (which still says "measured against legacy + WinForms baselines" for *density*). Stage 2's exit gate says "theming + AutomationId + conventions locked." That lock must explicitly reconcile: visual lane = intentional + restyle allowed; density + semantic + workflow lanes = parity-enforced. This is a real + decision item, not a copy-forward, and the stage text should call it out. +- **`IXCoreCommandBridge` scope is correctly bounded.** The seam already exists as an + interface; seam-catalog §1 says "shell-scope wiring happens in the shell phase, not per + region." Stage 2 should stand up the *bridge implementation over the existing Mediator/ + PropertyTable* for region-local + the minimal shell-scope commands the host needs, and + explicitly **defer** full shell command routing to Stage 11. The plan says this; keep it. +- **One scope gap:** the master plan lists "global feature-flag plumbing (default WinForms) + and the dual-run build" but the repo already ships the flag (`UIMode` property, + `LexicalEditSurfaceResolver`) and a single-CLR in-process dual surface in `RecordEditView`. + What's *missing* and should be named explicitly in Stage 2 is a **build-level dual-run + switch** (a way to produce/CI-test both the all-WinForms and flag-on configurations) — the + current setup is a runtime preference, not a build configuration. Clarify which is meant. + +## 2. Feasibility (repo-grounded) + +**Feasible, and the riskiest dependency is already retired.** The central feasibility +question — "does the host bridge run on net48?" — is answered yes by the shipping build: +`Src/Common/FwAvalonia/FwAvalonia.csproj:19` targets `net48`; `Directory.Packages.props:185-213` +pins **Avalonia 11.3.17** with the explicit comment that 11.3.x "is the last line that still +ships netstandard2.0 assemblies and therefore loads on net48" and that "Avalonia 12.x dropped" +that support. `Avalonia.Win32.Interoperability 11.3.17` (the `WinFormsAvaloniaControlHost` +carrier) is referenced and in use. So Stage 2 inherits a *proven* net48 + Avalonia-11 host; +it does not have to establish it. This is the single most important de-risking fact and it is +already true. + +Confirmed working in-repo today: +- In-process host: `LexicalEditHostControl` embedded in `RecordEditView` via + `m_lexicalEditSurfaceFactory.Create(LexicalEditSurface.Avalonia)` (RecordEditView.cs:599). +- Single Avalonia init gate + finalizer-safe sync context + (`LexicalEditHostControl.EnsureAvaloniaInitialized`, `FinalizerSafeSynchronizationContext`). +- Active-host contract enforcement (`Seams/ActiveHostContract.cs`) + audit tests + (`EngineIsolationAuditTests.cs`, `SurfaceAndHostContractTests.cs`, + `RecordEditViewActiveHostContractTests.cs`). +- Editing-aware refresh coordination across the boundary + (`AvaloniaRegionRefreshController`, RecordEditView.cs:611-637) — including the documented + cross-window undo re-entrancy hazard (`LockRecursionException`) and its deactivate-settle + mitigation. This is exactly the class of coexistence bug Stage 2 must generalize carefully. + +## 3. Best practices (incremental host / strangler spine) + +1. **Generalize by extracting an interface seam, not by widening the concrete host.** Keep + `LexicalEditHostControl`'s mitigations (the `IsInputKey`/`ProcessCmdKey`/`PreviewKeyDown` + directional-key bypass, the per-host remembered splitter width, the focus memento) as the + *reference behavior* a generic `RegionHostControl` or an `IRegionView`-based + host preserves. Do not lose these by accident in the rename — they encode hard-won + coexistence fixes. +2. **Keep WinForms owning all modality through Stage 11** (architecture-patterns §7, + dialog-ownership.md). Web research confirms the underlying constraint: + `Application.Current.ApplicationLifetime` is null when Avalonia runs hosted in WinForms, so + Avalonia modal windows are unsupported on this path; the supported pattern is a WinForms + `Form` owning embedded Avalonia content. Stage 2's pulled-forward dialog-ownership ports + must encode "modal = WinForms-owned" as a contract, not aspiration. +3. **Airspace:** because the WinForms-hosted Avalonia control is always topmost, no WinForms + control or popup can paint over the Avalonia surface. Use Avalonia *flyouts inside* the + surface (already the rule — `RegionMenuFlyout`), never free popup windows. The generic host + must expose the flyout path, not a window path. +4. **Tab/focus does not cross the boundary** (Avalonia issue #12025). The host owns focus + internally; there is no WinForms↔Avalonia Tab order. Keep this as a host-contract test. +5. **Surface registry must default to WinForms and fail safe.** `LexicalEditSurfaceResolver` + already returns `WinForms` for unknown tools and treats null tool as "supported" only for + resolution convenience — the app-wide registry should make *unregistered = ExplicitLegacy + or Blocked*, never silent Avalonia. No silent fallback (architecture-patterns §3). +6. **AutomationId day-one** (master-plan principle 8): the generic host must require an + AutomationId-deriving convention (from StableId) on every hosted view, enforced at the host + seam so Stage 5+ juniors cannot skip it. +7. **One global undo stack only.** The cross-window re-entrancy mitigation already in + RecordEditView is the canonical pattern; the generalized host must carry the undo-guard / + deactivate-settle hooks, not leave them per-surface. + +## 4. Interactions & dependencies + +- **Gates Stage 5 (dialogs) and Stage 11 (shell).** Stage 5 needs the generalized host + + dialog-ownership contract before juniors can place Avalonia dialog *content* inside + WinForms-owned modal forms. Stage 11 consumes the same window/dialog ownership ports and + promotes `IXCoreCommandBridge` from region-local to shell-global. **Stage 2 must ship the + *contract* shapes (ports/interfaces) that Stage 11 will implement** — and explicitly resist + implementing the shell. The plan already says "pull forward the contract layer ... without + replacing the shell yet" — good; the review's recommendation is to *name the exact ports* + (lifetime, main-window, active-window registry, dialog owner, dispatcher, shutdown, modal + state — per shell-migration design §2) so Stage 2 and Stage 11 share one definition. +- **Relationship to `fieldworks-avalonia-shell-migration`:** that change's design §7 says the + shell phase *consumes* the lexical-edit seams rather than redefining them, and §2/§3 list the + ports. Stage 2 is the right place to *extract* those ports while WinForms stays default. + Recommend Stage 2 own the port extraction and shell-migration design §2 (decision: "neutral + ports first") be cross-linked as the source of truth; avoid two divergent port definitions. +- **`IXCoreCommandBridge` ↔ `IRecordNavigationContext`:** both bridge to Mediator/ + PropertyTable (ISeams.cs). The nav-context seam already exists and is "coexistence + infrastructure, not throwaway." Stage 2's command bridge should reuse the same + PropertyTable-access discipline (never reach into PropertyTable directly from a region — + seam-catalog §1) and share the bus access with nav-context, not open a second path. +- **Ordering vs Stage 12 (runtime jump):** the **one-CLR constraint is the governing fact**: + during coexistence the whole process is one CLR on Avalonia 11.x / net48 (master plan §5). + Stage 2 must therefore write all new host/bridge/theme code **Avalonia-12-ready** (avoid APIs + removed in 12) but ship on 11.3.17. The ControlTheme pipeline especially is at risk: Fluent + theming and `ControlTheme`/resource APIs changed between Avalonia 11 and 12 — Stage 2 should + avoid 11-only theming idioms that Stage 12 must rip out. Add an explicit "Av12-ready" check + to the theming task. + +## 5. Recommended plan changes + +1. **Split the Stage 2 epic into two work-streams in the issue breakdown:** (a) *generalization + of existing assets* (host, surface registry, command-bridge impl, flag/build) — low risk, + can start immediately; (b) *new contract design* (window/dialog ownership ports + ControlTheme + baseline decision) — higher design content, needs the shell-migration design as input. +2. **Name the exact ports Stage 2 extracts** (lifetime, main-window, active-window registry, + dialog owner, UI dispatcher, shutdown, modal state) and declare them the shared source of + truth for Stage 11. Reuse `IUiScheduler`/`IRegionLifetime` from `ISeams.cs` rather than + adding new dispatcher/lifetime abstractions (seam-catalog §2; shell-design §3/§7). +3. **Make the ControlTheme baseline an explicit decision item**, reconciling §11 decision 4 + (restyle allowed) with architecture-patterns §12 (density parity enforced): visual lane = + restyle OK; density/semantic/workflow/perf lanes = parity. Update architecture-patterns §12 + wording in the same PR so it stops saying "mimic legacy." +4. **Disambiguate "dual-run build"**: state whether Stage 2 delivers a build-time configuration + matrix (CI builds/tests both all-WinForms and flag-on) in addition to the existing runtime + `UIMode` preference. Recommend yes — CI must exercise both surfaces. +5. **Add a host-contract test suite as a Stage 2 exit artifact**: directional-key bypass, + no cross-boundary Tab, modal-is-WinForms-owned, flyout-not-window, focus save/restore, + AutomationId-required-on-hosted-view, single-undo-stack guard. Promote the existing + `LexicalEditHostControlTests` patterns into the generic suite. +6. **Add an explicit "Avalonia-12-readiness" gate to the theming task** (no Av11-only theming + APIs), to keep Stage 12's bump small. +7. **Update `LexicalEditSurfaceResolver.SupportsAvaloniaForTool` null-handling** when + generalizing: a null/unknown tool currently returns "supported" (resolver.cs:72) — the + app-wide registry should treat unregistered tools as explicit legacy/blocked, not supported. + +## 6. Open questions / risks + +- **OQ-1 (port ownership):** Do the window/dialog ownership ports live in `Src/Common/FwAvalonia/Seams/` + (with the other seams) or in a new shell-contracts assembly? Recommend `Seams/` to keep one + seam home until Stage 11 needs more. +- **OQ-2 (ControlTheme scope):** Is the modernized-Fluent baseline a full app ControlTheme or + per-control themes layered on `FluentTheme` (currently `FwAvaloniaApp.Initialize` just adds + `new FluentTheme()`)? Density tokens (`FwAvaloniaDensity.cs`) must compose with it. +- **OQ-3 (build matrix):** Does CI have headroom to build/test both surface configurations, or + is flag-on the only CI lane with all-WinForms covered by the legacy suite? +- **Risk — theming churn at Stage 12 (Med/Med):** mitigated by the Av12-ready gate (rec. 6). +- **Risk — generalization drops a coexistence fix (Med/High):** the focus/keyboard/undo + mitigations in `LexicalEditHostControl`/`RecordEditView` are subtle; mitigated by the + host-contract test suite (rec. 5) capturing them *before* the refactor. +- **Risk — port definitions diverge from shell-migration (Med/Med):** mitigated by declaring + Stage 2 the single source (rec. 2) and cross-linking the shell-migration design. +- **Risk — surface registry silent-fallback regression (Low/High):** mitigated by rec. 7 + + existing `ActiveHostContract` audit tests. + +## 7. Confidence + +**High** on feasibility and the net48/Avalonia-11 foundation (directly verified in the +shipping csproj and packages.props, plus a working in-process host in RecordEditView). +**High** on the coexistence-risk catalog (airspace/focus/modality/threading), corroborated by +Avalonia upstream issues and already mitigated in-repo. **Medium** on the ControlTheme-baseline +and dual-run-build scope, which are under-specified in the master plan and are the two items +most likely to expand. The recommended split + explicit decision items convert those from +hidden scope into named, gateable work. + +--- + +### Sources (external) +- Avalonia WinForms migration guide: https://docs.avaloniaui.net/docs/migration/winforms/ +- `WinFormsAvaloniaControlHost` API: https://docs.avaloniaui.net/api/avalonia/win32/interoperability/winformsavaloniacontrolhost +- Modal dialog from host (ApplicationLifetime null): https://github.com/AvaloniaUI/Avalonia/discussions/15977 +- Tab/focus across interop boundary: https://github.com/AvaloniaUI/Avalonia/issues/12025 +- Hosting Avalonia in WinForms: https://github.com/AvaloniaUI/Avalonia/issues/11454 diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-03-virtualized-grid-tree.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-03-virtualized-grid-tree.md new file mode 100644 index 0000000000..ed40c23804 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-03-virtualized-grid-tree.md @@ -0,0 +1,247 @@ +# Stage 3 Review — Shared editable virtualized grid/tree control + +**Reviewer:** Claude (Opus 4.8) • **Date:** 2026-06-15 • **Branch:** `010-advanced-entry-view-phase-1-2` + +Scope under review: master plan §4 (stage table, row 3) and §6 Stage-3 detail in +`openspec/changes/avalonia-migration-roadmap/complete-migration-program.md`, against the frozen +architecture (`architecture-patterns.md` §4, §12), pivot triggers (`seam-catalog.md` §3), +perf budgets (`parity-evidence.md` §5), and the as-built code under `Src/Common/FwAvalonia/`. + +--- + +## 1. Scope assessment + +**Verdict: scope is correct in *charter* but mis-sized as *one epic of equal weight*. Split it, +sequence table-first, and pull the tree half forward into Stage 0 close-out where it already lives.** + +The instinct to own this control is right and is now *more* right than when the plan was written +(see §3 — TreeDataGrid's FOSS repo was archived 2025-10-13 and editing moved behind the commercial +Accelerate license). The "#1 off-the-shelf gap" framing holds. + +But "table AND tree in one stage" conflates two problems with very different maturity in this repo: + +- **The tree is largely already solved.** The detail/slice surface does *not* need a virtualized + tree at all. `Src/Common/Controls/DetailControls/DataTree.cs` (5,453 lines) lays slices out as a + **flat vertical stack with indent-only visual nesting** — it is not a real parent/child tree + widget. The Avalonia replacement `Src/Common/FwAvalonia/Region/LexicalEditRegionView.cs` (563 + lines) already renders that flat field list, and the committed baseline + `DataTreeTimingBaselines.json` tops out at **253 slices** (`timing-extreme`/`paint-extreme`). + 253 controls in a `ScrollViewer` is *not* a virtualization problem — it is comfortably below the + ~100+ threshold where Avalonia even recommends virtualizing per-item, and the existing view ships + it unvirtualized today. So "owned virtualized TREE" is **over-specified** for the surfaces named. + The genuinely unbounded tree need is the **popup possibility-list / chooser**, and that is + *already built and virtualized*: `FwOptionPicker.cs` uses a `VirtualizingStackPanel` with depth + indentation, and `TreeSpikeAndRtlTests.cs` (`SenseTreeSpikeTests`) validated stock `TreeView` for + the bounded (≤500) case per `architecture-patterns.md` §4. + +- **The table is the real, unsolved, large-data problem.** `LexicalBrowseView.cs` (165 lines) + exists but is explicitly **read-only display only** (its own header: "First version: read-only + display… sorting/filtering and bulk-edit columns follow"). The legacy surface it replaces is + enormous and feature-dense: `BrowseViewer.cs` (4,332), `XmlBrowseViewBase.cs` (2,245, inherits + native `RootSite`/`IVwRootBox`), `BulkEditBar.cs` (**7,685**), `FilterBar.cs` (2,835), + `DhListView.cs` (838), plus the fake-flid `XMLViewsDataCache.cs`. Editing, checkbox columns, + bulk-edit preview columns, RDE in-cell editing, multi-column sort, filtering, and column + reorder all live there. **This is where the engineering risk and most of the LOC sit.** + +**Recommendation:** keep one epic but make it **table-led and explicitly phased** (§5), and +**down-scope the "tree" deliverable** to "flat indented expander/collapse row chrome on the same +owned virtualizing list, used only if/when a detail surface exceeds the unvirtualized budget" — +governed by the existing `VirtualizingStackPanel` pivot trigger (`seam-catalog.md` §3), not built +speculatively. Treat the tree exit-gate fixture (253-slice detail) as a *budget the existing +unvirtualized view must already meet*, not proof of a new virtualized tree control. + +--- + +## 2. Feasibility (repo-grounded) + +**Owned virtualization on Avalonia 11.3.17 (the version in `Directory.Packages.props`) is feasible +for read display and is the only viable path given licensing; editable virtualization at scale is +feasible but carries two concrete, repo-relevant risks.** + +What is already proven in-repo: +- **Lazy data virtualization works.** `LexicalBrowseView.BrowseRowList` is an `IList`/ + `IReadOnlyList` facade that returns the count without materializing rows; cells + materialize only on `BrowseRow.Cells` access. `BrowseAndCanonicalJsonTests.cs` + (`TenThousandRows_RealizeOnlyTheVisibleWindow`) asserts <100 realized `ListBoxItem`s and <300 + materialized cells against a 10,000-row source. The 10k-row exit gate is therefore *already + partially met for read-only display.* +- **Typing-latency budget tooling exists.** `TypingLatencyHarnessTests.cs` enforces ≤6 ms/keystroke + at 100% DPI and ≤8 ms at 150% DPI over 500 keystrokes incl. RTL/bidi — the harness Stage 3's + editable-cell gate needs is in place. +- **Density/DPI gates exist** (`VisualParityAndDensityTests.cs`, `FwAvaloniaDensity` locked by + `DensityTokenGateTests`). + +Repo-grounded risks: +1. **`VirtualizingStackPanel` scroll/GC cost is a known upstream weakness.** Avalonia issue #18626 + (open, unresolved) documents a measure/arrange cycle on *every* scroll delta and GC pressure + from aggressive recycling; the maintainer-proposed fix (a ~50% viewport buffer) is not shipped. + This is *exactly* the condition behind the `seam-catalog.md` §3 pivot trigger ("escalate to a + fully owned realization-window virtualizer if scroll/expand or open-time budgets fail on the + production fixtures"). **The spike must measure scroll/expand on the production fixtures, not + just open-time**, because the current 10k test only proves realization count, not scroll + smoothness. +2. **Custom AutomationPeers for virtualized items are genuinely missing and non-trivial.** The + second survey found **zero** custom `AutomationPeer` subclasses anywhere in `FwAvalonia` — only + attached `AutomationProperties`. Avalonia's `ItemContainerGenerator` (unlike WPF/UWP) + **does not retain realized containers**; the panel does. A UIA tree that exposes *all* rows + (not just realized ones) for a virtualized grid therefore requires a custom + `ItemsControlAutomationPeer` that synthesizes peers for de-realized items and coordinates with + the virtualizing panel — this is real work the plan currently buries in one sub-bullet and is a + hard exit-gate dependency (`migration-checklist.md`/§7.7–7.9 require UIA2 evidence on realized + windows). **Size it as its own work item.** +3. **In-cell editing through the owned table is unproven.** `LexicalBrowseView` is read-only; the + legacy editable path (`XmlBrowseRDEView`, fake-flid `ktagEditColumnBase`, native `RootSite` + cells) has no Avalonia counterpart yet. Editable cells must reuse the existing owned field + controls (`FwMultiWsTextField`, `FwChooserField`) inside cells and route through `IEditSession`/ + `IUndoRedoCoordinator` — feasible because those seams exist, but it is the bulk of the build. + +Net: read-display + selection + keyboard at 10k rows is low-risk (mostly done). Editable cells + +custom AutomationPeers + scroll-budget-proven virtualization is the senior-grade work. + +--- + +## 3. Best practices (and an external development that strengthens the plan) + +- **Data virtualization vs UI virtualization — do both, as the repo already does.** The + `IBrowseRowSource`/`BrowseRowList` lazy facade (data virtualization) over a + `VirtualizingStackPanel` (UI virtualization) is the correct two-layer pattern; preserve it and + extend it to editable cells rather than introducing a second mechanism. +- **Prefer the existing stock substrate until a fixture fails.** Avalonia's own guidance and the + `seam-catalog.md` pivot trigger both say: own the *row/cell*, virtualize with stock primitives, + and escalate to a fully-owned realization-window virtualizer *only* if measured scroll/expand/ + open budgets fail. The spike's job is to fire-or-clear that trigger with numbers, not to default + to a from-scratch virtualizer. +- **Recycling/buffering:** if scroll smoothness fails (per #18626), the cheapest mitigation is a + realized buffer above/below the viewport before committing to a fully-owned panel; record which + was chosen and why in the manifest. +- **ItemsRepeater is NOT the substrate.** It is on a deprecation path (slated for eventual + obsolescence once a `VirtualizedUniformPanel`/`ItemsControl` virtualization story lands; kept + only for 12.x back-compat). The `seam-catalog.md` "ItemsRepeater" pivot trigger ("reconsider only + if un-deprecated with maintained virtualization") should be marked **confirmed-closed** by this + review. +- **Custom AutomationPeer pattern:** subclass `ItemsControlAutomationPeer`; synthesize child peers + from the *data* (row count) not the realized containers, and implement + `Selection`/`ExpandCollapse`/`Invoke`/`Grid` patterns explicitly. Day-one `AutomationId` from + StableId is already the repo convention (§7.5/7.8). + +**External development that *strengthens* the no-TreeDataGrid decision (record it):** TreeDataGrid's +FOSS repository was **archived 2025-10-13** (no further fixes/updates), and editing/advanced +features now require the commercial **Avalonia Accelerate** license (v11.2.0+). The plan's +"display-only, licensing-blocked" rationale is no longer just a snapshot — the FOSS option is now +*abandoned*. The `seam-catalog.md` TreeDataGrid pivot trigger ("re-evaluate if relicensed +permissively") has moved **further** from firing, not closer. This should be noted in the manifest +as the recorded decision. + +--- + +## 4. Interactions & dependencies + +What the downstream stages actually pull from Stage 3: + +- **Stage 4 (finish Lexical/Advanced Entry):** needs the **editable table** for the entry view's + embedded tables (lexical-edit tasks 7.x; §6 Stage-4 bullet "Tables/browse in the entry view on + the Stage-3 control"). This is the *first real consumer of editing*, and Stage 4 is the exemplar + every Track-II stream copies. **Stage 4 is gated on the editable table, not on any tree.** This is + the strongest argument for table-first. +- **Stage 7 (Texts & Words / Interlinear + Concordance):** needs the **table** for concordance / + occurrence grids. Note the survey finding: `ConcordanceControl.cs` drives a `BrowseViewer` + *indirectly* through a `RecordClerk` (`OccurrencesOfSelectedUnit`), and interlinear itself is + **`RootSite`/Views-engine document rendering, not a grid** — so Stage 7 depends on Stage 3 for the + *concordance browse grid* but on **Stage 9** (managed document engine) for interlinear proper. The + master graph already wires `S3→S7` and `S9→S7`; that is correct. Stage 7's grid need is + display + sort + filter + selection, plausibly *not* heavy in-cell editing. +- **Stage 8 (Lists, Notebook, bulk-edit):** the heaviest consumer of **bulk-edit columns** + (`BulkEditBar.cs`, 7,685 lines), **checkbox-select columns**, and **multi-column sort/filter**. + This is where the table's most complex non-editing features (preview columns via fake flids, + check-all/uncheck-all, transduce/find-replace) are actually exercised. + +Implication for sequencing: the table's feature set should land **incrementally aligned to its +consumers** — Stage 4 needs *editing*; Stage 7 needs *sort/filter/select*; Stage 8 needs +*bulk-edit/checkbox*. They do **not** all need everything at once, which makes incremental shipping +both possible and desirable (§5). + +Cross-stage conflict to flag: the master table marks Stage 3 "Parallel? with 2" and Stage 7 "Depends +on 3,9". If Stage 3 ships as one monolithic "table+tree+editing+bulk-edit" epic, it becomes a +**serial bottleneck** in front of 4/7/8. Phasing it (table-read → table-edit → bulk-edit) lets +Stage 4 start as soon as table-edit lands and lets Stage 8's bulk-edit work overlap. + +--- + +## 5. Recommended plan changes + +1. **Re-title and re-scope the epic to "Shared editable virtualized *table* (+ indented-row tree + chrome)".** Demote the standalone "owned virtualized TREE" deliverable: the detail surface is a + flat 253-row list that already ships unvirtualized; deliver expander/indent row chrome on the + *same* owned list and only escalate to virtualization if the 253-slice budget fails. Keep the + 253-slice fixture as an exit gate on the *existing* `LexicalEditRegionView`, not as proof of a + new control. +2. **Ship the table in three sub-milestones, gated by consumer:** + - **3a Read table at scale** — extend `LexicalBrowseView`: column header sort affordance, + selection, keyboard nav, **custom AutomationPeer**, 10k-row scroll/expand budget proven on + production fixtures at 100%/150% DPI (closes most of the current read gaps; #18626 risk + retired or pivot fired). *Unblocks Stage 7 grid + Stage 8 display.* + - **3b Editable cells** — in-cell editing reusing `FwMultiWsTextField`/`FwChooserField`, routed + through `IEditSession`/`IUndoRedoCoordinator`; typing-latency budget via the existing harness. + *Unblocks Stage 4 (exemplar) — highest priority after 3a.* + - **3c Bulk-edit + checkbox + filter** — checkbox-select column, multi-column filter/sort, + bulk-edit preview columns (replacing the fake-flid `XMLViewsDataCache` mechanism with an + in-memory model). *Unblocks Stage 8.* Can overlap Track II. +3. **Make the spike measure scroll/expand on production fixtures, not just open-time realization + count.** Explicitly evaluate the #18626 condition and record fire/clear of the + `VirtualizingStackPanel` pivot trigger in the manifest with numbers. +4. **Promote "custom AutomationPeer for the virtualized table" to a first-class, separately-tracked + work item** with UIA2/FlaUI realized-window evidence as its gate (it is currently one sub-bullet; + it is the single most novel piece of engineering in the stage). +5. **Record two pivot-trigger resolutions in `seam-catalog.md` in the Stage-3 PR:** TreeDataGrid → + *confirmed closed* (FOSS archived 2025-10-13, editing behind Accelerate commercial license); + ItemsRepeater → *confirmed closed* (on deprecation path; not the substrate). +6. **Reuse, don't rebuild:** `IBrowseRowSource`/`BrowseRowList`, `FwOptionPicker`'s + keyboard/focus/virtualization, `FwAvaloniaDensity`, `TypingLatencyHarnessTests`, the Path-3 + bundle. The survey confirms none of these need re-creation. + +--- + +## 6. Open questions / risks + +- **Scroll smoothness at 10k rows is unmeasured.** The current test proves realization *count*, not + scroll/expand latency. Upstream #18626 is unresolved. **Highest technical risk.** If it fails, the + stage absorbs a fully-owned realization-window virtualizer — a materially larger build the master + risk register only obliquely covers. +- **Custom AutomationPeer effort is unestimated and unprecedented in-repo** (zero exist today). + Could be the long pole within the stage; UIA enumeration of de-realized rows is the hard part. +- **Editable-cell semantics vs. native RDE / fake-flid cache.** Replacing `XMLViewsDataCache`'s + fake-flid preview/edit-storage model (90000000-range tags) with a managed in-memory model is + underspecified; bulk-edit *preview* columns especially. Risk of silently dropping a legacy + capability — `EngineIsolationAuditTests` + a census of `BulkEditBar` features should gate it. +- **Is heavy in-cell editing even needed for Stage 7 concordance?** If concordance grids are + display+navigate only, 3b need not block 7 — worth confirming with the Stage 7 owner to refine the + dependency graph (currently `S3→S7` implies the whole control). +- **Column reorder / resize / persistence** (legacy `DhListView` drag-reorder) is not mentioned in + the Stage-3 detail; confirm whether it is in-scope or deferred to a consuming stage. + +--- + +## 7. Confidence + +**High** on the central judgments: (a) owning the table is correct and *increasingly* correct given +the TreeDataGrid FOSS archival + Accelerate licensing move; (b) the "tree" half is over-scoped +because the detail surface is a flat 253-row list already shipping unvirtualized and the unbounded +tree need is the *already-virtualized* chooser; (c) the stage should ship table-first in three +consumer-gated sub-milestones; (d) custom AutomationPeers and scroll-budget proof are the real, +currently-understated risks. All grounded in read code (`LexicalBrowseView.cs`, +`LexicalEditRegionView.cs`, `FwOptionPicker.cs`, `DataTreeTimingBaselines.json`, the test files) and +current Avalonia status (11.3.17 in-repo; #18626; TreeDataGrid archival). + +**Medium** on the precise editable-cell effort and the AutomationPeer effort — both lack in-repo +precedent, so their sizing is the spike's first job. **Medium** on whether Stage 7 truly needs 3b +(editing) vs only 3a — depends on a concordance-grid scope answer from the Stage 7 owner. + +--- + +### Sources (external) + +- Avalonia `VirtualizingStackPanel` scroll/GC issue (open): https://github.com/AvaloniaUI/Avalonia/issues/18626 +- TreeDataGrid → Accelerate licensing change + FOSS repo archival (2025-10): https://avaloniaui.net/blog/building-a-sustainable-future-for-avalonia , https://github.com/AvaloniaUI/Avalonia.Controls.TreeDataGrid +- ItemsRepeater deprecation trajectory: https://github.com/AvaloniaUI/Avalonia/discussions/16829 +- Avalonia performance / virtualization guidance: https://docs.avaloniaui.net/docs/app-development/performance +- Custom AutomationPeer / ItemContainerGenerator (containers not retained by generator): https://api-docs.avaloniaui.net/docs/T_Avalonia_Controls_Generators_ItemContainerGenerator diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-04-finish-lexical-entry.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-04-finish-lexical-entry.md new file mode 100644 index 0000000000..e643157a31 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-04-finish-lexical-entry.md @@ -0,0 +1,191 @@ +# Stage 4 Review — Finish the Lexical / Advanced Entry Surface (Exemplar) + +> Reviewer pass against the live branch `010-advanced-entry-view-phase-1-2`. +> Master plan: `openspec/changes/avalonia-migration-roadmap/complete-migration-program.md` §6 Stage 4 +> (lines 252–258). Repo state read from `openspec/changes/lexical-edit-avalonia-migration/tasks.md`, +> `region-manifest.md`, `Src/xWorks/FullEntryRegionComposer.cs`, `Src/Common/FwAvalonia/`. + +## 1. Scope assessment + +Stage 4 (plan §6, lines 252–258) names five close-out work items: +1. Tables/browse on the Stage-3 control (lexical-edit 7.x). +2. Full P0/P1 field parity beyond the first slice (custom fields, rich references, media/pronunciation). +3. 150% DPI + scroll/expand/typing-latency budgets (tasks 2.13, 7.7). +4. JSON view-definition serialization + override migrator + runtime-XML-disable (9.x). +5. Path-3 bundle completeness; region manifest fully green. + +Against the actual open checkboxes in `tasks.md`, this scope is **mostly accurate but partly stale +and partly over/under-stated.** The genuinely-open work, quoted from the file: + +- **7.7** `- [ ] 7.7 Add large-fixture performance budgets for open time, scroll/expand latency, typing + latency, realized control count, memory, and cache invalidation. (In progress … **remaining:** + scroll/expand, typing latency, realized-count, memory, and cache-invalidation lanes — region-manifest §5.4.)` +- **2.13** is still `- [ ]` for the same reason: `**Still open:** scroll/expand and typing-latency + scenarios, and 150% DPI numbers (need a non-headless scaled-display run)`. +- **9.2** `- [ ] 9.2 … **remaining:** the JSON serializer + override migrator from + canonical-view-definition-design.md steps 1–3.` +- **9.3** `- [ ] 9.3 … **remaining:** user override fixtures per override-fixtures.md.` +- **9.4** `- [ ] 9.4 Disable runtime XML for a gated migrated surface while retaining import/audit fallback.` +- **10.4 / 10.5** Graphite/native-rendering and browser/PDF default-path validation — both `- [ ]`. +- **10.8 / 10.10 / 10.12** per-manifest evidence, Path-3 lane completeness, product wiring review — all `- [ ]`. +- **6.13** GATE (multi-WS text foundation) is `- [ ]` but the note says the code+automated lanes have + LANDED; the only residual is the **foundation change's** 8.2 (realized-window manual RTL + Khmer) and + 10.3 (full CI). Those belong to `avalonia-multi-writing-system-text-foundation`, not this change. +- **18.10–18.13** PR-readiness / foundational follow-ups (IME end-to-end wiring, dual-projection + unification, full build/test run, wiring checklist). + +**Stale claim in Stage 4 scope — item 4 (JSON serializer).** The plan implies 9.x is unstarted. It is +not: `Src/Common/FwAvalonia/ViewDefinition/ViewDefinitionJsonSerializer.cs` already exists and is real +(canonical property order, `FormatVersion = 1`, `Serialize`/`Deserialize`), with +`BrowseAndCanonicalJsonTests.cs` exercising it. `architecture-patterns.md` §1 even cites it as canonical +code. So 9.2's *serializer core exists*; what is genuinely open is the **override migrator** (sparse +JSON patches keyed by `StableId`) and the **runtime-XML-disable switch** (9.4). Stage 4 should be +re-scoped to "finish 9.x" not "build 9.x". + +**Overstated — item 1 (tables/browse).** `- [x] 7.1` is checked: `LexicalBrowseView.cs` exists and +`LexicalBrowseViewTests` proves a 10k-row source realizes <100 rows. The *detail* surface (the entry +view) does not actually contain a browse table in normal LexEntry layouts — browse/XMLViews is the +Stage-3/Stage-7 surface. Item 1 as written ("tables/browse in the entry view") risks importing +Stage-3 scope. What is truly left here is wiring the **already-built** browse control onto the shared +Stage-3 control once Stage 3 lands, and proving it at scale — a thin integration, not a build. + +**Understated — item 2 (P0/P1 field parity).** This is *further along* than "beyond the first slice" +implies. `FullEntryRegionComposer.cs` (2524 lines) already walks the complete `LexEntry/Normal` layout: +custom fields render via plugin factory with explicit degradation (`RegionCustomFieldRenderingTests`), +reference vectors/atomic refs render (`EntryReferenceVectorTests`, `LexReferenceMultiSliceTests`, +`FullEntryRegionReferenceChooserTests`), ghost references (`GhostLexRefSliceTests`), pictures decode and +render (11.6), pronunciation writing systems are handled (`FullEntryRegionComposer.cs:2168`, +`:2336–2340`). Tasks 7.4 and 11.1–11.17 are all `- [x]`. The honest residual P0/P1 gaps are narrow and +already named in the manifest §6: **custom fields ✘ (9.x B1)**, **rich-TsString runs** (ride the landed +foundation), **StText multi-paragraph + ORC editing** (deferred to Stage 9 / task 8.11), and +**chooser write-back beyond morph type** (6.3 follow-on / 11.2). Stage 4 should cite the manifest §6 +parity table as its acceptance scope rather than the vague "custom fields, rich references, +media/pronunciation," most of which are already green. + +**Correctly scoped — items 3 & 5.** Perf budgets (scroll/expand/typing/150% DPI) and Path-3/manifest +greening are the real, accurately-described heart of Stage 4. + +## 2. Feasibility (repo-grounded) + +- **Perf budgets (7.7/2.13) — feasible, needs hardware.** The harness, baselines, and enforcement + mechanism already exist: open-time + refresh-after-edit are measured and committed + (`region-manifest.md` §5.1–5.3, `DataTreeTimingBaselines.json`, `DataTreeReshowTimingTests`). A + `TypingLatencyHarnessTests.cs` already exists in `FwAvaloniaTests`. The blocker is purely operational: + `region-manifest.md` §5.4 — *"150% DPI numbers need a non-headless run on a scaled display."* Headless + Skia cannot produce these; this needs a real scaled-display machine in the loop. **Risk:** low + technical, medium logistical (CI cannot self-serve 150% DPI). +- **JSON override migrator + runtime-XML-disable (9.2/9.4) — feasible, design exists.** The serializer + is built; `canonical-view-definition-design.md` defines the 5-step sequence and the sparse-patch + override model keyed by `ViewNode.StableId`. 9.5 (`- [x]`) already enumerated the 12-family XML blocker + register with measured prevalence (161 ``, 48 ghost attrs, 230 `if`, 473 menu/hotlink). The + open risk the blockers doc itself flags: *ghost/conditional/chooser schema must be reserved before the + canonical Layer-1 JSON freezes* — i.e. freezing the format prematurely is the trap. +- **Manifest greening (item 5) — the hard gate.** `region-manifest.md` §6 verdict is **"Default stays + Legacy"** with Layout/Validation/Accessibility/Performance rows **Partial**. Each Partial row has a + named owner: layout = full-tree semantic comparison vs legacy DataTree not yet run; validation = + severity/async lanes; accessibility = keyboard-traversal assistive smoke pends chooser-dialog work + (3.16/6.3); performance = §5.4. These are all in-reach but are the long tail. +- **6.13 dependency is effectively satisfied** for editing behavior (the note documents the foundation's + code landed); Stage 4 should not block on it except for the two foundation-owned residuals. + +## 3. Best practices for an exemplar others copy + +Stage 4's deliverable is not just "green" — it is the **template Stages 6/8 (mid-level) clone**. The +exemplar must therefore make the *reusable pattern* visible, not just the lexical-edit instance: + +1. **Resolve the dual-projection split (18.11) before declaring exemplar-complete.** Today there are two + projectors: the thin `LexicalEditRegionMapper` (FwAvalonia) and the full `FullEntryRegionComposer` + (xWorks, 2524 lines). `- [ ] 18.11` says unify them *"before a 2nd region reuses it, so header/indent/ + value-binding logic is not re-derived."* If Stage 6 copies a 2524-line composer, the exemplar has + taught duplication. This is the single most important exemplar-quality item and it is currently a + tracked-but-open follow-up. **Recommend pulling 18.11 into Stage 4's exit gate**, or explicitly into + Stage 1 (platform kit) where the scaffolding template lives. +2. **Promote `RegionViewingServices` to the documented copy-me contract.** Task 8.3 built it as an + as-built capability→owner map with a positive-assertion test (`RegionViewingServiceReplacementTests`). + 8.9 (`- [ ]`) defers turning it into an injected per-capability seam *"when a second region adopts it."* + The exemplar should at minimum document the map as the thing Stage 6 reads first. +3. **Custom-slice plugin burn-down must be demonstrated, not just possible.** `RegionEditorPlugins.cs` + + `LexemeEditorBurnDownTests.cs` exist. The exemplar should ship a worked "here is how you census a + surface's custom slices and add a plugin" path, since that is exactly what Stages 6/8 face. +4. **Keep the evidence-language discipline.** Per `parity-evidence.md` §3, the manifest §6 honestly says + "Partial" and lists which axes are unproven. The exemplar's value is partly that it *models honest + gating*. Do not flip rows to Pass to "finish the stage"; close the underlying lane. + +## 4. Interactions & dependencies + +- **Hard dependency on Stage 3 (tables) — real but narrow.** Plan §4 lists Stage 4 `Depends on 2,3`. + The browse control (`LexicalBrowseView`) is *already built standalone* on stock + `VirtualizingStackPanel`. Stage 3 builds the *shared, reusable* owned grid/tree. The dependency is: + Stage 4 must re-home its browse view onto the Stage-3 control and prove 10k-row/253-slice at 150% DPI + on it. If Stage 4 ships its own browse view as "done," it pre-empts Stage 3's single-owner mandate. + **Sequencing note:** the detail entry view itself does **not** need a browse table, so most of Stage 4 + (P0/P1 fields, perf, JSON, manifest) can proceed *in parallel with* Stage 3; only the + browse-on-shared-control integration truly blocks on Stage 3. +- **Template for Stages 6 & 8.** Plan §5 graph: `S4 --> S6`, `S4 --> S8`; §8 staffing makes Stage 4 the + "scale-up milestone" before Track-II head-count grows. What Stage 4 *must demonstrate* for them: + (a) compile-from-live-inventory (done, 4.10), (b) composer walks IR → region model → fenced edit + session (done), (c) plugin registry for custom slices (done, needs worked example), (d) Path-3 bundle + per scenario (7.8 done for first slice; 10.10 open for "every scenario"), (e) the manifest as + definition-of-done. The **unified projector (18.11)** is the missing piece that makes (a)+(b) copyable. +- **Stage 9 boundary — correct as drawn, with one caveat.** Rich-text-in-fields is correctly *not* in + Stage 4: multi-WS `ITsString` field editing is the landed foundation (6.13), while **StText + multi-paragraph and ORC rich-run editing are explicitly deferred to Stage 9 / task 8.11** (`- [ ]`). + This boundary is clean. Caveat: Stage 4 item 2 says "rich references" — ensure that means reference + *vectors/atomic* (done) and not rich-text, to avoid dragging Stage 9 scope in. +- **Stage 10 boundary (Graphite/PDF).** 10.4/10.5 (`- [ ]`) are default-path Graphite + browser/PDF + validation. The entry surface is classified *outside* the Gecko/PDF boundary (5.7, `gecko-pdf-audit.md`) + and engine isolation is enforced (`EngineIsolationAuditTests`). So 10.4/10.5 are **not** Stage 4 + blockers for the entry region per se — they block the *global default switch*, which is a + cross-stage/cutover concern, not "finish the entry surface." **This is a scope-attribution risk:** if + Stage 4's exit gate is read as "region enabled by default," it inherits Stage 10/13 work. + +## 5. Recommended plan changes + +1. **Re-word item 1** from "Tables/browse *in the entry view*" to "Re-home the already-built + `LexicalBrowseView` onto the Stage-3 shared control and prove 10k-row / 253-slice at 150% DPI" — and + mark it explicitly blocked-on-Stage-3, while letting the rest of Stage 4 run in parallel. +2. **Re-word item 4** from "JSON serialization + …" to "**Finish** 9.x: override migrator (sparse + `StableId` patches) + runtime-XML-disable (9.4) + override fixtures (9.3); serializer core already + exists." Add the `canonical-view-definition-design.md` caveat: reserve ghost/conditional/chooser + schema before freezing Layer-1 JSON. +3. **Re-anchor item 2** to the manifest §6 parity table: the open P0/P1 gaps are *custom fields (9.x B1), + chooser write-back beyond morph type (6.3 follow-on), and StText/ORC (→Stage 9)* — most field types + are already green. Drop the implication that references/pictures/pronunciation are unbuilt. +4. **Add an explicit exemplar-quality exit criterion:** unify the dual projector (18.11) and document + `RegionViewingServices` + plugin burn-down as the copy-me contract. Without this, Stages 6/8 inherit a + 2524-line composer to clone. Consider moving 18.11 to Stage 1 (kit) if it should precede *all* surface + hand-off. +5. **Disambiguate the exit gate.** State clearly whether Stage 4 done = "region manifest gates green + + ready to enable" vs. "enabled by default." The latter pulls in 10.4/10.5/10.8 (global default-path + validation) which are genuinely cross-stage. Recommend: Stage 4 = manifest §6 rows flip Partial→Pass + and the region is *enable-able*; the global default flip stays in cutover (Stage 13). +6. **Note the 6.13 cross-change residual:** Stage 4 cannot be "fully green" until the *foundation + change's* 8.2 (manual RTL + Khmer realized-window evidence) and 10.3 (full CI) land. Add that as an + explicit cross-change dependency, not a Stage-4 task. + +## 6. Open questions / risks + +- **150% DPI / scroll / typing latency need real hardware** — headless Skia cannot produce them + (`region-manifest.md` §5.4). Who owns the scaled-display run, and is it in CI or manual? This gates + three manifest rows and is the most likely schedule slip. +- **Full-tree semantic comparison vs legacy DataTree (layout gate, manifest §6 "Partial")** has not been + run for the composed view — only deterministic typed snapshots. This is the AI-hallucinated-parity + risk the program flags as High/High (plan §9). It must be a real legacy-vs-Avalonia diff, not a + self-snapshot. +- **Override migrator format-freeze risk:** freezing canonical Layer-1 JSON before ghost/conditional/ + chooser schema is reserved (per `xml-retirement-blockers.md`) bakes in a migration the customer + override fixtures (9.3) will then break against. +- **Dual-projection debt (18.11)** is the quiet exemplar killer — easy to defer, expensive once two more + surfaces have copied it. +- **Architecture-patterns.md drift:** the reference doc cites `ViewDefinitionJsonSerializer.cs` and + `LexicalBrowseView.cs` as canonical/done while tasks 9.2/9.4 read as open. Reconcile the doc and the + task list so the exemplar's own provenance is not self-contradictory. + +## 7. Confidence + +**High** on the open-task inventory and the boundary findings — these are quoted directly from +`tasks.md` and confirmed against real files (`ViewDefinitionJsonSerializer.cs`, `LexicalBrowseView.cs`, +`FullEntryRegionComposer.cs`, `region-manifest.md` §6). **Medium** on the exemplar-debt severity +(18.11) — I read the projector split from task text and file structure, not a line-by-line diff of both +projectors. **Medium** on the perf-hardware logistics — the gap is stated in the manifest but the +ownership/CI answer is not in-repo. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-05-global-dialogs-choosers.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-05-global-dialogs-choosers.md new file mode 100644 index 0000000000..f9d32d38e2 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-05-global-dialogs-choosers.md @@ -0,0 +1,222 @@ +# Stage 5 Review — Global dialogs & choosers (junior-friendly reservoir) + +**Reviewer:** Claude (Opus 4.8) · **Date:** 2026-06-15 · **Stage lead level:** Junior · **Concurrency:** 4–6 streams +**Scope under review:** `complete-migration-program.md` §4 row 5, §6 Stage 5, §11 decision 3; cross-checked +against `architecture-patterns.md` §7, `dialog-ownership.md`, `migration-checklist.md`, `parity-evidence.md`, +and the as-built repo state. + +--- + +## 1. Scope assessment + +The stage targets the largest single block of work in the program (~200 surfaces) and is the designated +junior-with-Claude reservoir. The "~200 dialogs" figure is repo-validated: counting `: Form` subclasses +(non-designer, non-test) across the five major UI dirs gives **~123 Forms** plus **~78 UserControls** +(FwCoreDlgs 33, LexText 45, xWorks 17, Common 20, FdoUi 8 Forms). FwCoreDlgs alone has **46 `*.Designer.cs`** +and **121 non-designer `.cs`**. So the headcount premise is real. + +But "junior-friendly + mechanical" is **only partly true**, and the stage as written conflates three very +different populations: + +- **Genuinely mechanical (junior-safe):** small modal/warning dialogs and simple choosers — + `FwChooserDlg.cs` (396 LOC), `FwFontDialog.cs` (461), `FwStylesModifiedDlg.cs` (58), + `AddNewVernLangWarningDlg`, `DeleteWritingSystemWarningDialog`, `MissingOldFieldWorksDlg`, + `MoveOrCopyFilesDlg`, the BackupRestore set (already MVP, separate Presenters + `IBackupProjectView`). +- **Mid-complexity, structured but large:** `FwProjPropertiesDlg.cs` (885), `ValidCharactersDlg.cs` (1,965), + `FwWritingSystemSetupDlg.cs` (738, has `FwWritingSystemSetupModel.cs`), `FwNewLangProject.cs` (359 + + `FwNewLangProjectModel.cs`, a multi-step wizard). These are **not** junior-mechanical. +- **Views-engine-coupled (NOT Stage-5 work at all):** `FwFindReplaceDlg.cs` (3,290 LOC, **14** + `IVwRootSite`/`SimpleRootSite` references — search ops manipulate live rootbox selection) and + `FwStylesDlg.cs` (1,335 LOC, hosts `IVwRootSite m_rootSite` for live style preview). These embed the + native Views engine that Stage 9 replaces. `PicturePropertiesDialog.cs` (620) has a light + `SimpleRootSite.ImageNotFoundX` reference (cosmetic, not blocking). + +**Verdict:** the scope is realistic in *headcount* but mis-labelled in *difficulty*. Find/Replace and the +Styles dialog are explicitly listed as "FwCoreDlgs first" junior targets in §6, yet they are the two most +Views-coupled dialogs in the project and cannot be migrated until Stage 9. They must be re-tiered. + +--- + +## 2. Feasibility (repo-grounded) + +### 2a. The modal-during-coexistence tension — RESOLVED, but the plan's wording invites error + +This is the headline question, and the architecture already answers it. Per `architecture-patterns.md` §7 +and `dialog-ownership.md` rules 1 and 4: + +- **No Avalonia modal windows exist during coexistence.** `Avalonia Window.ShowDialog` against a WinForms + owner is "not a supported combination on 11.x in this app" (`dialog-ownership.md` rule 4). +- Anything modal stays a **WinForms `Form`** owned by `Control.FindForm()` of the host + (`LexicalEditHostControl`), blocking the shared message loop (rule 1). + +So how can a dialog be "migrated to Avalonia" before the shell (Stage 11)? The resolution — which §6 states +tersely and which must be made unmissable — is the **content/chrome split**: + +> A Stage-5 "migration" replaces the dialog's **content** (the panel of fields/controls) with an Avalonia +> view, **hosted via `WinFormsAvaloniaControlHost` inside a thin WinForms `Form` shell** that still owns +> modality, `DialogResult`, and `ShowDialog`. The window is WinForms; the body is Avalonia. + +This is exactly the `LexicalEditHostControl` spine (decision 2 / principle 2) applied at dialog scope. It is +feasible **today** on the as-built 11.x stack and is **not blocked on Stage 11**. Stage 11 only removes the +WinForms `Form` wrapper and promotes the body to a real Avalonia modal window once the shell can own one. + +The risk is purely *documentation*: §6 says "new Avalonia dialog *content* is fine inside the host" in one +clause, but the surrounding prose ("dialogs migrated to Avalonia") and the §8 staffing model read as if whole +Avalonia dialogs ship. A junior will reasonably try `new Window().ShowDialog(owner)` and hit the unsupported +path. **Make the host-wrapped-content contract a hard, first-line rule of the Stage-5 epic, with a code +template.** + +### 2b. CommunityToolkit.Mvvm + compiled bindings — feasible but currently **counter to the as-built grain** + +Decision 3 mandates CommunityToolkit.Mvvm + compiled bindings (`x:CompileBindings`) for dialogs/wizards. The +repo today contradicts the prerequisites: + +- **CommunityToolkit.Mvvm is not referenced anywhere** (not in any `.csproj`, no `packages.config`). +- **There are zero `.axaml` files in `Src/`.** Every as-built Avalonia surface — `FwOptionPicker.cs`, + `FwFieldControls.cs`, `RegionMenuFlyout.cs`, `LexicalEditRegionView.cs` — is **hand-written C# code-behind**, + net48, no XAML. Compiled bindings (`x:CompileBindings`) and `AvaloniaUseCompiledBindingsByDefault` are a + **XAML feature**; they do not apply to code-behind UI. + +This is not a blocker but it is an unacknowledged pivot: Stage 5 is the program's **first XAML + MVVM-toolkit +adoption**. The decision is sound (statically-checked bindings catch AI binding hallucinations at build time; +source-generated `[ObservableProperty]`/`[RelayCommand]` lower the junior boilerplate burden), but it means +Stage 5 introduces a *new authoring style* the codebase has never used, on the back of juniors. That tooling +must be stood up and proven **in Stage 1**, not discovered in Stage 5. + +The reuse story is good: `FwMultiWsTextField` (in `FwFieldControls.cs`) and `FwOptionPicker` (`FwOptionPicker.cs`) +are self-contained Avalonia controls that drop into a dialog body wherever WS-aware text or a chooser field is +needed — exactly per decision 3. They are code-behind controls, so they compose into a XAML dialog as plain +elements; no friction there. + +### 2c. The "compiled bindings" gate needs a code-behind exception + +Because the proven owned controls are code-behind, the Stage-5 convention should be: **XAML + compiled +bindings for the dialog layout/view-model wiring; code-behind owned controls embedded as child elements.** A +blanket "all dialogs are XAML with compiled bindings" rule would either force a rewrite of the proven controls +or be quietly violated. State the hybrid explicitly. + +--- + +## 3. Best practices for high-volume, parallel, junior+AI dialog migration + +1. **Tier the backlog before hand-off** (see §5). Juniors get Tier-A (simple, Views-free, <~600 LOC). Mid + devs get Tier-B (structured, has/needs a model). Tier-C (Views-coupled: Find/Replace, Styles) leaves + Stage 5 entirely and is re-parented to Stage 9. +2. **Ship a dialog scaffolding generator in Stage 1** (the §6 Stage-1 "new-surface generator" must cover the + *dialog* shape, not just regions): WinForms `Form` host shell + `WinFormsAvaloniaControlHost` + `.axaml` + view + `CommunityToolkit.Mvvm` view-model + AutomationId scheme + parity-bundle test stub + + localization-lane stubs. Decision 3 says dialogs do **not** use the region/composer generator, so a + *separate* dialog template is required and is currently unscoped in Stage 1. +3. **One dialog per PR, whole files owned** (principle 10 / §9 merge-contention mitigation). New `.axaml` + + `.cs` view-model + host `Form` are net-new files → near-zero merge contention across 4–6 streams. Defer + the call-site swap (replacing `new FooDlg()` with the hosted variant) to the per-milestone integration + step (principle 11). +4. **Compiled bindings are the anti-hallucination gate.** Enforce `x:CompileBindings="True"` so every binding + is build-checked — this is the cheapest defense against the program's #1 risk (AI "hallucinated parity", + §9). A binding typo fails the build instead of silently rendering blank. +5. **Parity bundle is mandatory per dialog** (`migration-checklist.md` Phase 7, `parity-evidence.md`): + semantic + visual + workflow + perf, captured before refactor. The visual lane permits the Fluent restyle + (decision 4) — assert field semantics/density, not pixels. **Enforce the evidence vocabulary**: a checked + box whose note says *substitute/placeholder/skipped/future/partial* is a review blocker. With juniors this + is the load-bearing control; require an integration-owner spot-check. +6. **AutomationId day one, every control** (principle 8, checklist Phase 5/8): nonlocalized IDs, localized + Names. This is the prerequisite for the WinAppDriver/Appium workflow lane. +7. **Localization lanes**: field labels via the StringTable lane; product strings via `FwAvaloniaStrings.resx` + (already the live pattern — see modified `FwAvaloniaStrings.resx` on this branch). Run + `fieldworks-localization-review` per dialog. Do **not** hardcode strings (AGENTS.md hard rule). +8. **Focus-return contract is not free** (`dialog-ownership.md` rule 2): when a dialog is launched *from* an + Avalonia surface, record the focused Avalonia control, restore after close. Bake this into the host + template so juniors inherit it rather than reimplement it. +9. **Wizards are not "dialogs."** `FwNewLangProject` is a multi-step state machine (`FwNewLangProjectModel` + with `Load*SetupDelegate` callbacks). Treat wizards as a distinct, mid-level sub-track with their own + navigation/step view-model pattern. + +--- + +## 4. Interactions & dependencies + +- **Stage 1 (platform kit) — hard prerequisite, currently under-scoped for dialogs.** Stage 5 cannot start + until Stage 1 delivers (a) the *dialog* scaffolding generator and host-`Form` template, and (b) the + CommunityToolkit.Mvvm + compiled-bindings + first `.axaml` tooling — none of which exists in the repo today + and none of which the region generator covers. Recommend adding an explicit Stage-1 sub-task: "Stand up and + prove the MVVM-dialog authoring stack (CommunityToolkit.Mvvm package ref, compiled-bindings build setting, + one trivial XAML dialog hosted in a WinForms `Form` shell, green parity bundle)." This *is* the Stage-1 + validation gate ("a junior migrates one trivial dialog"), so it is mostly already implied — but the + XAML/MVVM-toolkit novelty must be called out, because the as-built code is 100% code-behind. +- **Stage 2 (host spine) — correct dependency.** §4 lists Stage 5 depending on Stage 2, which generalizes + `LexicalEditHostControl` → reusable host and pulls forward the dialog-ownership contract layer. The + dialog-body hosting reuses exactly this. Correct as drawn. +- **Stage 11 (shell/modality) — NOT a blocker for Stage 5; it is a *follow-on*.** This is the crux. Stage 5 + ships WinForms-`Form`-wrapped Avalonia bodies during coexistence; Stage 11 later removes the wrapper and + promotes them to native Avalonia modal windows once the Avalonia shell can own modality. So Stage 5 → Stage + 11 is a *forward* edge (Stage 11 finishes what Stage 5 starts), not a backward block. The §5 graph already + draws `S5 → S11` this way; keep it, but annotate the edge as "shell promotes hosted dialog bodies to native + Avalonia windows" so it is not misread as a blocking dependency. +- **Stage 9 (Views replacement) — newly-surfaced dependency for two named dialogs.** `FwFindReplaceDlg` and + `FwStylesDlg` embed `IVwRootSite`/`SimpleRootSite` and cannot be migrated until the managed document engine + exists. They are currently listed under Stage 5 ("Find/Replace", "Styles") — that is a **cross-stage + conflict** and must be corrected. +- **Stage 6/8 — downstream.** §4 has Stage 8 depending on Stage 5 (shared chooser/launcher infra). Correct: + the shared `FwOptionPicker`/flyout chooser pattern hardened in Stage 5 is reused by detail surfaces. + +--- + +## 5. Recommended plan changes + +1. **Re-tier the Stage-5 backlog and remove Views-coupled dialogs.** + - Move `FwFindReplaceDlg` and `FwStylesDlg` out of Stage 5; re-parent to **Stage 9** (or a Stage-9-gated + sub-epic). Update §6 Stage 5's "FwCoreDlgs first" list to drop "Find/Replace" and "Styles" and add a note + that those two are Views-engine-coupled and Stage-9-gated. + - Split the remaining list into **Tier A (junior)**, **Tier B (mid: wizard + large structured dialogs: + New-Project wizard, WS setup, Project properties, Valid-Characters)**. The §8 staffing model already + allows mid devs; reflect the Tier-B mid assignment in Stage 5 rather than calling the whole stage junior. + +2. **Make the host-wrapped-content contract the first rule of the Stage-5 epic**, with a one-paragraph code + template (WinForms `Form` host + `WinFormsAvaloniaControlHost` + Avalonia body view-model). Cite + `dialog-ownership.md` rule 4 inline so the "no Avalonia modal window" constraint is unmissable for juniors. + +3. **Add the dialog-authoring stack to Stage 1's deliverables and gate.** Explicitly: CommunityToolkit.Mvvm + package reference, compiled-bindings build setting, the first `.axaml` in the repo, and the dialog + scaffolding generator (distinct from the region generator). Note in §11 decision 3 that this is the + program's first XAML/MVVM-toolkit adoption (the as-built Avalonia work is all code-behind). + +4. **State the code-behind-control exception to "compiled bindings everywhere"**: dialog layout is XAML + + compiled bindings; proven owned controls (`FwMultiWsTextField`, `FwOptionPicker`) embed as code-behind + child elements. Otherwise the rule is either violated or forces rewriting proven controls. + +5. **Annotate the §5 `S5 → S11` edge** as "promote hosted dialog bodies to native Avalonia modal windows" + (forward/finishing edge, not a block), to kill the "is Stage 5 blocked by Stage 11?" misreading. + +6. **Reuse the BackupRestore MVP set as the worked example for the runbook.** It already has + Presenter/`IBackupProjectView` separation (`BackupProjectPresenter.cs`, `RestoreProjectPresenter.cs`) — + the cleanest existing target for a first junior conversion to MVVM, and a good "before" for the parity + bundle. + +--- + +## 6. Open questions & risks + +- **Q (tooling):** Does compiled-bindings + CommunityToolkit.Mvvm + a `.axaml` dialog build cleanly on **net48**? + The as-built FwAvalonia project is `net48` and Avalonia 11.3.17. Compiled bindings work on 11.x, but this + combination is unproven in this repo and must be de-risked in Stage 1 before any junior touches it. +- **Q (host shell ownership):** When a Stage-5 dialog is itself launched from *another* WinForms dialog (not + from the Avalonia surface), what owns the host `Form`? `dialog-ownership.md` assumes the launcher is the + Avalonia surface. The common WinForms→WinForms dialog chain needs an owner rule too. +- **Risk (hallucinated parity at volume):** 4–6 junior streams × ~150 Tier-A/B dialogs is the program's + largest exposure to the §9 #1 risk. Compiled bindings + mandatory parity bundle + evidence-vocabulary + enforcement + a dedicated integration owner are the mitigations; do not relax the evidence gate for "simple" + dialogs. +- **Risk (shared-control churn):** `FwOptionPicker`/`FwMultiWsTextField` will be exercised hard and may need + changes mid-stage; a control change touches many dialogs at once (merge contention + re-verification). Freeze + the owned-control API early or funnel changes through one owner. +- **Risk (mis-tiering recurs as new domain dialogs arrive):** §6 says "domain dialogs across xWorks/LexText/ + FdoUi as their owning areas migrate" — those areas (esp. LexText, 45 Forms) include more Views-coupled + dialogs. Each incoming domain dialog needs the same Views-coupling triage before junior assignment. + +## 7. Confidence + +**High** on the central resolution (modal-during-coexistence is solved by host-wrapped WinForms-`Form` + +Avalonia body; Stage 5 is **not** blocked by Stage 11), the repo facts (counts, LOC, Views coupling, absence +of XAML/MVVM-toolkit), and the Find/Replace + Styles mis-tiering. +**Medium** on exact effort tiering of the mid band and on the net48 compiled-bindings build (flagged as the +key Stage-1 de-risk). All claims are grounded in cited repo paths and the frozen architecture docs. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-06-lexicon-grammar-morphology.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-06-lexicon-grammar-morphology.md new file mode 100644 index 0000000000..b28dc58c2b --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-06-lexicon-grammar-morphology.md @@ -0,0 +1,248 @@ +# Stage 6 Review — Lexicon completion + Grammar/Morphology detail + +> Reviewer pass over **Stage 6** of `complete-migration-program.md`. +> Scope as written: Lexicon remaining slices/launchers (MSA, references, +> examples); Morphology (inflection features/classes, phonological +> environments, categories); Grammar detail editors via `FdoUi` (POS, +> inflection, phonological features); custom slice classes → plugin registry +> with burn-down tracking. Lead level: **Mid**. +> Grounding: repo inventory of `Src/LexText/Lexicon/`, +> `Src/LexText/LexTextControls/`, `Src/LexText/Morphology/`, `Src/FdoUi/`, +> `Src/Common/Controls/DetailControls/`, and the Stage-0 plugin/owned-control +> machinery in `Src/xWorks/` and `Src/Common/FwAvalonia/`. + +## 1. Scope assessment + +**Verdict: the epic as written is mis-bundled and under-sized in the wrong +dimension.** It groups three areas that look adjacent ("detail-view-heavy +grammar/lexicon") but have *different dependency profiles*: + +- **(A) Lexicon references + MSA/feature launchers** — genuinely reuse the + Stage-4 region/composer + plugin registry. Several are *already* lane- + classified and partially absorbed in Stage 0 (see §4). This is the part + that fits the "mid dev follows the exemplar" framing. +- **(B) Morphology rule/template/environment editors** — these are **not + detail-field editors at all**. They are bespoke Views-based document + surfaces (affix templates, phonological rule formulas, environment strings, + interlinear). They belong with Stage 7/9, not here. +- **(C) `FdoUi` grammar editors (BulkPos/InflectionClass/InflectionFeature/ + PhonologicalFeature)** — these are **bulk-edit-bar controls living in the + XMLViews browse surface, not the DataTree/region detail surface**. They + have no place in a "detail" epic; they depend on Stage 3 (the editable grid) + and the browse-bulk-edit machinery, not on the region composer. + +So the epic bundles work that splits cleanly across **three different +substrates** (region composer, document engine, browse/bulk-edit). The "this +is too much for one epic" instinct in the prompt is correct, but the right cut +is **by substrate/dependency, not by headcount**. Recommend splitting (§5). + +**One scope nuance the prompt got right:** these *are* detail-heavy, but a +large fraction does **not** reuse the Stage-4 pattern. The honest split is +roughly: a third reuses region/composer + plugins cleanly; a third is bulk- +edit (browse) work; a third is Views-based and Stage-9-blocked. + +## 2. Feasibility (repo-grounded) + +### Custom slice/launcher census (counts) + +| Area | Path | Custom slice/launcher classes | +| --- | --- | --- | +| Lexicon | `Src/LexText/Lexicon/` | ~32 (21 slices + ~14 launchers) | +| Morphology | `Src/LexText/Morphology/` | 14 | +| FdoUi grammar editors | `Src/FdoUi/` | 4 (+1 sibling in Lexicon) | + +The shared **base** classes live in +`Src/Common/Controls/DetailControls/` (`Slice.cs`, `ViewSlice.cs`, +`ViewPropertySlice.cs`, `FieldSlice.cs`, `StringSlice.cs`, +`ButtonLauncher.cs`, `ReferenceLauncher.cs`, `AtomicReferenceLauncher.cs`, +`VectorReferenceLauncher.cs`, plus `CustomAtomicReferenceSlice` / +`CustomReferenceVectorSlice`). DetailControls is frozen per the master plan — +these bases are deleted at cutover, never extracted. + +### The dominant feasibility fact: native Views reaches deep into the bases + +`ViewSlice` (`Src/Common/Controls/DetailControls/ViewSlice.cs`) takes a +`SimpleRootSite` in its constructor and exposes it as `RootSite`. +`ViewPropertySlice` and `StringSlice` derive from it → **transitively +Views-based**. Critically, *even the reference launchers are Views-based at +the base*: `AtomicReferenceLauncher` embeds `AtomicReferenceView` and +`VectorReferenceLauncher` embeds `VectorReferenceView`, both `RootSite`-backed. + +This matters because it means the **legacy** reference slices render through +Views — but Stage 0 already proved they do **not** need the Views engine on +the Avalonia path: the region composer renders reference vectors with the +owned `FwReferenceVectorField` +(`Src/Common/FwAvalonia/Region/FwFieldControls.cs`, `RegionFieldKind.ReferenceVector`), +not by hosting a RootSite. So *reference-launcher* Views usage is **not** a +Stage-9 dependency — it is replaced by owned controls. Confirmed by Stage 0's +burn-down classifying `EntrySequenceReferenceSlice`, `GhostLexRefSlice`, and +`LexReferenceMultiSlice` as **LaneAbsorbed** (D3) into the composer. + +### What *is* genuinely Stage-9-blocked + +These embed a Views **document** surface (a `RootSiteControl`/`XmlView`/ +`PatternView` rendering rich structured content), which the region's owned +controls do **not** cover: + +- Lexicon: `ReversalIndexEntrySlice` (already deferred to **gate 6.13** in the + Stage-0 burn-down), `MSADlgLauncherSlice` / `MsaInflectionFeatureListDlgLauncherSlice` + / `PhonologicalFeatureListDlgLauncherSlice` — *but* these last three are + already handled as **LauncherRouted (D4)** plugins (value-preview + dialog), + so their Views preview is replaced by a text value, not by a hosted engine. +- Morphology: `InflAffixTemplateSlice` (`XmlView`), `InterlinearSlice` + (`AnalysisInterlinearRs : RootSite`), `RuleFormulaSlice` + + `AffixRuleFormulaSlice`/`MetaRuleFormulaSlice`/`RegRuleFormulaSlice` + (`PatternView : RootSiteControl`), `PhEnvStrRepresentationSlice` + (`StringRepSliceView : RootSiteControl` with a custom VC and inline + environment validation). These are **4 distinct document-editor families, + all hard-blocked on Stage 9** — they are not field launchers and have no + owned-control equivalent today. + +### What reuses Stage 4 cleanly (low risk, mid-appropriate) + +- The **lexical-reference family** (~9 slices: `LexReferenceMultiSlice` parent + + Pair/Collection/Sequence/Unidirectional/TreeBranches/TreeRoot variants + + `EntrySequenceReferenceSlice`) — vector/atomic reference editing the + composer + `FwReferenceVectorField` + `FwOptionPicker` already model. The + one wrinkle: `LexReferenceMultiSlice` **dynamically generates child slices** + by reference type and owns add/delete/edit context menus — the composer must + reproduce that fan-out, which is real work but is region-pattern work. +- The **MSA / inflection-feature / phonological-feature launchers** — already + routed via `LauncherRegionPlugin`/`DialogLauncherPlugins` + (`Src/xWorks/RegionEditorPlugins.cs`, `DialogLauncherPlugins.cs`). Stage 6 + inherits a working pattern; remaining work is the missing + `FwDialogLauncherField` owned control (referenced but **not yet a + first-class `RegionFieldKind`** — see §3) and the actual chooser dialogs. +- `BasicIPASymbolSlice` (`StringSlice` + auto-population) — a text field with a + side effect; fits an owned text control + edit-session hook. + +### FdoUi bulk editors — feasible but wrong-substrate + +`BulkPosEditor`/`BulkPosEditorBase`, `InflectionClassEditor`, +`InflectionFeatureEditor`, `PhonologicalFeatureEditor` (and +`BulkReversalEntryPosEditor` in Lexicon) all implement `IBulkEditSpecControl` +and embed a WinForms `TreeCombo` (no Views, no chooser dialog — inline popup +tree). **No Stage-9 dependency.** But they are bulk-edit-bar controls on the +**browse** surface, so they depend on Stage 3 (editable grid) + a migrated +bulk-edit bar, not the region composer. They should not gate or live inside a +"detail" epic. + +## 3. Best practices + +- **Census-first + burn-down is already a hard gate.** Stage 0 ships + `LexemeEditorBurnDownTests` (`Src/xWorks/xWorksTests/`) which parses + `class=`/`assemblyPath=` from the layout XML and forces *every* custom slice + into exactly one lane (PluginRouted / CompanionDesignated / LauncherRouted / + ExplicitlyDeferred / LaneAbsorbed). **Stage 6 must extend this same census to + the Morphology and Grammar layout files** (not invent new tracking). The + prompt's "burn-down tracking" line should point at this existing test. +- **Reuse the plugin contract verbatim.** `IRegionEditorPlugin` + (`LegacyClassName` + `BuildControl(RegionEditorBuildContext)`) and + `RegionEditorPluginRegistry.Register/Resolve/RegisteredClassNames` are the + add-a-slice contract (architecture-patterns.md §5). New Stage-6 plugins key + off legacy class identity → zero layout edits. +- **Promote `FwDialogLauncherField` to a real owned control / `RegionFieldKind`.** + Today it is plugin-delivered (`DialogLauncherPlugins.cs`) and *not* in the + Stage-0 `RegionFieldKind` vocabulary. Stage 6 multiplies launcher usage + (MSA, features, allomorph/MSA ad-hoc links). This is a Stage-1/Stage-4 + platform gap that Stage 6 will hit immediately — flag it as a prerequisite. +- **Explicit "unsupported" rows over silent fallback** for the Stage-9-blocked + document editors — never host a hidden RootSite (active-host contract). +- Per-surface Definition of Done (§7 of the plan) applies unchanged: + 100%/150% DPI, Path-3 bundle, `EngineIsolationAuditTests` green (which will + *catch* any accidental Views/`RootSite` symbol in a migrated row). + +## 4. Interactions & dependencies + +- **Hard dep on Stage 4 (exemplar) and the plugin registry** — correct as + drawn (`S4 → S6`). The reference-vector and launcher lanes are literally the + Stage-0/Stage-4 machinery. +- **Undeclared hard dep on Stage 9.** The plan shows Stage 6 depending only on + Stage 4, but the Morphology rule/template/environment/interlinear editors + (4 families) and Lexicon `ReversalIndexEntrySlice` are Views-document + surfaces with **no owned-control path** — they are blocked on the Stage-9 + managed document engine exactly like Stage 7. The current graph + (`S9 → S7` only) **misses `S9 → S6`**. This is the single biggest plan + error for this stage. +- **Undeclared dep on Stage 3 + browse-bulk-edit.** The `FdoUi` editors are + bulk-edit-bar controls; they need the Stage-3 editable grid and a migrated + bulk-edit bar. No `S3 → S6` edge exists today. +- **Overlap with Stage 5 (dialogs).** Confirmed and significant: the MSA, + feature, phonological-feature, and ad-hoc-coprohib launchers all *open + dialogs* (`MsaCreatorDlg`, `PhonologicalFeatureChooserDlg`, + `MsaInflectionFeatureListDlg`, `LinkAllomorphDlg`, `LinkMSADlg`, + `LinkEntryOrSenseDlg`). The launcher *row* is Stage-6 region work; the + *dialog content* is Stage-5 work. These must be sequenced so the dialog + exists (or stays a WinForms-owned modal per `dialog-ownership.md`) before the + launcher row claims parity. Add an explicit `S5 → S6` (launcher-dialog) + dependency. +- **Within-stage dep:** `CustomReferenceVectorSlice`/`CustomAtomicReferenceSlice` + bases are shared by both Lexicon refs and Morphology ad-hoc-coprohib slices — + one composer mapping serves both. + +## 5. Recommended plan changes + +1. **Split Stage 6 by substrate into three deliverables:** + - **6a — Lexicon detail completion (mid, region/composer):** lexical + references (the ~9-slice family incl. `LexReferenceMultiSlice` fan-out), + MSA + inflection-feature + phonological-feature launcher rows, examples, + `BasicIPASymbolSlice`. Depends on Stage 4 + Stage 5 (launcher dialogs). + - **6b — Morphology + grammar document editors (senior, Stage-9-blocked):** + affix templates, rule formulas (×4), phonological environment strings, + interlinear slice, reversal-index entry. **Re-parent under / sequence + after Stage 9**; this is effectively Stage-7-class work and should not be + "mid." + - **6c — Grammar bulk editors (mid, browse/bulk-edit):** the four `FdoUi` + `IBulkEditSpecControl` editors + `BulkReversalEntryPosEditor`. **Depends + on Stage 3** and the bulk-edit-bar migration; move to Stage 8 (browse/ + bulk-edit reservoir) or make it the first browse-bulk-edit slice. +2. **Add missing dependency edges to §5 graph:** `S9 → S6` (document editors), + `S3 → S6` (bulk editors), `S5 → S6` (launcher dialogs). +3. **Down-grade lead level** for the document-editor portion from Mid to + Senior (it is Views-engine work). +4. **Make "burn-down tracking" concrete:** extend + `LexemeEditorBurnDownTests` census to the Grammar/Morphology layout XML so + every Morphology/FdoUi custom class is lane-classified before work starts. +5. **Add a platform prerequisite:** promote `FwDialogLauncherField` to a + first-class `RegionFieldKind`/owned control in Stage 1 or Stage 4 before + Stage 6 scales launcher rows. +6. **Correct the scope sentence** in the plan: replace "Detail-view-heavy areas + that reuse region/composer + plugin registry directly" — that is true only + for ~1/3 of the listed work. + +## 6. Open questions / risks + +- **Stage-9 timing dominates 6b.** If Stage 9 slips, the Morphology rule/ + template/environment/interlinear editors and reversal-index entry cannot + reach parity — they can only render explicit "unsupported" rows. Risk: the + epic looks "mostly done" (references migrated) while the linguistically + critical editors are blocked. Mitigation: split per §5 so 6a can close + independently and 6b tracks Stage 9 honestly. +- **`LexReferenceMultiSlice` dynamic child-slice generation** is the highest- + complexity *region-pattern* item: the composer must reproduce per-reference- + type slice fan-out + add/delete/edit context menus. Needs a characterization + baseline before refactor. +- **Phonological-environment validation** (`PhEnvStrRepresentationSlice` does + inline slash/bar/error checking) — does this move to the `IValidationService` + seam, or stay inside the document editor? Decide during the Stage-9 spike. +- **Bulk-editor "fill-in-the-blanks" semantics** (`InflectionFeatureEditor` + pattern-matches values from the current sense) and + `PhonologicalFeatureEditor`'s cross-control `EnableTargetFeatureCombo` event + are non-trivial bulk-edit behaviors to characterize before grid migration. +- **TreeCombo replacement.** All four FdoUi editors depend on WinForms + `TreeCombo`+`PopupTreeManager`; the Avalonia equivalent (a bounded popup-tree + combo) is not yet in the owned-control set — confirm `FwOptionPicker` covers + the popup-tree case or schedule a new owned control. + +## 7. Confidence + +**High** on the central findings: the custom-class census and base-class +Views-dependency facts are read directly from the repo; the bulk editors are +unambiguously browse/`IBulkEditSpecControl` controls; the Stage-0 burn-down +and plugin contract are confirmed in code. **High** that the epic is +mis-bundled across three substrates and that `S9 → S6` / `S3 → S6` edges are +missing. **Medium** on exact effort sizing of `LexReferenceMultiSlice` fan-out +and on whether every "examples" slice is already lane-absorbed (examples were +not separately enumerated in the inventory and may overlap the +already-migrated Stage-4 entry surface). **Medium** on whether the +feature-launcher dialogs are scheduled in Stage 5 or implicitly here. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-07-interlinear-discourse.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-07-interlinear-discourse.md new file mode 100644 index 0000000000..c007716829 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-07-interlinear-discourse.md @@ -0,0 +1,179 @@ +# Stage 7 Review — Texts & Words / Interlinear + Discourse + +> Reviewer pass against the live branch `010-advanced-entry-view-phase-1-2`. +> Master plan: `openspec/changes/avalonia-migration-roadmap/complete-migration-program.md` §6 Stage 7 +> (lines 277–283) and Stage 9 (lines 292–306). Repo evidence read directly from +> `Src/LexText/Interlinear/`, `Src/LexText/Discourse/`, `Src/Common/SimpleRootSite/`, +> and `Src/Common/FwUtils/CachePair.cs`. Architecture refs: +> `.claude/skills/fieldworks-winforms-to-avalonia-migration/references/{architecture-patterns,parity-evidence,migration-checklist}.md`. + +## 1. Scope assessment + +Stage 7 (plan lines 277–283) bundles five very different surfaces under one senior epic: + +1. Interlinear doc + sandbox (`Src/LexText/Interlinear`, ~98 files) — word/morpheme breakdown, glossing, POS. +2. Concordance + raw-text + statistics views. +3. Constituent charts (`Src/LexText/Discourse`) — chart body, logic, export. +4. Import wizards (SFM, LinguaLinks) — flagged as splittable to Stage-5-style hand-off. +5. Stated dependencies: Stage 9 (managed document engine) + Stage 3 (shared tables). + +**This is the right grouping by *domain* but wrong as a single deliverable.** The five items differ +by 1–2 orders of magnitude in Views coupling. Measured from the repo: + +| Surface | Lead lines | Views coupling | Migratability | +| --- | --- | --- | --- | +| **Sandbox / FocusBox** (`SandboxBase.cs` 4973, `.ComboHandlers.cs` 3450, `.MorphemeBreaker.cs` 1152, `.SandboxVc.cs` 797) | ~14k | **Extreme** — `RootSite` base, secondary `VwCacheDa` cache, fragment VC, `IVwSelection` hit-test editing | Hardest surface in the whole program | +| **Interlinear doc** (`InterlinVc.cs` 3376, `InterlinDocForAnalysis.cs` 2606, `InterlinDocRootSiteBase.cs` 1262) | ~13k | **Extreme** — 287 `IVwEnv` calls in `InterlinVc` alone, 70+ `kfrag*` constants, 8 VC overrides | Very hard | +| **Constituent chart** (`ConstituentChartLogic.cs` 5459, `ConstChartVc.cs` 727, `ConstChartBody.cs` 513, `MakeCellsMethod.cs` 570) | ~11k | **Logic: none; rendering: high** — `ConstituentChartLogic` has ~zero Views refs; rendering concentrated in ~1,800 lines | Logic ports as-is; rendering medium | +| **Concordance / ComplexConc / Statistics** (`ConcordanceControl.cs` 1846, `ComplexConcControl.cs` 812, `StatisticsView.cs` 301) | ~3k | **Low** — `UserControl` bases, 0 `IVwEnv`/`IVwRootBox` in the host controls (the *preview* pane reuses `InterlinVc`) | Mostly Stage-5-style | +| **Import wizards** (`InterlinearSfmImportWizard`, `LinguaLinksImport`, `BIRDInterlinearImporter`, `WordsSfmImportWizard`) | ~several k | **None** — WinForms wizard dialogs + non-UI importers | Junior/Stage-5 dialogs | + +**Recommendation: split Stage 7 into four epics** (details in §5). One epic this size, spanning a +trivial wizard dialog and the single most Views-entangled control in FieldWorks, cannot be sized, +staffed, or gated coherently. + +## 2. Feasibility (repo-grounded) + +### 2a. Sandbox is the deepest Views construction in the codebase — and Stage 9 does not describe it + +`SandboxBase : RootSite` (`Src/LexText/Interlinear/SandboxBase.cs:34`). It is **not** a document/StText +editor. It is an interactive analysis-builder backed by a private in-memory data access: + +- `protected CachePair m_caches` (`SandboxBase.cs:146`); `m_caches.CreateSecCache()` (`:993`) creates a + `VwCacheDaClass` (`Src/Common/FwUtils/CachePair.cs:195–202`) with a bidirectional HVO map between + fake "SbWord/SbMorph" objects and real LCModel objects. `IVwCacheDa cda = (IVwCacheDa)m_caches.DataAccess` + (`SandboxBase.cs:1112`). `MakeRoot` binds it: `m_rootb.DataAccess = m_caches.DataAccess; + m_rootb.SetRootObject(kSbWord, m_vc, SandboxVc.kfragBundle, m_stylesheet)` (`SandboxBase.cs:~4065–4074`). +- Editing is driven by **selection hit-testing**: `ShowComboForSelection(IVwSelection …)`, `ScanForIcon`, + `FindNearestSelectionType`, and `m_rootb.MakeSelAt(pt.X, pt.Y, …)` (`SandboxBase.cs:2451–2624, ~4400`). + Re-render is `RootBox.Reconstruct()` after `m_caches.DataAccess.PropChanged(...)` (multiple sites). +- `MorphemeBreaker` mutates the secondary cache directly: `m_cda = (IVwCacheDa)m_sda` (`SandboxBase.MorphemeBreaker.cs:53–65`). +- `SandboxVc.Display(IVwEnv vwenv, int hvo, int frag)` (`SandboxBase.SandboxVc.cs:292`) is a fragment + switch (~8 cases) with 100+ `IVwEnv` calls. + +None of this maps onto Stage 9's stated deliverables (lines 300–303): "multi-paragraph `StText` +editing, … managed selection/caret model replacing `VwSelection`, structured document model replacing +`VwRootBox`/box hierarchy." The Sandbox needs a **secondary in-memory presentation cache + decorator +model**, **icon/picture-anchored hit-test editing**, and a **combo/flyout editing harness** — none of +which is named in Stage 9. + +### 2b. Interlinear doc is a second, distinct specialized Views construction + +`InterlinVc : FwBaseVc` with `Display(IVwEnv)` at `InterlinVc.cs:604`; 287 `IVwEnv` invocations, 70+ +`kfrag*` fragments, `OpenMappedPara`/`AddObjVecItems` interleaving of word/morph/gloss/POS lines into +aligned columns. Hosts (`InterlinDocForAnalysis`, `InterlinDocRootSiteBase`, `InterlinTaggingChild`, +`InterlinPrintView`, `RawTextPane`, `TitleContentsPane`, the Discourse `InterlinRibbon`) all derive +from `RootSite`. The aligned word/morpheme grid is a *layout idiom* (multiple WS rows under one +vernacular word, column alignment across the paragraph) that ordinary `TextLayout`/StText editing does +not produce. `InterlinViewDataCache` and the per-pane decorators add another SDA-decorator layer. + +### 2c. Discourse splits cleanly — favorable + +`ConstituentChartLogic.cs` (5459 lines = ~49% of Discourse) is **pure domain logic** — drag/move/merge, +`MoveToColumn`, `MakeWordGroup`, `InsertRow`, `ToggleMissingMarker` — with essentially no `IVwEnv`/ +`IVwRootBox`/`IVwSelection` references (the few hits are comments/event-fire). It can port as-is. +Rendering is confined to `ConstChartVc` (`Display(IVwEnv)` at ~`:225`), `ConstChartBody : RootSite`, +`MakeCellsMethod` (`IVwEnv` table-cell emission), and `ChartRowEnvDecorator : IVwEnv` (RTL buffering — +deletable, Avalonia handles `FlowDirection`). `DiscourseExporter : CollectorEnv` is a view-walk export +(reuses `ConstChartVc.Display`); it should be rewritten as a direct domain→XML exporter to drop the +Views dependency. Net: the chart is a **table** with portable logic, and is the strongest candidate to +ride **Stage 3** (shared editable grid) rather than the Stage 9 document engine. + +### 2d. Concordance / statistics / import are low-coupling + +`ConcordanceControl`, `ComplexConcControl`, `ConcordanceControlBase`, `StatisticsView` are `UserControl` +hosts with no `IVwEnv`/`IVwRootBox` in the host itself (the result/preview pane re-hosts `InterlinVc`). +These plus the SFM/LinguaLinks/BIRD import wizards are ordinary WinForms dialogs and non-UI importers — +Stage-5-class work that does not need Stage 9 at all. + +## 3. Best practices for migrating complex custom-rendered editing surfaces + +1. **Spike the Sandbox as its own Stage 9 sub-spike, before committing the engine API.** It exercises + secondary-cache editing + hit-test combos, which the StText spike (plan line 297) will not surface. + If the managed engine API is frozen without it, Stage 7 inherits an engine that cannot express the + Sandbox and stalls late. +2. **Replace the secondary `VwCacheDa` with a managed presentation/decorator model**, not a port of + `IVwCacheDa`. The architecture already prefers projected presentation models over LCModel/COM + surfaces (architecture-patterns §2). The Sandbox's SbWord/SbMorph HVO-map is exactly an off-thread + presentation model with an edit-context seam. +3. **Separate logic from rendering first, prove with characterization tests.** Discourse already did + this (`ConstituentChartLogic`); do the equivalent extraction for the Sandbox (morpheme-break, + approve-and-move, choose-analysis) and Interlinear line-choice logic so the hard logic is unit-tested + independent of the renderer, per checklist Phase 2. +4. **Map the interlinear aligned grid to the Stage-3 owned table / Stage-9 layout primitives explicitly.** + It is neither a plain table nor a plain paragraph; record which primitive owns column alignment as a + pivot trigger in `seam-catalog.md`. +5. **Capture legacy timing baselines on the largest texts first.** `InterlinVc` uses lazy/`AddLazyVecItems` + rendering precisely because texts are large; a non-virtualized managed reimplementation will regress. +6. **Engine-isolation audit (`parity-evidence.md` §4) will flag every one of these surfaces today** + (`IVwRootBox`, `IVwEnv`, `IVwSelection`, `RootSiteControl`). That is expected; the gate is that the + *migrated* surface is clean. + +## 4. Interactions & dependencies + +- **Stage 9 (critical) — currently under-scoped for Stage 7.** Stage 9's deliverables (lines 300–303) + cover multi-paragraph `StText` + selection/caret + structured doc model. They do **not** name (a) the + Sandbox secondary-cache/decorator editing model, (b) icon-anchored hit-test combo editing, or (c) the + interlinear aligned word/morpheme/gloss grid layout. RawTextPane (plain `StText`) is the only Stage-7 + surface Stage 9 as-written clearly covers. **This is the headline cross-stage conflict.** +- **Stage 3 (tables) — correctly relevant, and more so than the plan implies.** The constituent chart + and the concordance results grid are table surfaces; the chart in particular should be built on the + Stage-3 owned virtualized grid, not the Stage-9 document engine. The plan lists Stage 3 as a Stage-7 + dependency (line 129) but the prose (line 283) emphasizes Stage 9; rebalance toward Stage 3 for the + chart + concordance. +- **Stage 5 (dialogs).** The plan already allows splitting import wizards to Stage 5 (line 282). Concur, + and extend: concordance/statistics host UI are also Stage-5-class. Per decision §11.3, the import + wizards should use MVVM + compiled bindings (no IR/region machinery — they have no XML layout). +- **Stage 6 / FdoUi.** Interlinear info-pane and the analysis choosers reuse `FwOptionPicker`-style + field controls already built in Stage 0; reuse, do not rebuild. + +## 5. Recommended plan changes + +1. **Split Stage 7 into four epics** with distinct leads/dependencies: + - **7A — Interlinear doc + Sandbox/FocusBox (senior; hard-blocks on extended Stage 9).** The long pole + of the surface track. + - **7B — Constituent chart (mid/senior; depends on Stage 3, light Stage 9).** Port + `ConstituentChartLogic` as-is; rebuild rendering on the Stage-3 grid; rewrite `DiscourseExporter` as + direct domain→XML. + - **7C — Concordance + ComplexConc + Statistics + raw-text host (mid; depends on Stage 3 + 9 only for + the embedded interlinear preview).** + - **7D — Import wizards: SFM / LinguaLinks / BIRD / Words-SFM (junior; Stage 5 pattern; MVVM).** +2. **Expand Stage 9 scope (lines 292–306) to explicitly name the interlinear/sandbox needs**, or add a + named Stage 9 sub-spike: (a) secondary in-memory presentation cache + decorator editing model + replacing `CachePair`/`VwCacheDa`; (b) picture/icon-anchored hit-test editing replacing + `IVwSelection` scan + `MakeSelAt`; (c) aligned multi-line word/morpheme/gloss grid layout. Without + this, Stage 9's "Views replacement" claim is incomplete and Stage 7A is mis-gated. +3. **Re-tag the Stage-7 row dependency** in the §4 table from "3,9" to per-sub-epic dependencies, and add + a risk-register line: "Stage 9 scoped to StText but Interlinear/Sandbox need specialized constructs — + verify in the Stage 9 spike." +4. **Move 7C/7D off the "depends on 9" critical path** so concordance/statistics/import can ship in + parallel with the engine work rather than waiting on it. +5. **Add a Stage-7A explicit dependency on the Stage 9 spike output** (not just Stage 9 completion): + freeze no engine API until the Sandbox spike runs. + +## 6. Open questions / risks + +- **Does the Stage 9 spike (line 297) include the Sandbox?** As written it lists bidi/IME/CJK/custom-WS/ + multi-paragraph/structured selection — not secondary-cache hit-test editing. If "no," Stage 7A is at + high risk of discovering a missing engine capability late. **(Top risk.)** +- **Aligned-grid layout ownership:** Stage 3 grid vs. Stage 9 layout — undecided. Interlinear column + alignment crosses both; needs an explicit decision/pivot record. +- **Tagging / ComplexConcordance pattern VCs** (`InterlinTaggingVc`, `ComplexConcPatternVc` — 121 `IVwEnv` + calls) are extra specialized VCs not separately budgeted; fold into 7A/7C scope explicitly. +- **Performance on large corpora:** lazy `AddObjVecItems`/`AddLazyVecItems` in `InterlinVc` implies real + virtualization is mandatory; a naive managed port will regress. Baseline on the largest available text. +- **Print views** (`InterlinPrintView`, chart print/export) overlap Stage 10 (print/preview); confirm + ownership to avoid a gap. + +## 7. Confidence + +**Code-coupling characterization: High.** Base classes, `Display(IVwEnv)` signatures, the secondary +`VwCacheDa` cache, hit-test combo editing, and the `ConstituentChartLogic` separation are all confirmed +by direct file/line evidence cited above. + +**Stage-9-coverage gap conclusion: High.** Stage 9's written deliverables (lines 300–303) demonstrably +do not name the Sandbox/interlinear-specific constructs the repo shows are load-bearing. + +**Effort sizing & exact split boundaries: Medium.** Directionally firm (Sandbox/Interlinear are the long +pole; chart logic is free; concordance/import are Stage-5-class), but precise epic boundaries and the +grid-vs-engine layout decision should be confirmed during the Stage 9 spike. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-08-notebook-lists-dictconfig.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-08-notebook-lists-dictconfig.md new file mode 100644 index 0000000000..fddf3e6096 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-08-notebook-lists-dictconfig.md @@ -0,0 +1,204 @@ +# Stage 8 Review — Notebook, Lists, Dictionary-config UI, Remaining Tools + +> Reviewer pass against the live branch `010-advanced-entry-view-phase-1-2`. +> Master plan: `openspec/changes/avalonia-migration-roadmap/complete-migration-program.md` §6 Stage 8 +> (lines 285–288), §4 table (line 130), §5 graph (lines 159, 180–182). Repo state read from +> `Src/xWorks/` (DictionaryConfiguration* family, DictionaryDetailsView/, RecordEditView/BrowseView/DocView), +> `Src/Common/Controls/XMLViews/BulkEditBar.cs`, `Src/FwCoreDlgs/IUtility.cs`+`UtilityDlg.cs`, +> `DistFiles/Language Explorer/Configuration/{Notebook,Lists}/`, `UtilityCatalogInclude.xml`, +> and the lexical-edit audit set (`native-views-audit.md` §8.6, `coverage-map.md`, `region-manifest.md`). + +## 1. Scope assessment + +**Verdict: this is a grab-bag, and it should be split.** Stage 8 as written (plan §6, lines 285–288) +bundles four things that have almost nothing in common architecturally, pattern-wise, dependency-wise, +or in lead level: + +1. **Notebook + Lists + bulk-edit** — pure region/composer + shared-grid surfaces. These reuse the + exact Stage 4 detail pattern and the Stage 3 browse/grid control. They are the *natural* mid-level + Track-II follow-on to Stage 6. +2. **Dictionary-configuration UI** (the `DictionaryConfigurationDlg` family + `DictionaryDetailsView/`) — + a hand-authored WinForms **dialog tree**, not an XML-view-driven surface. Per decision §11.3 these + must use **CommunityToolkit.Mvvm + compiled bindings, NOT the region/composer pattern**. This is + Stage 5 idiom, not Stage 6/8 idiom. +3. **Dictionary config *preview* wiring** — the live preview pane is a **Gecko/XULRunner browser** + (`DictionaryConfigurationDlg.cs:10` `using Gecko;`, hard-fails at `:45` if `m_preview.NativeBrowser` + is not a `GeckoWebBrowser`, and does DOM highlighting over `GeckoElement` at `:187`/`:208`/`:228`). + Replacing this is **Stage 10's** entire charter (plan §6 line 307; §10 maps it to Stage 10). +4. **"Remaining utilities/tools + sweep for stragglers"** — an open-ended census/long-tail bucket + spanning ~23 registered `IUtility` implementations (`UtilityCatalogInclude.xml`) and the whole + non-migrated WinForms surface population. + +These four have different leads (mid for 1, junior for 2 dialogs, senior for 3 preview, mixed for 4), +different patterns (region vs MVVM vs browser-replacement), and different dependencies (Stage 3/4 vs +Stage 5 vs Stage 9/10). Folding them into one "mid" epic violates the plan's own §8 staffing model and +the decision-§11.3 pattern split. **Recommend splitting Stage 8 into 8a (Notebook/Lists/bulk-edit, +mid, region pattern) and 8b (Dictionary-config dialogs, junior/mid, MVVM pattern, preview deferred to +Stage 10)**, and **hoisting the straggler-census out of Stage 8 entirely** (see §5). + +## 2. Feasibility (repo-grounded) + +### Notebook / Lists / bulk-edit — high reuse, genuinely a Stage-4/Stage-6 clone + +- **Notebook** is three tools (`DistFiles/Language Explorer/Configuration/Notebook/{Edit,Browse,Document}/ + toolConfiguration.xml`) over the `RnGenericRec` model, built on the *exact* generic hosts Stage 4 owns: + `notebookEdit` = `RecordBrowseView` (left) + `RecordEditView` (right, DataTree detail); + `notebookBrowse` = `RecordBrowseView`; `notebookDocument` = `XmlDocView`. There are **no + Notebook-specific custom slice classes** in the search — it rides the shared DetailControls stack. + `native-views-audit.md` §8.6 line 237 already routes `notebookEdit` as **explicit legacy fallback** + through `RecordEditView` (covered by `RecordEditViewSwitchTests`, task 2.11). So the detail half is a + near-mechanical Stage-4-pattern application; the browse half rides Stage 3; the **document half is the + catch** (see Stage 9/10 interaction below). +- **Lists** is ~25–29 possibility-list editor tools (`Lists/Edit/toolConfiguration.xml`, etc.), *all* + `RecordEditView` detail over `CmPossibility`/subclasses, several with hierarchical tree-bar handlers + (`PossibilityTreeBarHandler`, `SemanticDomainRdeTreeBarHandler`). `native-views-audit.md` §8.6 line 238 + already routes `posEdit`/`domainTypeEdit` as explicit legacy fallback. Feasibility is high **because the + detail surface is the same composer** — but the **tree-bar / record-list sidebar** (hierarchical + possibility navigation) is a real sub-surface that the lexical-edit exemplar did *not* exercise, and + the unbounded-tree case maps to `architecture-patterns.md` §4's "owned flattened virtualized list with + expander/indent" — i.e. it **depends on Stage 3** for the tree control. Custom-list create/delete + dialogs (`Src/xWorks/CustomListDlg.cs`, `DeleteCustomList.cs`) are Stage-5-style dialogs. +- **Bulk edit** is the real engineering item in this group. `Src/Common/Controls/XMLViews/BulkEditBar.cs` + is a large 6-tab WinForms surface (List Choice, Bulk Copy, Click Copy, Process/Transduce, Find/Replace, + Other) bolted onto `BrowseViewer` when `bulkEdit="true"` (e.g. `Words/BulkEdit/toolConfiguration.xml`, + `Lexicon/ReversalEntriesBulkEdit/toolConfiguration.xml`). It is **not** a detail surface and **not** a + simple browse; it is editable-grid-plus-operation-UI and is **squarely Stage-3-dependent** (the shared + editable virtualized grid). It also has custom column editors (`BulkReversalEntryPosEditor` in + `Src/LexText/Lexicon/LexEdDll/ReversalEntryBulkEdit.cs`) that need the plugin-registry treatment. + Bulk-edit is the highest-risk item in 8a and should be sized as its own issue, gated on Stage 3. + +### Dictionary-configuration dialogs — MVVM, sizeable, mechanically tractable + +- The family is **~43 .cs files** in `Src/xWorks/`: the main `DictionaryConfigurationDlg.cs` (+Designer), + `DictionaryConfigurationTreeControl.cs` (WinForms `UserControl`), the **15-file `DictionaryDetailsView/` + folder** (`DetailsView`, `ListOptionsView`, `SenseOptionsView`, `GroupingOptionsView`, + `PictureOptionsView`, `ButtonOverPanel`, `LabelOverPanel`, each `.cs`+`.Designer.cs`), plus the + Manager/Import/Rename dialogs (`DictionaryConfigurationManagerDlg`, `DictionaryConfigurationImportDlg`, + `DictionaryConfigurationNodeRenameDlg`). Controllers (`DictionaryConfigurationController`, + `DictionaryDetailsController`, the `IDictionary*View` interfaces) are **already MVP-style** — view + interfaces + controllers — which makes the MVVM port unusually clean: the controllers largely become + view-models. This is the most MVVM-ready dialog cluster in the repo and is a **good junior+Claude + reservoir** under the Stage-5 idiom, *not* a region-composer job. +- `DictionaryConfigurationMigrators/` (4 files: `IDictionaryConfigurationMigrator`, `PreHistoricMigrator`, + `FirstAlphaMigrator`, `FirstBetaMigrator`) and `DictionaryConfigurationMigrator.cs` are **pure + data/format logic, no UI** — they migrate `.fwdictconfig`/`ConfigurableDictionaryNode` trees and are + framework-agnostic. They carry over unchanged; do not put them in any UI epic. + +### Preview + XHTML generation — this is Stage 10, not Stage 8 + +- The preview pipeline is Gecko all the way down: `XhtmlDocView.cs` and `GeneratedHtmlViewer.cs` host + `GeckoWebBrowser` (via `Src/XCore/HtmlControl.cs`); XHTML/CSS are generated by + `ConfiguredLcmGenerator.cs`, `LcmXhtmlGenerator.cs`, `CssGenerator.cs`; PDF export references + `GeckofxHtmlToPdf`. `GeckoWebBrowser`/`GeckofxHtmlToPdf` are on the **forbidden-symbol list** + (`parity-evidence.md` §4) and removing Gecko is **Stage 10's defining deliverable**. Stage 8 *cannot* + ship the dictionary-config dialog "at parity" while its central preview pane is a forbidden Gecko + browser — either the dialog ships with preview still hosted in a coexisting WinForms/Gecko island + (acceptable during coexistence, but then it is *not* a clean Avalonia surface), or it blocks on + Stage 10. **This is the single biggest cross-stage conflict in the stage.** + +## 3. Best practices + +1. **Honor decision §11.3 explicitly in the epic text.** Dictionary-config dialogs are hand-authored UI + with no XML view-definition to compile — forcing them through the IR/composer is the exact misfit the + decision warns against. Write 8b as "MVVM + compiled bindings, reuse `FwMultiWsTextField`/`FwOptionPicker` + inside the dialog where WS-aware fields appear," matching Stage 5. +2. **Reuse the existing MVP seam.** The `IDictionary*View` + `*Controller` split is already a clean + logic/UI boundary. The runbook for 8b should say "controller → view-model, view interface → compiled + bindings," which is lower-risk than greenfield MVVM and a good teaching example for juniors. +3. **Census custom slices and bulk-edit column editors first** (`migration-checklist.md` Phase 1). Lists + tree-bar handlers and bulk-edit custom editors (`BulkReversalEntryPosEditor`) must be registered in + `RegionEditorPlugins.cs` with burn-down tests before the surfaces claim parity (`architecture-patterns.md` + §5). Notebook itself appears plugin-free, which is a point in its favor. +4. **Keep the four parity lanes honest per surface.** Each Notebook/Lists tool and each dialog needs its + own Path-3 bundle (`parity-evidence.md` §1); do not let "Notebook ≈ Lexicon detail" justify skipping + semantic/visual snapshots — the WS-heavy possibility lists and the document view have their own + typography/density behavior. +5. **Preview parity is a Stage-10 lane, not a Stage-8 checkbox.** Print/preview fidelity, page paging + (`LcmXhtmlGenerator` EntriesPerPage), and DOM highlight-on-select are Stage-10 deliverables; 8b's exit + gate must not assert preview parity. + +## 4. Interactions & dependencies + +- **Stage 3 (shared editable grid/tree) — hard blocker for the heaviest parts.** Bulk-edit *is* an + editable grid; the Lists hierarchical record-list/tree-bar is an unbounded tree. Both are exactly the + control Stage 3 owns. Plan §4 already lists Stage 8 `Depends on 4,5` but **omits Stage 3** — that is a + dependency-graph gap (see §5). Notebook/Lists *detail* and the dictionary dialogs can proceed without + Stage 3; bulk-edit and the Lists tree-bar cannot. +- **Stage 4 (exemplar) — the detail half clones it.** Notebook Edit and the 25+ Lists editors are the + cleanest possible reuse of the Stage-4 composer/region pattern; they are the validation that the + exemplar generalizes. They also inherit Stage 4's open exemplar-debt (the dual-projector unification, + 18.11) — if that is not resolved, Notebook/Lists clone a 2524-line composer. +- **Stage 5 (dialogs) — the dictionary-config dialogs *are* Stage-5-shaped work.** Plan §4 says Stage 8 + `Depends on 5`, but the dependency is really *pattern reuse*, not sequencing: 8b should be staffed and + run as additional Stage-5 streams. Custom-list/Import/Rename dialogs likewise. +- **Stage 9 (managed document engine) — the document/preview surfaces depend on it, and the plan does not + say so.** `notebookDocument` (`XmlDocView`) and `XhtmlRecordDocView`/`RecordDocView` are + document-rendering surfaces. Multi-paragraph/structured document rendering is Stage 9's charter + (plan §6 line 292). Plan §4 omits the Stage-9 dependency for Stage 8's document views. +- **Stage 10 (Gecko/Graphite removal + dictionary-preview) — direct overlap.** Plan §10 line 307 + explicitly names "dictionary-preview replacement" as Stage 10. So the *preview* half of Stage 8 line 287 + ("config preview wiring") **belongs to Stage 10**, while the *configuration dialog* half stays in + Stage 8. The plan currently double-books the preview across Stage 8 and Stage 10. This must be + disambiguated or the two epics collide. + +## 5. Recommended plan changes + +1. **Split Stage 8 into 8a and 8b.** 8a = Notebook + Lists + bulk-edit (mid, region/composer + Stage-3 + grid/tree). 8b = Dictionary-configuration dialogs (junior/mid, MVVM + compiled bindings, Stage-5 idiom, + reusing the existing MVP controllers). This matches §8 staffing and decision §11.3. +2. **Move "config preview wiring" out of Stage 8 into Stage 10.** Restate Stage 8 line 287 as + "Dictionary-configuration *dialogs* (`DictionaryConfigurationDlg` family); the Gecko preview pane and + its XHTML/CSS/PDF pipeline are migrated by Stage 10." Add an explicit cross-link so 8b's exit gate + excludes preview parity and instead permits the preview to remain a coexisting island until Stage 10. +3. **Add Stage 3 and Stage 9 to Stage 8's dependency row** (plan §4 line 130 currently says only `4,5`). + Bulk-edit + Lists tree-bar block on Stage 3; the document/preview views block on Stage 9/10. Update the + §5 mermaid graph edges accordingly (`S3 --> S8`, `S9 --> S8` for the document/preview slice). +4. **Hoist the "sweep for stragglers via the surface registry" out of Stage 8 into Stage 1 (or a + standalone census issue).** Today there is **no app-wide surface registry** — only the + *lexical-edit-scoped* assets (`LexicalEditSurfaceSelectionService.cs`, and the audit docs + `native-views-audit.md` §8.6 / `coverage-map.md` / `view-inventory.md` / `region-manifest.md`, all + scoped to the entry surface). Plan §6 Stage 2 already charters "generalize → an app-wide surface + registry/switch." The *census that feeds that registry* (the ~200+ Form/UserControl population, the 23 + `IUtility` entries, the dialog inventory) is a foundation/Stage-1 deliverable, not a tail task buried in + a mid-level surface epic. Stage 8 should *consume* the registry to claim "remaining areas done," not be + the place the census first happens. Recommend: a single living "surface census" artifact under + `openspec/changes/avalonia-migration-roadmap/` (sibling to the reviews), owned by Stage 1/2, that every + surface stage burns down against. +5. **Reclassify the migrators and XHTML generators as non-UI.** `DictionaryConfigurationMigrators/` and + `DictionaryConfigurationMigrator.cs` are framework-agnostic data logic — carry over unchanged, not in a + UI epic. The XHTML/CSS generators (`ConfiguredLcmGenerator`, `LcmXhtmlGenerator`, `CssGenerator`) are + the *content* side of the preview and move with Stage 10's preview replacement, not with the dialog. +6. **Size bulk-edit as its own gated issue.** It is the only true engineering item in 8a (editable grid + + 6 operation tabs + custom column editors), explicitly blocked on Stage 3 and the plugin registry. + +## 6. Open questions / risks + +- **Preview double-booking (Stage 8 vs Stage 10) is unresolved in the plan.** Until §6 line 287 and + line 307 are reconciled, two epics claim the dictionary preview. Highest-impact ambiguity here. +- **Can the dictionary-config dialog ship "at parity" with a coexisting Gecko preview island?** During + coexistence the rule is WinForms owns modality (`architecture-patterns.md` §7); a Gecko preview hosted + beside an Avalonia dialog body may be acceptable transitionally, but then the surface is *not* a clean + Avalonia surface and `EngineIsolationAuditTests` would flag `GeckoWebBrowser` if it leaks into the + migrated assembly. Where exactly does the boundary sit? This needs an explicit decision. +- **No app-wide surface registry exists yet** — "sweep for stragglers via the surface registry" presumes + an asset that is still lexical-edit-scoped. If Stage 2 has not produced it by the time Stage 8 runs, the + straggler sweep has nothing to sweep against and silently becomes an unbounded discovery task. +- **Lists tree-bar / hierarchical possibility navigation is unexercised by the exemplar.** Semantic-domain + and other hierarchical lists need the Stage-3 unbounded-tree control plus the `*TreeBarHandler` behavior; + the lexical-edit slice did not prove this path. Medium risk it is under-sized. +- **Bulk-edit "Process/Transduce" and "Find/Replace" tabs embed regex/transform UI** that may have its own + hidden dialog/chooser dependencies; census needed before sizing. +- **Notebook Document and `XhtmlRecordDocView` are document surfaces** that quietly depend on Stage 9; if + Stage 8 is read as "Notebook done," it inherits Stage 9 scope it cannot satisfy. + +## 7. Confidence + +**High** on the architectural split argument and the Gecko-preview finding — these are confirmed from +source: `DictionaryConfigurationDlg.cs` hard-requires `GeckoWebBrowser` (lines 10/45/187/208/228), the +tool configurations name the exact generic hosts, `BulkEditBar.cs` is a confirmed `BrowseViewer`-bolted +surface, and `native-views-audit.md` §8.6 already routes Notebook/Lists as explicit legacy fallback. +**High** that no app-wide surface registry exists yet (only the lexical-edit-scoped service + audit docs). +**Medium** on bulk-edit and Lists-tree-bar sizing — I read these from tool configs, file structure, and +the audit, not a line-by-line read of `BulkEditBar.cs` internals or the tree-bar handlers. **Medium** on +the precise §11.3-vs-Gecko boundary for the dialog (whether a coexisting preview island passes the +isolation audit) — that needs a deliberate program decision, not just a repo read. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-09-managed-document-text-engine.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-09-managed-document-text-engine.md new file mode 100644 index 0000000000..d3df74205f --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-09-managed-document-text-engine.md @@ -0,0 +1,292 @@ +# Stage 9 Review — Managed Document/Text Rendering & Editing Engine (Views Replacement) + +Reviewer: Claude (Opus 4.8, 1M). Date: 2026-06-15. +Scope under review: master plan §4 (stage row 9), §6 Stage 9, §11 open question (build-vs-extend), +risk register rows on rich-text/bidi/IME and HarfBuzz coverage. Grounded in `Src/views/`, +`Src/Common/SimpleRootSite/`, `Src/Common/FwAvalonia/Region/FwFieldControls.cs` +(`FwMultiWsTextField`), `openspec/changes/avalonia-multi-writing-system-text-foundation/`, and +`lexical-edit-avalonia-migration/native-views-audit.md`. This is the program's **long pole**; the +review treats it as such. + +--- + +## 1. Scope assessment + +**Verdict: NOT realistic as one stage. Decompose into a spike gate + four sequenced sub-epics.** +The native surface this stage replaces is far larger than a single epic can carry, and the stage +text silently bundles three different problems — *text layout/shaping*, *selection/caret model*, +and *structured document model* — each of which is itself a multi-quarter senior effort. + +Repo-measured size of what is being replaced (Explore audit, this worktree): + +- **Native C++ Views engine `Src/views/`: ~134K LOC, 136 files** (50 `.cpp` / 86 `.h`). + `VwSelection.cpp` alone is **~14.4K LOC**; `VwTextBoxes.cpp` ~10.1K; `VwRootBox.cpp` ~5.3K. +- **Managed host `Src/Common/SimpleRootSite/`: ~25K LOC, 31 files.** `SimpleRootSite.cs` ~7.1K, + `EditingHelper.cs` ~3.9K, `SelectionHelper.cs` ~2.1K. +- **~32 public COM interfaces** form the contract (`IVwRootBox`, `IVwSelection`, `IVwEnv`, + `IVwGraphics`, `IVwRootSite`, `IRenderEngine`, `IVwViewConstructor`, `IVwStylesheet`, + `IVwOverlay`, `IVwPrintContext`, `IVwSynchronizer`, `ILgSegment`, …). +- **~153 files reference `IVwRootBox`; ~80 reference `SimpleRootSite`/RootSite** — i.e. the + consumer fan-out is wide, but per `native-views-audit.md` §8.6 only ~6–10 *surfaces* are + strongly coupled (Interlinear, Discourse, XMLViews browse, xWorks doc views, DetailControls + StText, parser sandbox). + +**What `FwMultiWsTextField` actually is (the DELTA framing matters):** it is **not** a managed text +engine. It is a thin wrapper around stock Avalonia `TextBox` (one per WS row) with three bolt-ons: +(a) bidi-aware caret arrow navigation delegated to `RegionBidirectionalTextNavigation`, (b) +grapheme-cluster selection normalization, and (c) `ITsString` run write-back staging via +`RegionRichTextEditAlgorithms.ApplyPlainTextEdit` (`FwFieldControls.cs:144-291`). Shaping, layout, +line-breaking, hit-testing, and IME are **stock Avalonia `TextBox`/`TextLayout`** — FieldWorks owns +none of it. The multi-WS foundation (`avalonia-multi-writing-system-text-foundation`, done +2026-06-15) explicitly closed **only** the single-paragraph string-slice blockers +(`MultiStringSlice`/`StringSlice`/`GhostStringSlice`) and **explicitly deferred `StText` +multi-paragraph editing and embedded-object/ORC editing** (`native-views-audit.md` §8.3 "Deferred," +design.md decision 4). + +So the true Stage-9 DELTA over Stage 0 is everything the field foundation isn't: + +| Already done (Stage 0 / foundation) | Stage 9 DELTA (not started) | +|---|---| +| Single-para, single-/multi-WS `ITsString` field editing over stock `TextBox` | **Multi-paragraph `StText`** editing (paragraph model, splitting/merging, para-level props) | +| Per-WS font/RTL/keyboard projection; bidi caret nav within a field | **Selection across structured content** (nested objects, levels — what `IVwSelection`/`SelLevInfo` model in 2.1K + 14.4K LOC) | +| Grapheme-cluster-safe edit/caret; IME *composition-state* modeling | **Owned text layout/shaping/line-break** (stop depending on stock `TextBox` once content exceeds what `TextBox` renders correctly) | +| Read-only ORC/embedded-object rendering | **Editable embedded objects/ORCs, footnotes, pictures, tables** (`VwTableBox`, `VwPictureBox`, ORC anchors) | +| Headless + typing-latency evidence for fields | **Interlinear/sandbox** structured layout (Stage 7's dependency) | + +**Recommended decomposition (one parent epic, gated children):** + +- **9.0 Spike (gate, blocking).** The five named spike scenarios + the HarfBuzz-coverage proof. + Output decides build-vs-extend AND feeds the Stage-10B removal gate. Do not open 9.1+ until 9.0 + exits. +- **9.1 Multi-paragraph `StText` editing** (closes the one deferred string blocker; smallest + document-model step; unblocks lexicon comment/note fields and Notebook detail). +- **9.2 Managed structured selection/caret model** replacing `IVwSelection`/`SelectionHelper` + (the 14.4K-LOC core; selection across levels/nested objects; the hardest correctness surface). +- **9.3 Owned text layout/shaping surface** — only *if* the spike shows stock `TextLayout`/`TextBox` + is insufficient for document surfaces (it almost certainly is for justified/complex multi-line + StText, drop-caps, tables, overlays). HarfBuzz/Skia via Avalonia `TextLayout`, FieldWorks-owned + box/line model replacing the `VwParagraphBox`/`VwStringBox`/`VwLazyBox` hierarchy. +- **9.4 Embedded objects / footnotes / pictures / tables / overlays** (the `VwTableBox` / + `VwPictureBox` / ORC / `IVwOverlay` long tail; can trail). + +This mirrors the Stage-10 reviewer's "split the bundled stage" pattern and matches the audit's own +deferral structure. **Interlinear-specific** layout (Stage 7) should stay in Stage 7 consuming 9.2/9.3 +seams, not be pulled into 9 — but 9.0's spike *must* include an interlinear/sandbox scenario or +Stage 7 inherits an unvalidated dependency. + +--- + +## 2. Feasibility (repo-grounded + web) + +### 2a. The framework gap is real and is the program's #1 risk + +The master plan's risk register already ranks "rich-text/bidi/IME gaps in Avalonia" High/High; the +web evidence confirms it is not theoretical. Avalonia 11 added IME and rich-text *inlines*, but the +**editable** complex-text surface still carries open caret/selection/RTL defects: + +- RTL/Unicode `TextBox` rendering issues: + [#5794](https://github.com/AvaloniaUI/Avalonia/issues/5794). +- Reversed-selection caret placement: [#12055](https://github.com/AvaloniaUI/Avalonia/issues/12055); + Shift+Arrow caret: [#13648](https://github.com/AvaloniaUI/Avalonia/issues/13648); + Home/End caret: [#12063](https://github.com/AvaloniaUI/Avalonia/issues/12063); + word-jump caret: [#14726](https://github.com/AvaloniaUI/Avalonia/issues/14726); + caret hidden under non-empty selection: [#16388](https://github.com/AvaloniaUI/Avalonia/issues/16388). +- The vendor rich-text editor is **Pro-tier** with RTL unverified + ([avaloniaui.net/blog/rich-text-editor](https://avaloniaui.net/blog/rich-text-editor)); + AvaloniaEdit is a **code editor**, not rich text + ([AvaloniaEdit](https://github.com/AvaloniaUI/AvaloniaEdit)). + +**Implication:** the `FwMultiWsTextField`-over-stock-`TextBox` strategy that worked for *fields* +does **not** extend to document surfaces. Stock `TextBox` carries exactly the caret/selection bugs +above, and FieldWorks has been patching around them per-field (`RegionBidirectionalTextNavigation`, +hit-test normalization in `FwFieldControls.cs:203-222`). At document scale (multi-paragraph, +mixed-direction, structured selection across objects) that patch-the-stock-control approach does not +scale — Stage 9 will need a **FieldWorks-owned selection/layout model above the rendering layer**, +exactly as `avalonia-multi-writing-system-text-foundation/design.md` risk note #1 already warned for +fields. This is the strongest argument that 9.2 (owned selection) and 9.3 (owned layout) are +unavoidable, not optional. + +### 2b. Build-vs-extend (the §11 open question) + +Both paths are fully managed. Repo-grounded reading: + +- **Extend the existing foundation** (`FwMultiWsTextField` + `RegionRichTextEditAlgorithms` + + `RegionImeCompositionState`): viable for **9.1 StText** if a paragraph is modeled as a stack of + the existing per-WS editors. Breaks down at 9.2/9.3 because stock `TextBox` owns selection/layout + and cannot express cross-object selection or FieldWorks box semantics. +- **Build an owned document control** over Avalonia `TextLayout` (the read-only shaping/measure + primitive) + an owned caret/selection/box model: necessary for 9.2–9.4. `TextLayout` is HarfBuzz/ + Skia-backed and gives shaping + line-breaking + hit-testing as a primitive, so "build" here is + *owning the editable/selection/box layer over `TextLayout`*, **not** re-implementing shaping. + +**Recommendation:** record the likely outcome now so the spike validates rather than discovers it — +**extend for 9.1, build an owned `TextLayout`-based control for 9.2+**. The native `VwBox` +hierarchy (`VwParagraphBox`/`VwStringBox`/`VwGroupBox`/`VwTableBox`/`VwLazyBox`) is the reference +spec for the owned box model; `VwLazyBox` in particular flags that **lazy/virtualized box +realization** (Stage 3's virtualization gap) is also a document-engine concern — coordinate 9.3 with +Stage 3. + +### 2c. Graphite coverage — the verdict (this is Stage 9's gating deliverable for Stage 10B) + +**Verdict: HarfBuzz/managed shaping covers the vast majority of formerly-Graphite scripts, but there +is a confirmed, irreducible gap — Awami Nastaliq (Urdu Nastaliq) is Graphite-ONLY by design and has +no OpenType equivalent. "HarfBuzz only" must be qualified, not asserted unconditionally.** + +Evidence: + +1. **HarfBuzz does not implement Graphite itself.** `hb-graphite2` *delegates* to the external + `libgraphite2` (`gr_face`) and is **disabled by default** in HarfBuzz builds + ([hb-graphite2](https://harfbuzz.github.io/harfbuzz-hb-graphite2.html), + [graphite-shaping](https://harfbuzz.github.io/graphite-shaping.html)). So "HarfBuzz/managed only, + Graphite removed" genuinely means *no Graphite shaping at all* — there is no hidden HarfBuzz + Graphite fallback. The repo's own `graphite-decommissioning.md` §2 already recorded this + correctly. +2. **SIL has itself moved its flagship fonts to OpenType-only.** Charis SIL v7 (2 Jun 2025) and + Doulos SIL **removed Graphite** ("application/OS OpenType support has greatly improved") + ([Charis news](https://software.sil.org/charis/news/), + [Doulos news](https://software.sil.org/doulos/news/)). Most Latin/Cyrillic/IPA/diacritic-heavy + scripts are therefore covered by OpenType today. +3. **The hard exception is real and named.** Awami Nastaliq **does not support OpenType and will not + work in OpenType apps** — because Nastaliq requires Graphite **collision avoidance** that + "OpenType engines" lack; SIL states they would add OpenType "should OpenType engines gain the + necessary collision avoidance support" ([Awami FAQ](https://software.sil.org/awami/faq/)). Annapurna + SIL (Devanagari) still ships both engines. This is exactly the population the + `graphite-transition-support` change flagged: "Graphite-rendered writing systems are concentrated + in exactly the minority-language projects FieldWorks exists to serve" (proposal.md, "Why"). + +**Consequence for the spike:** 9.0's HarfBuzz-coverage proof must be **data-driven, not a font-by- +font opinion** — scan project LDML for `IsGraphiteEnabled` + Graphite-only feature strings (the +repo already has the `km.ldml` Khmer fixture and the G0–G3 classifier salvageable from +`graphite-transition-support` tasks 1.3/1.4/3.2), and classify each as G0 (dual-engine, OT-safe), +G2 (dual-engine but renders *differently* under OT feature strings), or **G3 (Graphite-only, no OT +path — Awami Nastaliq)**. Stage 10B removal is safe only for G0/G2-with-accepted-delta; G3 has **no +in-app fidelity path after removal** and needs an explicit product decision (freeze those projects +on legacy, or accept degraded rendering). The Stage-10 review reached the same conclusion from the +removal side; this review supplies the coverage proof it depends on. + +--- + +## 3. Best practices + +- **Spike-first is correct and already in the plan — make its exits measurable.** Each of the five + spike scenarios (mixed bidi, CJK IME, custom WS, multi-para StText, cross-structure selection) and + the HarfBuzz-coverage scan must produce a Path-3 bundle or a written go/no-go, not a "looks fine." + Add an **interlinear/sandbox** scenario so Stage 7's dependency is validated in the spike. +- **Own the selection/layout model above the rendering primitive** (foundation design risk #1). + Use `TextLayout` for shaping/measure/hit-test; do **not** rely on stock `TextBox` selection/caret + for document surfaces given the open bidi/caret defects (§2a). +- **Reuse the native `VwBox` hierarchy as the spec, not the implementation.** It is the proven box + model (paragraph/string/group/table/lazy/picture); the managed model should map to it for parity + fixtures while being a clean C# rewrite. +- **One global undo stack** (master principle 9 / `architecture-patterns.md` §8): route document + edits through `IUndoRedoCoordinator` → LCModel action handler; never a parallel Avalonia history. + StText edits are multi-paragraph LCModel mutations — keep them inside the fenced `IEditSession`. +- **Spell-check is a service, not a render concern.** Per `native-views-audit.md` §8.7/8.8, the + Avalonia editor queries `ISpellEngine` directly and **draws its own squiggles** — it must NOT call + `SetSpellingRepository`/`IGetSpellChecker` (those exist only for `VwRootBox`). Footnotes, overlays, + and ORC editing similarly need owned managed equivalents. +- **IME and RTL need realized-window evidence, not headless** (foundation design decision 6; parity- + evidence §2). Headless proves run fidelity/cluster/selection logic; IME composition and complex- + script editing need UIA2/realized-window lanes. +- **Keep the forbidden-symbol audit green every PR** (`EngineIsolationAuditTests`) — no + `IVwRootBox`/`IVwEnv`/`IVwGraphics`/`IRenderEngine`/`GraphiteEngineClass` in the Avalonia path. +- **Measure typing latency at 100% and 150% DPI** and commit budgets before any parity claim + (extends the foundation's `typing-latency-evidence.md` to multi-paragraph). + +--- + +## 4. Interactions & dependencies + +- **Stage 7 (Interlinear/Discourse) hard-depends on Stage 9 — and the plan's §5 graph already wires + `S9 → S7`.** But Stage 9's *current scope text* does not name interlinear layout, sandbox, or + constituent-chart needs. `native-views-audit.md` §8.6 lists the Stage-7 consumers as + `InterlinDocRootSiteBase`, `InterlinDocForAnalysis`, `SandboxBase`, `ConstChartBody`, + `InterlinRibbon` — all `RootSite`-derived. **Action:** Stage 9 owns the *engine seams* (selection, + layout, box model, editable structured content) and 9.0's spike must include an interlinear + scenario; Stage 7 owns the interlinear-specific *composition* over those seams. State this split + so Stage 7 does not inherit unscoped engine work and Stage 9 does not absorb interlinear UI. +- **Stage 10B (Graphite removal) is gated on Stage 9's coverage proof — correct, and the Stage-10 + reviewer already relies on it.** Tighten the gate: Stage 9's exit must include the **LDML/G0–G3 + fixture scan** identifying G3 (Graphite-only) projects, not just "spike says HarfBuzz is fine." + The G3/Awami-Nastaliq finding (§2c) is a *blocking input* to whether 10B can remove Graphite at + all vs. defer the native-engine deletion to Stage 13. +- **Stage 3 (virtualized grid/tree).** `VwLazyBox` shows document layout itself needs lazy/ + virtualized box realization. 9.3's owned layout and Stage 3's virtualization primitive should + share the realization-window approach; coordinate so the document engine doesn't reinvent + virtualization. +- **Stage 4 (finish Lexical/Advanced Entry).** Stage 4 closes the *field* surface that the + foundation already powers; it does **not** depend on Stage 9 except for the one deferred `StText` + field (lexicon comments/notes) — which 9.1 unblocks. Note this so Stage 4 can ship with `sttext` + fields read-only (already the case per §8.3) and upgrade when 9.1 lands. +- **Stage 13 (native decommission).** The native `Src/views` deletion + RegFree/build cleanup lands + here, not in Stage 9. Stage 9 only stops the *Avalonia path* from using native Views; `Src/views` + stays alive for legacy-mode/parity baselines and all the §8.6 unplanned-area consumers until + Stage 13. +- **Spell/ICU/parser services** stay behind seams (`ISpellEngine`, `Icu`/`CustomIcu`, + `ParserConnection`) — unchanged by Stage 9 (§8.8). + +--- + +## 5. Recommended plan changes + +1. **Decompose Stage 9 into one parent epic + gated children 9.0 (spike), 9.1 (StText), 9.2 + (selection/caret model), 9.3 (owned layout/box model), 9.4 (embedded objects/tables/overlays).** + Block 9.1+ on 9.0 exit. (§1.) +2. **Reframe the stage scope as the DELTA over the landed foundation** — explicitly state that + single-paragraph field editing is *done* (`FwMultiWsTextField`) and Stage 9 is multi-paragraph + + structured selection + owned layout + editable embedded content. Avoids re-litigating field work. +3. **Add a HarfBuzz-coverage / G0–G3 fixture-scan deliverable to 9.0's exit gate**, salvaging the + classifier from `graphite-transition-support` (tasks 1.3/1.4/3.2). Make the **G3 (Graphite-only, + e.g. Awami Nastaliq) finding an explicit blocking input to Stage 10B.** (§2c.) +4. **Record the build-vs-extend recommendation** (extend for 9.1; build owned `TextLayout`-based + selection/layout for 9.2+) so the spike validates rather than re-opens it. (§2b.) +5. **Add an interlinear/sandbox scenario to the 9.0 spike** and state the Stage-9/Stage-7 seam split + so Stage 7's dependency is de-risked. (§4.) +6. **Coordinate 9.3 owned layout with Stage 3 virtualization** (the `VwLazyBox` lazy-realization + overlap). (§4.) +7. **Note that native `Src/views` deletion is Stage 13, not Stage 9** — Stage 9 only severs the + Avalonia path. (§4.) + +--- + +## 6. Open questions / risks + +1. **G3 Graphite-only projects (Awami Nastaliq / Urdu Nastaliq) have no managed rendering path after + removal.** OpenType cannot do Nastaliq collision avoidance ([Awami FAQ](https://software.sil.org/awami/faq/)). + Needs a product-owner decision: freeze those projects on legacy, accept degraded OT rendering, or + hold native Graphite until those users migrate. **This is a real, named, unresolved risk the + roadmap's "Graphite fully removed" decision has not reconciled with FieldWorks' minority-language + mission** — the `graphite-transition-support` change exists precisely because of it. (High.) +2. **Is the owned selection model (9.2) buildable to parity?** `VwSelection` is ~14.4K LOC of + cross-level, cross-object selection logic; `SelectionHelper`/`TextSelInfo` add ~2K+. This is the + single largest correctness surface and the deepest unknown. The spike must exercise selection + across structured content with real fixtures before committing the build. (High.) +3. **Does stock Avalonia `TextLayout` give enough for FieldWorks document layout** (justification, + complex line-breaking, drop-caps, inverted/RTL paragraphs, overlays, tables)? If not, 9.3 grows + toward re-implementing more of the box engine than budgeted. (Med-High.) +4. **IME at document scale across structured boundaries** — composition spanning paragraph/object + edges is the kind of state the foundation modeled only for single fields. Realized-window + evidence required; environment-sensitive. (Med.) +5. **Performance/virtualization** — `VwLazyBox` laziness is load-bearing for large StTexts and + interlinear; the owned layout must virtualize box realization or large documents regress. + Coordinate with Stage 3. (Med.) +6. **Footnotes / ORC / embedded windows (`IVwEmbeddedWindow`) editing** — the long tail (9.4) is + easy to under-scope; it is the difference between "lexicon notes work" and "Scripture/structured + documents work." (Med.) + +--- + +## 7. Confidence + +**High** on the repo-grounded size and DELTA framing (native LOC/interface counts from the +`Src/views` + `SimpleRootSite` audit; `FwMultiWsTextField` is verifiably stock-`TextBox`-based; +`StText`/ORC explicitly deferred per `native-views-audit.md` §8.3 and foundation design.md). +**High** on the Graphite-coverage verdict (HarfBuzz delegates to external libgraphite2 and is off by +default; SIL dropped Graphite from Charis/Doulos; Awami Nastaliq is Graphite-only by design — all +web-confirmed and consistent with the repo's own `graphite-decommissioning.md`/`graphite-transition- +support`). +**Medium-high** on the decomposition shape (clear from the audit's deferral structure and the native +hierarchy, but exact epic boundaries are the owner's call). +**Medium** on build-vs-extend specifics and on whether `TextLayout` suffices for document layout — +these are genuinely what the 9.0 spike must decide; this review narrows the question and predicts the +likely answer but does not pre-empt the spike. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-10-browser-pdf-graphite-removal.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-10-browser-pdf-graphite-removal.md new file mode 100644 index 0000000000..86702b9ee7 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-10-browser-pdf-graphite-removal.md @@ -0,0 +1,274 @@ +# Stage 10 Review — Browser/PDF & Dictionary-Preview Replacement; Graphite Full Removal + +Reviewer: Claude (Opus 4.8). Date: 2026-06-15. +Scope under review: master plan §4 (stage table row 10), §6 Stage 10, §11 decision 1, and the +now-superseded `graphite-transition-support` change. + +--- + +## 1. Scope assessment + +**Verdict: the stage bundles two genuinely distinct workstreams that should be split into two +epics under one parent.** They share a theme ("retire native-bound rendering") but have +different blast radius, different gates, different risk profiles, and different critical-path +positions: + +- **10A — Browser/PDF/dictionary-preview replacement (Gecko/XULRunner retirement).** This is a + *surface-migration + packaging* problem. It is large (8 referencing projects, ~14 source call + sites, a hard startup dependency, a renamed PDF exe, installer harvest, binding-redirect + patching) and it spans areas owned by **other stages** (Stage 7 Interlinear/Discourse, Stage 8 + dictionary-config UI, Stage 6/parser, plus shell startup). It is a **Stage 13 cross-platform + blocker** in its own right (XULRunner is Windows-only as packaged here). +- **10B — Graphite full removal.** This is a *rendering-core + native-decommission + data-policy* + problem. It is gated hard on Stage 9 (HarfBuzz coverage proof), touches the native Views engine + and `RenderEngineFactory`, and reaches into LCModel-owned model properties + (`IsGraphiteEnabled`, `DefaultFontFeatures`) that this repo does not own. + +These only weakly interact (the single real coupling is the Gecko `gfx.font_rendering.graphite` +preference and the `--graphite` flag passed to the PDF maker — both of which simply *disappear* +when Gecko goes away, requiring no Graphite-engine work). Splitting lets 10A proceed on the +Track-II surface cadence while 10B stays blocked behind Stage 9 without holding up browser +retirement. **Recommendation: keep Stage 10 as the parent epic; create child epics 10A (Gecko/PDF) +and 10B (Graphite removal) with independent gates.** + +One scope gap in the current write-up: Stage 10's bullet names only `GeckoWebBrowser` and +`GeckofxHtmlToPdf`, but the dominant cost is the **process-wide XULRunner bootstrap** in +`Src/Common/FieldWorks/FieldWorks.cs:164-192` (hard `ApplicationException` if the Firefox folder is +missing) and the **shutdown double-free hack** at `:399-409`. The stage scope text should name the +startup/shutdown coupling explicitly — it is the true gate for "Gecko removed from codebase," not +the leaf browser controls. + +--- + +## 2. Feasibility (repo-grounded + web) + +### 2a. How dictionary preview is rendered today + +The preview/doc-view pipeline is fully repo-owned and **does not depend on Gecko to produce +content** — only to *display* it: + +- XHTML generation: `Src/xWorks/LcmXhtmlGenerator.cs`, `ConfiguredLcmGenerator.cs`, + `DictionaryExportService.cs`; CSS from `Src/xWorks/CssGenerator.cs`. These are managed and + HarfBuzz/OpenType-agnostic — they emit HTML + CSS. +- Display controls (the Gecko-bound part): + - `Src/xWorks/XhtmlRecordDocView.cs:67` — `new XWebBrowser(BrowserType.GeckoFx)`, the **preview + pane inside Lexical Edit** (on by default). + - `Src/xWorks/XhtmlDocView.cs:59` — Dictionary/Reversal/Classified doc views. + - `Src/xWorks/DictionaryConfigurationDlg.cs:44-49` — configure-dictionary live preview (uses + `GeckoElement` DOM walking at `:228` to highlight config changes). + +**This is the architecturally favorable case:** because the content is generated HTML/CSS, the +replacement is "render this HTML string in a managed view," not "rebuild the dictionary renderer." + +### 2b. What replaces it in managed Avalonia + +Two viable managed targets, both confirmed by web research: + +1. **Avalonia 12 built-in `Avalonia.Controls.WebView`** (NuGet `Avalonia.Controls.WebView`, v12.x, + now open-source and in-box). It wraps the *native* platform engine: WebView2 (Windows), + WebKit (macOS), WebKitGTK (Linux), with bidirectional JS interop and no bundled Chromium. + This is the natural fit and is **cross-platform**, which directly unblocks Stage 13. It does, + however, reintroduce a native dependency per platform (WebView2 runtime on Windows). Crucially + it **lands with Stage 12 (Avalonia 12)** — so Stage 10A's *managed* preview cannot fully land + before Stage 12 unless an interim WebView2-direct integration is used. + (https://www.nuget.org/packages/Avalonia.Controls.WebView/, + https://docs.avaloniaui.net/docs/app-development/embedding-web-content) +2. **A managed HTML-render-to-Avalonia path** for the *highlight/interaction*-light surfaces + (the simpler help panes). Heavier, generally not worth it given option 1. + +For the **DOM-interaction** surfaces (config-dialog highlighting via `GeckoElement`; interlinear +config via `AutoJSContext`/`GeckoInputElement` in `ConfigureInterlinDialog.cs`; parser trace via +`WebPageInteractor.cs`), the migration is non-trivial: each uses a different Gecko DOM-interop +pattern and must be re-expressed as WebView2 `ExecuteScriptAsync`/message-channel interop, or +(better) reworked so interaction happens in Avalonia rather than in the document DOM. These are +the real engineering cost in 10A and are spread across **Stages 6/7/8**, reinforcing the split. + +### 2c. PDF export — well isolated + +PDF export is a **single runtime call site**: `Src/xWorks/XhtmlDocView.cs:849-925` +(`GeneratePdfToPrint`), invoking `FieldWorksPdfMaker.exe` (the renamed `GeckofxHtmlToPdf`) with +`"" "" --graphite --reduce-memory`, used only for large dictionaries (>10k entries) and +as a print fallback. Smaller prints use Gecko `Window.Print()` (`:963-967`). Packaging: +`Build/PackageRestore.targets:477-520` downloads/renames the exe and patches its binding redirect; +`Build/Installer.legacy.targets` suppresses an ICE30 duplicate-file warning. + +Replacement options (web-confirmed): +- If Avalonia WebView2 is adopted, **`CoreWebView2.PrintToPdfAsync`** replaces both the PDF-maker + exe and `Window.Print()` with one managed API — the cleanest path and it removes the harvested + exe entirely. +- `wkhtmltopdf`/DinkToPdf are **archived (2023/2024) with unpatched CVEs — do not adopt** + (https://medium.com/iron-software/whatever-happened-to-wkhtmltopdf-and-dinktopdf-916dc6cdb1cc). +- QuestPDF is a *programmatic* PDF builder, **not** an HTML renderer, and is revenue-license-gated + above $1M — wrong tool for "render existing dictionary XHTML." Headless Chromium (PuppeteerSharp/ + Playwright) is the only other faithful HTML→PDF path but reintroduces a bundled browser, which + contradicts the migration's "shed native deps" charter. + +**Feasibility conclusion:** 10A is feasible and the content side is favorable; the cost is the +startup decoupling + the per-surface DOM-interop rewrites + packaging/installer changes, and the +clean version of it is naturally an **Avalonia-12 / Stage-12-aligned** piece of work. + +### 2d. Graphite removal — feasibility + +Graphite is **surprisingly well-isolated at the decision point** but reaches into model + UI + build: + +- Single selection point: `Src/Common/SimpleRootSite/RenderEngineFactory.cs:107-126` + (`GetRenderingEngine`) — `if (ws.IsGraphiteEnabled) { GraphiteEngineClass.Create(); ... if + (graphiteEngine.FontIsValid) return it; else release and fall through to Uniscribe }`. Removal + here is a one-branch deletion that always returns the Uniscribe/OpenType engine. +- Managed COM factory: `Src/Common/ViewsInterfaces/Views.cs:2605-2767` (`GraphiteEngineClass`). +- Native engine: `Src/views/lib/GraphiteEngine.{h,cpp}`, `GraphiteSegment.{h,cpp}`; project entries + in `Src/views/views.vcxproj`; native lib `Lib/src/graphite2/` built by the `graphite2Windows` + target in `Build/Windows.targets:64,69,220-256`; RegFree manifest entry in + `Build/RegFree.targets:50-51`. Native deletion is wholesale and self-contained. +- WS-setup UI: `Src/FwCoreDlgs/FwCoreDlgControls/DefaultFontsControl.cs` (Graphite checkbox/groupbox), + `FontFeaturesButton.cs:421` (`GraphiteEngineClass.Create()` for feature labels), + `FwWritingSystemSetupModel.cs:514-523`. Feature utility: `Src/Common/FwUtils/GraphiteFontFeatures.cs`. +- **Model properties are LCModel-owned (external):** `IsGraphiteEnabled` and `DefaultFontFeatures` + live on `CoreWritingSystemDefinition` in SIL.LCModel, not this repo. They cannot be *deleted* + here; they can only be ignored (treated as always-OpenType). Note `DefaultFontFeatures` is **not + Graphite-specific** — it also drives `CssGenerator.cs:1562` and `WordStylesGenerator.cs:479,488` + for export, so it must **survive** as an OpenType feature string. Only the *Graphite engine + selection* and the *Graphite feature-ID conversion* path retire. + +**Is removal safe once Stage 9 lands?** Conditionally yes, and the gate is correctly placed: +removal is safe **only if** the Stage 9 spike proves HarfBuzz covers the scripts Graphite formerly +handled. The repo's own `graphite-transition-support/design.md` flags the hard cases — **Awami +Nastaliq is Graphite-only with no OpenType replacement** today, and dual-engine fonts with +*Graphite feature strings* (tier G2) render differently under OpenType. Removing Graphite entirely +makes *those projects' rendering change on legacy surfaces too* (because `RenderEngineFactory` is +shared by WinForms and any surviving native-Views surface, not just Avalonia). This is the single +biggest correctness risk in the whole stage and is **stronger than the superseded plan assumed** — +see §6. + +--- + +## 3. Best practices + +- **Decouple the startup bootstrap first.** Make XULRunner init lazy/conditional before touching + any consumer, so "Gecko absent" stops being a hard process-fail. This is the keystone that lets + 10A proceed incrementally. +- **Prefer one managed PDF API over a harvested exe.** `CoreWebView2.PrintToPdfAsync` collapses + three things (PDF-maker exe, binding-redirect patch, ICE30 suppression) into in-box managed code. +- **Do not adopt archived/CVE-bearing HTML→PDF libs** (wkhtmltopdf/DinkToPdf). Avoid reintroducing + a bundled Chromium (Puppeteer/Playwright) — it fights the charter. +- **Treat `DefaultFontFeatures` as OpenType, never delete it.** Retire only the Graphite *engine + selection* and the `GraphiteFontFeatures.ConvertFontFeatureCodesToIds` ID-conversion path. +- **Keep the forbidden-symbol audit unchanged** — it already lists `GraphiteEngineClass`, + `UniscribeEngineClass`, `FwGrEngine`, `GraphiteSegment`, Gecko symbols, `GeckofxHtmlToPdf` + (`Src/Common/FwAvalonia/FwAvaloniaTests/EngineIsolationAuditTests.cs`). After 10B the audit + graduates from "not on the Avalonia path" to "absent from the whole codebase." +- **Coordinate the LCModel property deprecation with liblcm owners** rather than mutating in-repo. +- **Measure before sunsetting** (the one transition-support practice worth keeping): run the + fixture/LDML scan for G2/G3 (Graphite-feature and Graphite-only) prevalence so removal is + evidence-based, not calendar-based. + +--- + +## 4. Interactions & dependencies + +- **Stage 9 (hard gate, correct).** 10B removal is gated on Stage 9 proving HarfBuzz coverage. The + master plan already states this (§6 Stage 9, §11 decision 1, risk register). Keep it; tighten it + to require the *fixture-scan evidence*, not just a spike opinion. +- **Stage 12 (Avalonia 12) — under-acknowledged dependency.** The clean managed browser/preview + replacement (`Avalonia.Controls.WebView`) ships in Avalonia 12. So **10A's preferred + implementation is Stage-12-aligned**, yet the dependency graph (§5) shows `S10 → S12`, implying + 10 precedes 12. Either (a) 10A uses an interim WebView2-direct integration on the 11.x line, or + (b) 10A's managed-preview piece is sequenced *with/after* Stage 12. This ordering tension is not + currently called out and should be. +- **Stage 13 (cross-platform).** XULRunner is the packaged Windows-only blocker; retiring it (10A) + is a prerequisite for Linux/macOS. Native WebView via Avalonia 12 is cross-platform, so 10A done + right *advances* Stage 13 rather than just unblocking it. +- **Stage 7 / Stage 8 / Stage 6.** The Gecko consumers are spread across these surfaces + (Interlinear/Discourse config preview → Stage 7; dictionary-config preview → Stage 8; parser + trace, MGA help, SFM import help → Stage 6/misc). 10A cannot be a single self-contained senior + task; it is a **cross-cutting sweep** whose per-surface pieces should land *with each owning + surface stage*, leaving 10A to own the startup/shutdown decoupling, the PDF path, the shared + `XCore.HtmlControl` wrapper, and packaging. The plan should say this. +- **Print parity** interacts with Stage 11 shell (print menu/command wiring) and the global undo + model only loosely; print is essentially read-only rendering. + +--- + +## 5. Recommended plan changes + +1. **Split Stage 10 into 10A (Gecko/PDF/preview) and 10B (Graphite removal)** under one parent + epic, with independent gates (10A: surface + packaging + cross-platform; 10B: Stage-9 HarfBuzz + evidence + LCModel coordination). +2. **Add the startup/shutdown XULRunner coupling to the stage scope text** and make "lazy/optional + XULRunner init" the first 10A task — it is the real "Gecko removed" gate. +3. **Note the Stage-12/Avalonia-12 dependency for the managed browser control** explicitly, and + decide interim-WebView2-on-11.x vs sequence-10A-with-12. Update the §5 mermaid edges accordingly + (10A's preview work is not strictly before 12). +4. **Reassign the per-surface Gecko consumers to their owning surface stages** (7/8/6); keep 10A + owning startup, PDF, shared `HtmlControl`, and packaging/installer. +5. **Specify the PDF replacement as `CoreWebView2.PrintToPdfAsync`** (default), explicitly rejecting + wkhtmltopdf/DinkToPdf (CVE/archived) and QuestPDF (not an HTML renderer). +6. **Clarify 10B scope on model properties:** retire Graphite *engine selection* + ID-conversion; + **keep** `DefaultFontFeatures` (OpenType) and `IsGraphiteEnabled` (read-as-false) pending an + LCModel-owner deprecation; flag the liblcm coordination as an explicit dependency. + +### graphite-transition-support supersession handling + +The master plan (§11 decision 1, §2 principle 13, §6 Stage 10) already declares +`graphite-transition-support` superseded by "remove, not warn." **The change itself is not yet +updated** — it still presents Path A (legacy-harbor + graded warning) as the decision and defines +the G0–G3 classifier, warning UX, sunset milestones, and Path B pivot. Concretely: + +- **Archive / add a superseded banner** to `graphite-transition-support/proposal.md`, + `design.md`, `tasks.md`, and its spec delta. Its core premise (keep Graphite, warn on Avalonia, + sunset at M2) is now reversed: there is no Avalonia warning tier because there is no Graphite at + all post-Stage-10. +- **Salvage and re-home three still-valuable pieces into Stage 10B** (do not lose them): + 1. The **G0–G3 classifier / fixture-scan** (tasks 1.3, 1.4, 3.2) — repurposed as + *pre-removal impact evidence* ("how many projects break when Graphite is gone"), feeding the + Stage-9 HarfBuzz-coverage gate. + 2. The **font-replacement policy + outreach** (tasks 3.3, 4.3) — still needed; Awami Nastaliq / + Graphite-only projects now have *no* in-app fidelity path, so the migration/outreach + obligation is *greater*, not lesser. + 3. The **settings-preservation rule** (read storage, never silently rewrite; migrate only on + explicit user action with undo) — still correct under full removal. +- **Update the cross-references** in `lexical-edit-avalonia-migration` (its section-5 re-homing and + `graphite-decommissioning.md` banner) to point at Stage 10B rather than the warning-based change. +- **Reconcile the contradiction in the design's premise:** transition-support assumed Graphite + removal was *coupled to WinForms deletion at M3* and that **legacy surfaces keep rendering + Graphite**. Full removal from the codebase means **legacy/native-Views surfaces also lose + Graphite** (shared `RenderEngineFactory`). The roadmap should state whether full removal waits + until WinForms/native Views are themselves retired (Stage 13) — otherwise Graphite-only projects + break on the *legacy* surface mid-program, which transition-support explicitly promised they + would not. **This is a real sequencing decision the plan has not yet made.** (See Open Questions.) + +--- + +## 6. Open questions / risks + +1. **Does full Graphite removal happen at Stage 10, or is it deferred to Stage 13 (with WinForms/ + native-Views decommission)?** Because `RenderEngineFactory` is shared, deleting the Graphite + branch at Stage 10 degrades *legacy* surfaces too, breaking the transition-support promise that + legacy keeps rendering Graphite until WinForms goes. Likely resolution: **10B disables the + Graphite engine selection on the managed/Avalonia path and stops shipping new Graphite enablement + at Stage 10, but the native `GraphiteEngine` deletion + RegFree/build cleanup lands with the + native decommission in Stage 13.** The plan should make this explicit. (High impact.) +2. **Awami Nastaliq / Graphite-only fonts with no OpenType replacement** — confirmed by the repo's + own design doc. After removal these render wrong everywhere. Needs product-owner sign-off and a + replacement-or-freeze decision per affected project before removal. (High.) +3. **Avalonia 12 WebView2 = a *new* native runtime dependency on Windows.** "Remove Gecko" trades + XULRunner for the WebView2 runtime. Acceptable (WebView2 is OS-serviced, cross-platform, not + bundled) but it is not "zero native" — the plan should acknowledge it rather than imply pure + managed rendering. (Med.) +4. **DOM-interaction surfaces** (config highlight, interlinear config, parser trace) are the real + 10A engineering cost and need a per-surface interop strategy decision (WebView2 + `ExecuteScriptAsync` vs rework-in-Avalonia). (Med.) +5. **LCModel coordination** for `IsGraphiteEnabled`/`DefaultFontFeatures` deprecation is an external + dependency on the liblcm repo and its release cadence. (Med.) + +--- + +## 7. Confidence + +**High** on the repo-grounded inventory (Gecko/PDF call sites, the single `RenderEngineFactory` +decision point, native/build/RegFree footprint, model-property ownership) — these were verified +against the existing `gecko-pdf-audit.md` and read directly. +**Medium-high** on the split recommendation and the salvage list (clear from structure). +**Medium** on the exact sequencing fixes (Stage-10-vs-13 Graphite-deletion timing; Stage-12 +WebView dependency) — these are genuine plan decisions the owner must make; I have surfaced the +tension and the most likely resolution but the choice is theirs. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-11-application-shell-replacement.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-11-application-shell-replacement.md new file mode 100644 index 0000000000..2ca0b7680d --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-11-application-shell-replacement.md @@ -0,0 +1,197 @@ +# Stage 11 Review — Application shell replacement (senior) + +> Reviewer pass over Stage 11 of `complete-migration-program.md` (the IV-Shell stage whose +> body is the existing `fieldworks-avalonia-shell-migration` change). Grounded in the existing +> change (proposal/design/tasks/spec) and the live repo. File paths are absolute-from-repo-root. + +## 1. Scope assessment + +**Stage 11 is too large to be a single epic and must decompose.** The roadmap row collapses +the entire `fieldworks-avalonia-shell-migration` change — which itself carries **10 task groups +and ~20 ADDED requirements** — into one line. That change is a full second migration program, not +a stage peer to (say) Stage 5. The natural seams already exist in the existing change's task +groups and should become **sub-epics**: + +- **11a — App lifetime & windowing.** Avalonia desktop lifetime, main-window ownership, + active-window registry, multi-window, modal owner, shutdown/disposal. (shell tasks 2, 10.1–10.2; + spec "Windowing uses framework-neutral lifetime services", "Avalonia owns final desktop lifetime", + "Shutdown deterministic", "Hidden WinForms startup disallowed".) +- **11b — Main.xml typed-shell compiler.** Importer + typed model + diagnostics + snapshot tests. + (shell tasks 3; spec "Shell XML imports into typed shell definition", "Unsupported constructs are + diagnostic", "Typed definition is the runtime target", "localization survives".) +- **11c — Command/state bridge.** Typed command descriptors + xCore mediator/PropertyTable bridge + + parity validation. (shell tasks 4; spec "Commands are typed descriptors", "XCore mediator bridges", + "Command parity validated".) +- **11d — Shell composition / navigation & panes.** Replace `SilSidePane`/OutlookBar, + `CollapsingSplitContainer`/`MultiPane`; menus/toolbars/status/layout. (shell tasks 5, 6, 7.) +- **11e — Screen registry & area-by-area migration.** (shell tasks 6, 9; spec "screen registry", + "each migrated screen has a manifest", "legacy content explicit and temporary".) +- **11f — Startup/installer/default-switch + FlexUIAdapter retirement.** (shell tasks 10.) + +This is exactly the decomposition the existing change already implies; the roadmap just needs to +say so. Each sub-epic is independently gateable and 11b/11c are largely non-UI and unblock the rest. + +**One scope contradiction to fix:** Stage 11's row pulls in "compile Main.xml" and "route mediator +through the typed command bridge," but Stage 2's plan text (§6 Stage 2) already says *"Stand up the +XCore mediator/PropertyTable bridge seam (`IXCoreCommandBridge`)"* and *"Pull forward the contract +layer of `fieldworks-avalonia-shell-migration` (window/dialog ownership contracts)."* The +Stage 2 review confirms the **ports/contracts** are designed in Stage 2 and **implemented** in +Stage 11. The roadmap should state this split explicitly so Stage 11 is not read as re-defining the +lifetime/command contracts — it *implements and shell-scopes* them. (See cross-stage conflicts below.) + +## 2. Feasibility (repo-grounded) + +The shell coupling is real and deep — feasible but senior-only and multi-quarter: + +- **Entry point is WinForms-bound.** `Src/Common/FieldWorks/FieldWorks.cs` is `[STAThread] static int Main` + → `Application.Run()` (line ~383), with `Form.ActiveForm`/`Form dialogOwner` threaded through ~15 + project lifecycle methods (`OpenExistingProject`, `ChooseLangProject`, `CreateNewProject`, + `BackupProject`, `RestoreProject`, `ArchiveProjectWithRamp`, `HandleRestoreRequest`, …). The spec's + "Hidden WinForms startup is disallowed" / "Avalonia owns final desktop lifetime" requirements are + the hardest part: every one of these `Form`-typed seams must move behind the framework-neutral + dialog-owner/lifetime ports (Stage 2) before the Avalonia `Main` can exist. **11a is the critical-path + long pole inside Stage 11.** +- **`XWindow` is `: Form`.** `Src/XCore/xWindow.cs` (2,498 lines) *is* a WinForms Form holding + `CollapsingSplitContainer m_mainSplitContainer`, `RecordBar`, `m_sidebar`, `StatusBar`, + `IUIAdapter` rebar/sidebar/menubar adapters, and a `Mediator`/`PropertyTable`. `FwXWindow` + (`Src/xWorks/FwXWindow.cs`, 2,588 lines) derives from it (`IFwMainWnd, ISettings, IRecordListOwner`). + Replacing the shell means re-homing all of this; it cannot be incremental within one window + instance, which is why area-by-area screen migration (11e) + a parallel Avalonia main window is the + only viable path. +- **Main.xml compiler is tractable and bounded.** `DistFiles/Language Explorer/Configuration/Main.xml` + is **989 lines with 118 ``s** pulling **51 config XML files totaling ~15.7k lines**. The + command/choice model already lives in typed C# (`xCoreInterfaces/Command.cs` 768 lines, + `ChoiceGroup.cs` 808 lines, `Inventory.cs` 1,709 lines) — so 11b compiles XML into types that + *already have a runtime peer*. This is the same deterministic-import + snapshot pattern proven by + the Lexical-Edit view-definition compiler (`Src/Common/FwAvalonia/ViewDefinition/`); high + confidence it transfers. The diagnostics-not-silent-omission requirement is the right discipline. +- **Command bridge already seeded.** `IXCoreCommandBridge` and `IRecordNavigationContext` exist in + `Src/Common/FwAvalonia/Seams/ISeams.cs` and are consumed at the region edge + (`Src/xWorks/RecordEditView.cs`, `RecordClerkNavigationContext.cs`). `seam-catalog.md` §1 + explicitly reserves *"shell-scope wiring happens in the shell phase, not per region"* — so 11c is a + documented promotion of an existing seam, not a green-field design. Feasible. +- **Navigation/pane replacements are owned-control work.** `SilSidePane/` (OutlookBar + ~20 files), + `MultiPane.cs` (674), `CollapsingSplitContainer.cs` (667), `PaneBarContainer`, `RecordBar`. None + have stock Avalonia equivalents; 11d is custom-control build comparable in weight to Stage 3's grid. +- **FlexUIAdapter retirement is ~3.2k lines** across `Src/XCore/FlexUIAdapter/` (Menu/Bar/Sidebar/ + Toolbar/PaneBar adapters). Its *default* removal is the spec's `10.7`; deferring removal until after + the default switch is correct. + +## 3. Best practices (shell strangling) + +- **Strangle the shell at the area/tool boundary, run two main windows side by side.** The existing + change's "screen registry + per-screen manifest + explicit legacy host for non-migrated screens" + (Decision 5, spec "screen registry"/"legacy content explicit and temporary") is textbook Strangler + Fig and matches §2 principle 1. Keep it. +- **Compile config to types, keep XML as import/audit only** (Decision 3, spec "typed definition is the + runtime target"). This is the same bet that worked for view-definitions; reuse the cache-keyed, + off-thread, deterministic-snapshot machinery rather than reinventing it. +- **Bridge commands before replacing them** (Decision 4). Preserve command IDs/shortcuts as the stable + contract so menus/toolbars/context-menus can be re-skinned (Fluent restyle, program decision 4) + while behavior parity is asserted by command-target tests, not pixels. +- **One UI thread / one message loop until the switch.** Modality stays WinForms-owned through the + whole transition (architecture-patterns §7, `dialog-ownership.md`). Avalonia desktop lifetime only + becomes real at the *default switch* (11f / shell task 10.6), gated by full-app smoke + (spec "Full-app smoke gates protect default switch"). +- **Headless-first.** shell task 5.4 (Avalonia.Headless for shell creation, nav host swap, command + dispatch, dialog ownership, focus traversal, pane state) keeps deep behavior testable without UI + automation — consistent with the program's harness discipline. + +## 4. Interactions & dependencies + +**Stage 5 ↔ Stage 11 modal-dialog ordering verdict: the ordering is CORRECT — dialogs (Stage 5) +must precede the shell (Stage 11), not the reverse.** The prompt's framing ("this unblocks Avalonia +modal windows (Stage 5 dialogs!)") inverts the actual constraint and would be a real bug if taken +literally. The repo evidence: + +- On the **coexistence path (Avalonia 11.x + .NET FW 4.8)** Avalonia modal windows are **not + supported**; the only supported modal pattern is a **WinForms `Form` owning embedded Avalonia + content** (architecture-patterns §7; `dialog-ownership.md`; corroborated in the Stage 2 review's + sources). Stage 5 therefore deliberately ships **Avalonia dialog *content* inside WinForms-owned + modal forms** (roadmap §6 Stage 5 "Coexistence rule"; existing change does not require Avalonia + modality for dialogs). Stage 5 does **not** need a real Avalonia modal window and is **not blocked** + by Stage 11. +- Conversely, Stage 11's `Window.ShowDialog` (true Avalonia modality) only becomes available once the + **Avalonia desktop lifetime owns the main window** — i.e. at the Stage 11 default switch. So + Avalonia-native modal windows are a *Stage 11 deliverable*, used *after* the shell exists; they are + not a prerequisite that Stage 5 waits on. +- The dependency arrow is therefore **5 → 11** (the roadmap graph already draws `S5 … → S11`), and + Stage 11's gate ("enough of 5–8") is right: high-frequency dialogs migrated as WinForms-owned + content first, then the shell flips them to Avalonia-owned modality. **Action:** make this two-phase + dialog lifecycle explicit in Stage 11 — re-host the already-migrated Stage 5 dialog *content* from + WinForms-owned to `Window`-owned modality is a distinct 11a task, not free. + +**Stage 12 (runtime jump) ordering vs Stage 11: correct and important.** §4/§5 sequence +`11 → 12 → 13`: replace the WinForms shell first so the .NET 10 + Avalonia 12 jump ports *surviving* +managed code, not WinForms about to be deleted (principle 14). One caveat to record in Stage 11: +because `XWindow : Form` and `FieldWorks.cs` use `Application.Run()`, the **co-running WinForms shell +keeps the whole process on net48/Avalonia 11.x through all of Stage 11** (one CLR per process). New +Stage 11 Avalonia shell code must be written **Avalonia-12-ready** (avoid APIs removed in 12) exactly +as §5 demands, or 11→12 incurs avoidable rework. + +**Stage 2 dependency.** Stage 11 *consumes* the lifetime/dialog-owner/dispatcher/command ports that +Stage 2 designs (Stage 2 review §4–5; shell task 2.1 "following `avalonia-ui-scheduler` and +`avalonia-lifetime`"). Stage 11 should not redefine `IUiScheduler`/`IRegionLifetime` (already in +`ISeams.cs`). + +## 5. Recommended plan changes + +1. **Decompose Stage 11 into the six sub-epics (11a–11f)** above and add an internal dependency note: + `11a (lifetime) + 11b (compiler) + 11c (command bridge)` are foundational and can run in parallel; + `11d (composition)` depends on 11b/11c; `11e (screens)` depends on 11d + per-area Track-II + completion; `11f (default switch + FlexUIAdapter removal)` is last. +2. **State the Stage 2/Stage 11 split explicitly in the roadmap row:** Stage 2 = *contract/port design + + seam stand-up*; Stage 11 = *implementation + shell-scoping + default switch*. Remove any + implication that Stage 11 re-creates the lifetime/command contracts. +3. **Add an explicit "dialog modality re-host" task to 11a:** migrate Stage 5's WinForms-owned Avalonia + dialog *content* to Avalonia `Window`-owned modality at the switch; this is the only point where + true Avalonia modal windows appear. Tie it to the full-app smoke gate. +4. **Make the area-by-area gate concrete:** Stage 11's exit gate "enough of 5–8" should enumerate which + areas must be Avalonia (Lexicon, then Words/Grammar/Notebook/Lists) before the default switch, and + which may remain explicit legacy islands with manifests (spec "legacy content explicit and + temporary"). Texts&Words/Interlinear (Stage 7, Views-engine-heavy, depends on Stage 9) is the most + likely legacy island at first switch — call that out. +5. **Reuse, don't rebuild, the compiler infra:** point 11b at + `Src/Common/FwAvalonia/ViewDefinition/` (cache key, off-thread, deterministic snapshot) so the + Main.xml compiler shares the proven pipeline. +6. **Add Open Question resolution owners.** The existing change's 5 open questions (runtime target; + partner XML extension points; docking library vs owned; browser/PDF engine; first-switch blocking + screens) are still unanswered and each gates a sub-epic. Map them: OQ1→11a/Stage 12, OQ3→11d, + OQ4→Stage 10, OQ5→11e gate. + +## 6. Open questions / risks + +- **OQ-A (FlexUIAdapter half-life):** removing it from the *default* path (11f) is clean, but ~3.2k + lines and `IUIAdapter` are referenced by `XWindow`; confirm no migrated Avalonia screen transitively + pulls a `BarAdapterBase`/`MenuAdapter` during coexistence (active-host contract should catch this). +- **OQ-B (docking):** `MultiPane`/`CollapsingSplitContainer`/`SilSidePane` have no stock Avalonia + peer. Existing change task 7.5 says "evaluate a docking library only if owned controls cannot meet + workflows." Risk: under-scoping 11d. Spike 11d alongside Stage 3's owned-control work; record the + build-vs-library decision with a pivot trigger. +- **OQ-C (partner extension XML):** Decision/Open-Q2 — third-party `Main.xml` includes/extension hooks. + The typed compiler's "diagnostics not silent omission" is the right safety net, but a partner whose + extension has no Avalonia equivalent is a hard blocker for *their* default switch; needs a policy. +- **OQ-D (multi-window/active-window registry):** `FieldWorks.cs` tracks `s_activeMainWnd` and + `Form.ActiveForm as IFwMainWnd`. The Avalonia active-window registry must preserve multi-project / + multi-window semantics; this is subtle and under-tested in the existing tasks (only 6.4 nav tests). +- **Risk — "hallucinated parity" at shell scale.** A shell smoke test that *launches and clicks + around* is not command-parity evidence. Enforce command-target/shortcut parity tests (shell task 4.3, + spec "command parity validated") as the gate, per §7 DoD and `parity-evidence.md` language rules. +- **Risk — pulling the shell too early** (roadmap §9 names this, likelihood Low/impact High). The + "enough of 5–8" gate is the mitigation; keep it hard and enumerated (change #4). + +## 7. Confidence + +**High** that Stage 11 must decompose, that the existing change already supplies the seams, and that +the **Stage 5→11 dialog ordering is correct** (dialogs ship as WinForms-owned Avalonia content first; +Avalonia-native modality is a Stage 11 output) — grounded in `dialog-ownership.md`, +architecture-patterns §7, the Stage 2 review, and the `FieldWorks.cs`/`xWindow.cs` WinForms coupling. + +**High** on the Stage 11→12 ordering rationale (port surviving code, not soon-deleted WinForms). + +**Medium** on sizing of 11d (navigation/pane owned controls) and 11a (lifetime re-homing of the ~15 +`Form dialogOwner` seams in `FieldWorks.cs`) — both are larger than the single-line roadmap row +suggests and warrant their own spikes. + +**Medium-low** on partner/extension XML and multi-window active-window registry parity — under-specified +in the existing tasks and dependent on unresolved open questions. diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-12-runtime-modernization.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-12-runtime-modernization.md new file mode 100644 index 0000000000..dc00b8757f --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-12-runtime-modernization.md @@ -0,0 +1,280 @@ +# Stage 12 Review — Runtime & toolchain modernization (.NET 10 + Avalonia 12) + +Reviewer: senior migration review (Claude, Opus 4.8). Date: 2026-06-15. +Scope reviewed: `complete-migration-program.md` §2 principle 14, §4 table row 12, §5 +sequencing note, §6 Stage 12 detail, §9 risk register, §11 decisions; the as-built +toolchain (`Directory.Build.props`, `Src/Directory.Build.props`, `Directory.Packages.props`, +`FieldWorks.proj`, `build.ps1`); the actual TFM landscape across `Src/`; the +`fieldworks-managed-netfx-review` skill; Stage 1 and Stage 2 reviews; external research on +Avalonia 12 supported TFMs / breaking changes and .NET Framework → .NET 10 migration tooling. + +--- + +## 1. Scope assessment + +Stage 12 bundles four deliverables: (a) port surviving managed code net48 → .NET 10; +(b) bump Avalonia 11.3.x → 12; (c) coordinated whole-process bump behind green build/test/CI +with test-project TFM retargeting; (d) declare cross-platform prerequisite satisfied. The +**late sequencing is correct**, and the bundling is **defensible but should be explicitly +ordered internally**, not treated as one atomic flip. + +Findings: + +- **The late placement is well-justified and survives scrutiny.** Principle 14 / §5 are right: + the runtime jump should port surviving managed code, not WinForms that Stage 13 deletes. The + one-CLR constraint (one process, one runtime, one Avalonia major) makes a *coordinated* bump + mandatory — you cannot trickle net48→net10 per project while a single in-process + `WinFormsAvaloniaControlHost` spans the boundary. So "late + coordinated" is the only coherent + shape. Confirmed correct. + +- **(a) and (b) are genuinely coupled by the one-CLR fact and should NOT be split into + separate stages — but they are two distinct work-streams inside the one epic.** Avalonia 12 + *requires* net8+ (drops net48 — see §2), so the Avalonia bump is *impossible* until the net10 + port lands; conversely there is no reason to bump Avalonia while still on net48 (12 won't + load). They must ship in the same coordinated cutover. The right structure is **one epic, two + sequenced internal work-streams**: WS-1 net48→net10 port (gates first), WS-2 Avalonia 11→12 + bump (depends on WS-1), with a single CI gate flipping both. Recommend the plan name this + internal ordering explicitly rather than implying simultaneity. + +- **The plan's test-project framing is stale and should be corrected.** Stage 12 text says + "retarget `net48`/`net8` multi-targeting in test projects," and the + `fieldworks-managed-netfx-review` skill is built around a "net48 / C#7.3 vs SDK-style net8 + boundary." **Neither matches the repo today.** A full sweep of `Src/` found **130 projects, all + `net48`, zero net8/net9/net10, and no multi-targeting + anywhere** (`grep -rhoiE "" --include="*.csproj" Src/` → only `net48`). + `Directory.Build.props:62` even defaults C# `LangVersion` to **8.0**, not 7.3 (so the skill's + "C#7.3" red-flag list is also stale). There is no net8 lane to "retarget from"; Stage 12 is a + **net48 → net10 port of the whole managed tree**, not a multi-targeting consolidation. This is + a larger and simpler-shaped job than the plan's wording implies (no dual-TFM bookkeeping to + unwind — but also no partial-net8 head start to build on). + +- **A hidden, sizeable sub-deliverable is missing from the scope: NUnit 3 → 4.** Avalonia 12's + headless test packages require **NUnit 4** (Avalonia docs: "Headless … NUnit upgraded to v4"). + The repo pins **NUnit 3.14.0** (`Directory.Packages.props:174`) across ~40 test projects, and + `Src/Directory.Build.props:32-34` carries the explicit note "When SIL packages upgrade to NUnit + 4, update this … and add global using aliases for `ClassicAssert`." NUnit 3→4 is a breaking + API change (`Assert.That` model, `ClassicAssert`) touching every test assembly, **coupled to** + both the Avalonia 12 bump and the SIL.TestUtilities upgrade. This belongs explicitly in Stage + 12 scope as WS-3, gated by the SIL package family moving to NUnit 4. + +- **Residual-WinForms-on-net10 scope is correct but should state the dependency on Stage 13 + ordering.** The plan says residual WinForms moves to WinForms-on-.NET 10 (Windows-only). That is + feasible (WinForms is a supported net10 workload), but the *amount* of residual WinForms depends + on how much of Stages 11/13 has landed. If Stage 12 runs strictly before Stage 13's deletions, + the surviving WinForms surface — and the C++/COM interop it carries — is still non-trivial. + +## 2. Feasibility (repo-grounded + web) + +**The single governing fact is verified in-repo and is the gate on timing: Avalonia 12 cannot +run on net48, so the Avalonia bump *forces* the .NET 10 port first.** + +- `Directory.Packages.props:183-213` pins **Avalonia 11.3.17** with the explicit comment: + *"Pinned to 11.3.x because it still ships netstandard2.0 assemblies and can therefore load on + .NET Framework 4.8. Avalonia 12.x dropped netstandard2.0 (net8+ only) and cannot host in-process + on net48."* `Src/Common/FwAvalonia/FwAvalonia.csproj` and `FwAvaloniaTests` both target + **net48**. So during coexistence the whole process is correctly net48 + Avalonia 11.3.17. +- External confirmation: Avalonia 12 **drops netstandard2.0 and .NET Framework; minimum is .NET + 8, recommended .NET 10** (Avalonia 12 breaking-changes doc; discussion #18606 "Dropping support + for .NET Framework 4.x and netstandard2.0 in 12.0"). This *validates the program's whole timing + premise*: the Av11→12 bump is **physically blocked** until the process leaves net48. Stage 12's + late placement is not just tidy — it is the earliest point the bump is even possible, given the + one-CLR rule and the decision to keep WinForms hosting in-process until Stage 11. + +- **net48 → net10 effort for FieldWorks is moderate-to-high but de-risked by prior stages:** + - *C++ interop / registration-free COM:* The native Views/Kernel/Generic engine is `.vcxproj` + (`Src/views/views.vcxproj`, `Src/Kernel/Kernel.vcxproj`, etc.) consumed via reg-free COM. + .NET 10 fully supports COM interop and reg-free COM activation contexts on Windows, and + `FieldWorks.proj` already builds native-first (`BuildNativeFirst` target, line 49). The + interop *surface* is unchanged by the runtime; the risk is in P/Invoke/marshalling defaults + (e.g. `CharSet`, `BinaryFormatter` removal, `System.Drawing.Common` becoming Windows-only + package) rather than the COM model itself. **Crucially, by Stage 12 the native *UI/render* + COM surface is largely gone** (Stage 9 replaced Views; Stage 13 decommissions the rest), so + the interop that survives the port is mostly the *non-UI* linguistics services + (Kernel/Generic/ICU/XAmple) behind service seams — a much smaller, more stable interop set. + - *Dependencies already removed by Stage 12:* Gecko/XULRunner and Graphite are gone (Stages 10, + principle 13) — these were among the worst net48-coupling offenders (native, x86-ish, old + interop). Their prior removal materially shrinks the port. + - *Mechanical bulk:* 130 SDK-style-ish net48 projects retargeted to net10. `.NET Upgrade + Assistant` automates the TFM bump and flags blockers, but its value here is limited because + the projects are already SDK-style and CPM-managed; the real work is API-compat + (`System.Web`, `System.Drawing`, `AppDomain`/remoting, BinaryFormatter, config-system + differences, `System.Configuration` app.config model) and binding-redirect removal. + - *Build/CI:* `build.ps1` / `FieldWorks.proj` are MSBuild-Traversal and TFM-agnostic; the bump + is a props change plus per-project fixes, not a build-system rewrite. `Directory.Build.props` + already centralizes `LangVersion`, `RuntimeIdentifiers`, x64 — a good single chokepoint. + +- **Avalonia 11.3.17 → 12 breaking changes that hit this repo specifically** (Avalonia 12 + breaking-changes doc): + - *Compiled bindings on by default* — aligns with Decision 3 (dialogs use `x:CompileBindings`); + low friction, mostly positive. + - *`UseSkia()` must now also call `UseHarfBuzz()`* — directly relevant: the program's managed + shaping (principle 13, HarfBuzz-only) must wire `UseHarfBuzz()` explicitly at the Av12 bump. + - *Clipboard/drag-drop rewrite:* `IDataObject` → `IAsyncDataTransfer`/`DataTransfer`, + `DoDragDrop()` → `DoDragDropAsync()`. The repo has clipboard/drag-drop seams + (`ISeams.cs`) — these seam signatures will change at Av12. + - *Binding internals:* `IBinding`/`InstancedBinding` removed → `BindingBase`/ + `BindingExpressionBase`; binding-plugin system removed. Owned controls that construct bindings + programmatically (`FwFieldControls`, `FwMultiWsTextField`) need an audit. + - *Focus/gestures:* `KeyboardNavigationHandler` → `FocusManager`; `Gestures` no longer public; + `GotFocusEventArgs` → `FocusChangedEventArgs`. The host's focus memento / directional-key + bypass (`LexicalEditHostControl`) touches this area. + - *DevTools, TopLevel interfaces, `Screen` now abstract, `TitleBar`/`CaptionButtons` removed* + — shell-relevant (Stage 11 output), low impact on field surfaces. + - *Direct2D1 removed (Skia only):* repo already uses Skia (`SkiaSharp 2.88.9`); no impact. + - *Headless test runner: NUnit 3 → 4* — see §1; the largest mechanical Avalonia-coupled cost. + + Net: the Av11→12 delta is real but **bounded and mostly seam-localized**, *provided* the + "Av12-ready" discipline (principle 14, Stage 2 rec. 6) was actually honored through Stages 1-11. + +## 3. Best practices (large .NET Framework → modern .NET + major UI-framework bump) + +1. **Bump the runtime first, the UI framework second, in one coordinated cutover.** Get the whole + tree compiling/testing green on net10 *while still on Avalonia 11.3.17 if it loaded on + net10* — but note 11.3.x is netstandard2.0, so it *does* load on net10, giving a valuable + intermediate checkpoint: **net10 + Avalonia 11** is a legal, testable state. Use it. Land + net10 with Avalonia unchanged, get green, *then* bump Avalonia 12. This decomposes the two + biggest risk sources instead of debugging them simultaneously. (This intermediate state is the + single most useful tactic available and the plan should mandate it.) +2. **Leaf-first port order.** Standard guidance (MS WinForms migration docs, Upgrade Assistant): + port no-dependency projects first (Utils/Kernel-wrapper/test-utilities), then up the graph to + xWorks/LexText/shell. CPM + the central `Directory.Build.props` make a global TFM switch + trivial, but per-project compat fixes must still go leaf-first to keep the tree buildable. +3. **Use `.NET Upgrade Assistant` for *analysis*, hand-port the fixes.** Run its analyzer to + produce the blocker report (BinaryFormatter, `System.Web`, remoting, config) but don't trust + its automated edits across 130 projects under `TreatWarningsAsErrors=true`; treat the report as + a checklist, port deliberately. +4. **Pin the API-compat hazards as explicit tasks:** BinaryFormatter removal (also an Avalonia 12 + clipboard change), `System.Drawing.Common` Windows-only package + runtime config, + `System.Configuration`/app.config model differences, `AppDomain`/remoting usage, `CodeDom`, + any `BinaryFormatter`-based caching/serialization. Search the tree for each before the port. +5. **Keep the one-CLR / one-Avalonia-major invariant as a CI assertion**, not a convention: a + build-time check that no project targets a different TFM or Avalonia major than the rest. +6. **Treat NUnit 3→4 as its own reviewed work-stream** (global `ClassicAssert` aliasing per the + in-repo TODO), gated on SIL.TestUtilities shipping NUnit 4; do not let it ride silently on the + Avalonia bump PR. +7. **Apply `fieldworks-managed-netfx-review` — but update the skill first.** The skill's central + premise (net48 *coexisting with* SDK-style net8) is now historically inaccurate for this repo; + at Stage 12 the relevant boundary is **net48 → net10 (single target), C#8→latest, NUnit + 3→4**. Refresh the skill in the same PR (its own "Keep This Skill Current" clause requires it). +8. **Verify with repo scripts only** (`./build.ps1`, `./test.ps1`) on both the intermediate + net10+Av11 checkpoint and the final net10+Av12 state — never bare `dotnet build`. + +## 4. Interactions & dependencies + +- **Depends on Stage 11 (shell) and most of 5-10 — correctly stated.** The value of "port + surviving code, not deleted WinForms" is only realized if the WinForms shell (11) and surfaces + (5-8) are gone first. If Stage 11 slips, Stage 12 either slips with it or wastes effort porting + WinForms that Stage 13 deletes. **Hard gate: Stage 12 must not start until Stage 11 has retired + the WinForms shell**, else the residual-WinForms-on-net10 surface balloons. +- **Gates Stage 13 (cross-platform) — correctly the prerequisite.** net48 is Windows-only; net10 + is the thing that unblocks Linux/macOS. The plan's claim that cross-platform is *held* to Stage + 13 by decision (not by capability) is consistent: Stage 12 delivers the *capability*, Stage 13 + spends the validation cost. Good separation. +- **The "new code is Av12-ready through Stages 1-11" claim is plausible but partially unverifiable + and is the chief sequencing risk.** Stages 1-11 build on **Avalonia 11.3.17** (the only version + that loads on net48). "Av12-ready" can only mean *avoiding APIs Avalonia 12 removes* — and + several removed-in-12 APIs are exactly what coexistence code must use on 11: `IDataObject` + clipboard/drag-drop, programmatic `IBinding`, `KeyboardNavigationHandler`/`Gestures`, + `GotFocusEventArgs`. **You cannot fully avoid 11-era APIs while running on 11**; the realistic + posture is "minimize and centralize behind seams so the Av12 delta is localized," not "write + code that needs no Av12 changes." Recommend the plan downgrade the claim from "Av12-ready + (no changes needed)" to "**Av12-delta-localized** (11-only APIs confined to named seams: + clipboard, drag-drop, binding construction, focus/gestures, theming)" and add a Stage-2/Stage-11 + exit check that those APIs appear *only* inside those seams. This converts a vague promise into a + testable invariant and is the best mitigation for "breaking changes ripple late" (§9 risk). +- **Theming coupling with Stage 2 (already flagged in Stage 2 review rec. 6):** Fluent/ControlTheme + resource APIs changed in 12; the Stage 2 "Av12-ready theming" gate is the upstream control. If + that gate held, Stage 12 theming churn is small; if not, it surfaces here. +- **HarfBuzz coupling:** Av12 requires explicit `UseHarfBuzz()` after `UseSkia()`. Since principle + 13 already mandates HarfBuzz-only shaping (Graphite removed in Stage 10), this is a one-line + wiring change that *aligns* with the program rather than fighting it — but it must be on the + Stage 12 checklist or text shaping silently breaks at the bump. + +## 5. Recommended plan changes + +1. **Re-word Stage 12's test-project line.** Replace "retarget `net48`/`net8` multi-targeting in + test projects" with the accurate task: "**retarget all ~130 net48 projects (product + test) to + net10; there is no existing net8 lane**." State that the repo is uniformly net48 today + (verified) so this is a single-target port, not a multi-targeting consolidation. +2. **Make the internal ordering explicit: WS-1 net10 port → (intermediate green net10+Av11 + checkpoint) → WS-2 Avalonia 12 bump → WS-3 NUnit 3→4.** Keep it one epic; sequence the + work-streams; mandate the net10+Av11 intermediate checkpoint as a defined gate. +3. **Add NUnit 3→4 as a named Stage 12 deliverable**, coupled to the SIL.TestUtilities NUnit-4 + upgrade and the Avalonia 12 headless requirement; reference the existing in-repo TODO + (`Src/Directory.Build.props:32-34`). +4. **Add an explicit Avalonia-12 breaking-change checklist** to the stage: `UseHarfBuzz()` wiring, + clipboard/drag-drop seam rewrite (`IDataObject`→`IAsyncDataTransfer`), binding-construction + audit, focus/gesture API migration, DevTools package swap, `Screen`-abstract usage. Cite the + Avalonia 12 breaking-changes doc. +5. **Downgrade the "Av12-ready" principle wording to "Av12-delta-localized"** (§2 principle 14, + §5, §9 risk row) and add an upstream exit-gate in Stages 2/11 that confines 11-only-removed + APIs to named seams. This is the real mitigation for the late-ripple risk. +6. **Add a hard "Stage 11 shell retired" entry gate** to Stage 12 so the residual-WinForms-on- + net10 port surface stays small; if Stage 11 slips, Stage 12 slips with it (do not start the + port over a still-WinForms shell). +7. **Refresh `fieldworks-managed-netfx-review` in the same PR** to reflect the net48→net10 single + target, C#8→latest, and NUnit 3→4 reality (its current net48-vs-net8 framing is stale). +8. **Add a CI invariant** asserting one TFM (net10) and one Avalonia major (12) across the whole + solution after the bump — encodes the one-CLR rule as a test. +9. **Pin the API-compat hazard list as pre-port discovery tasks** (BinaryFormatter, System.Drawing + Windows-only, System.Configuration, AppDomain/remoting); run `.NET Upgrade Assistant` analysis + for the blocker report but hand-port the fixes. + +## 6. Open questions / risks + +- **OQ-1 (intermediate checkpoint):** Will the program adopt the **net10 + Avalonia 11.3.17** + intermediate green checkpoint (legal because 11.3.x is netstandard2.0)? Strongly recommended — + it decouples the two biggest risk sources. *Verify* 11.3.17 actually restores/loads on net10 in + a spike before committing to it as a gate. +- **OQ-2 (NUnit 4 timing):** Is SIL.TestUtilities on NUnit 4 by the time Stage 12 runs? If not, + Stage 12 is blocked on an upstream dependency for the Avalonia-12 headless requirement, or must + carry a temporary NUnit-version split. Track this as an external dependency now. +- **OQ-3 (residual WinForms size):** How much WinForms genuinely survives into Stage 12 vs. is + deleted in Stage 13? This determines the WinForms-on-net10 port cost and the surviving + C++/COM-interop surface. Needs a census at Stage 11 close. +- **Risk — net48→net10 API-compat surprises (Med/High):** BinaryFormatter, System.Drawing, + config, remoting. *Mitigation:* pre-port discovery tasks (rec. 9) + leaf-first port + + intermediate checkpoint. +- **Risk — Av12 breaking changes ripple late (Med/Med, but under-mitigated as written):** the + "Av12-ready" claim is partially unachievable on Av11. *Mitigation:* rec. 5 (delta-localized + + seam-confinement exit gate upstream). +- **Risk — NUnit 3→4 churn across ~40 test projects (Med/Med):** hidden in current scope. + *Mitigation:* rec. 3, dedicated work-stream, `ClassicAssert` aliasing. +- **Risk — Stage 11 slip drags the WinForms surface into the port (Med/High):** *Mitigation:* + rec. 6 hard entry gate. +- **Risk — C++/CLI / reg-free COM marshalling regressions on net10 (Low/Med):** interop model is + preserved on net10; surviving interop is mostly non-UI linguistics services by Stage 12. + *Mitigation:* native-first build already enforced; spike the Kernel/Generic/ICU interop early. + +## 7. Confidence + +**High** on the governing constraint and timing: Avalonia 12 drops net48 (verified in +`Directory.Packages.props` comment + Avalonia 12 docs/discussion #18606), so the Av11→12 bump is +*physically blocked* until the net10 port — the late, coordinated, one-CLR sequencing is not just +reasonable, it is forced. **High** that the plan's "net48/net8 multi-targeting in test projects" +wording is stale: the repo is uniformly net48 (130 projects, zero net8/9/10, verified). **High** +that NUnit 3→4 is a missing, coupled sub-deliverable. **Medium** on net48→net10 *effort* (depends +on the residual-WinForms and surviving-COM census, which only Stage 11 close can size) and on +whether the "Av12-ready" discipline actually held through Stages 1-11 — both are the chief +remaining uncertainties, and both are convertible to named gates by the recommendations above. +**Recommendation: keep Stage 12 as one late epic; do NOT split into separate stages (the one-CLR +rule couples them), but sequence three internal work-streams (net10 port → Av12 bump → NUnit 4) +with a net10+Av11 intermediate checkpoint, and fix the stale test-TFM wording.** + +--- + +### Sources (external) +- Avalonia 12 breaking changes (TFMs, removed APIs, NUnit 4, UseHarfBuzz, clipboard/drag-drop, focus): https://docs.avaloniaui.net/docs/avalonia12-breaking-changes +- Avalonia 12 release / supported platforms (net8 min, net10 recommended): https://avaloniaui.net/blog/avalonia-12 ; https://docs.avaloniaui.net/docs/supported-platforms +- "Dropping support for .NET Framework 4.x and netstandard2.0 in 12.0" (rationale, <4% telemetry): https://github.com/AvaloniaUI/Avalonia/discussions/18606 +- TreeDataGrid v12 breaking changes (API stability refactor): https://docs.avaloniaui.net/controls/data-display/structured-data/treedatagrid/breaking-changes-v12 +- MS: Upgrade a .NET Framework WinForms app to .NET (incremental, leaf-first): https://learn.microsoft.com/en-us/dotnet/desktop/winforms/migration/ +- .NET Upgrade Assistant (analysis/blocker report for net48→modern): https://learn.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview + +### Sources (in-repo, verified) +- Avalonia 11.3.17 pin + net48/netstandard2.0 rationale: `Directory.Packages.props:183-213` +- Uniform net48 (no net8) + C#8 default: `grep` over `Src/**/*.csproj` (130× net48, 0× net8/9/10); `Directory.Build.props:62` +- NUnit 3.14.0 pin + "upgrade to NUnit 4" TODO: `Directory.Packages.props:174`; `Src/Directory.Build.props:32-34` +- FwAvalonia/FwAvaloniaTests target net48: `Src/Common/FwAvalonia/FwAvalonia.csproj`, `FwAvaloniaTests/*.csproj` +- Native-first build order (COM/codegen prerequisite): `FieldWorks.proj:49` (`BuildNativeFirst`) +- Native UI/render engine still .vcxproj: `Src/views/views.vcxproj`, `Src/Kernel/Kernel.vcxproj` diff --git a/openspec/changes/avalonia-migration-roadmap/reviews/stage-13-cutover-cross-platform.md b/openspec/changes/avalonia-migration-roadmap/reviews/stage-13-cutover-cross-platform.md new file mode 100644 index 0000000000..996482c312 --- /dev/null +++ b/openspec/changes/avalonia-migration-roadmap/reviews/stage-13-cutover-cross-platform.md @@ -0,0 +1,203 @@ +# Stage 13 Review — Final cutover, native decommission & cross-platform enablement + +> Reviewer pass over Stage 13 of `complete-migration-program.md` (§4 row 13, §6 Track IV). +> Grounded in the as-built repo (`Src/views/`, `Src/Common/ViewsInterfaces/`, `Src/CacheLight/`, +> `Src/Common/SimpleRootSite|RootSite`, `Src/Common/Controls/{DetailControls,XMLViews}`, +> `Src/Common/FwAvalonia/`, `FLExInstaller/`) and the `retire-linux-era-view-shims` change. +> Status: planning review only; no code/behavior change proposed here. + +## Scope assessment + +Stage 13 bundles **five** distinct, high-blast-radius workstreams under one "senior" epic: +(1) flip the global default to Avalonia; (2) delete the WinForms surface layer (shell, dialogs, +DataTree/Slice, SimpleRootSite/RootSite, XMLViews, interop spine); (3) decommission native C++ +UI/render (`Src/views/`, `ManagedVwDrawRootBuffered`) and the `IVwRootBox`/`IVwGraphics`/`IVwEnv` +COM surface; (4) cross-platform Linux/macOS build+headless+smoke; (5) final accessibility/ +localization/performance gates. **This is too much for one stage and should split.** The five +have different reversibility, different skill profiles, and different failure signatures: + +- **Cutover (flip + staged rollback)** is a *runtime config* change — reversible in minutes, no + code deleted. This is the highest-value, lowest-cost, most-reversible action. +- **Decommission (delete WinForms + native UI)** is *destructive and irreversible-by-design*. + Deleting code while a freshly-flipped default is still proving itself in the field removes the + rollback path Stage 13 nominally relies on. Cutover and deletion **must not** be the same step, + let alone the same epic. +- **Cross-platform enablement** is *additive net-new validation* (CI lanes, OS smoke, packaging + for two new OSes) that depends on the deletion being *done* but shares no code with it. + +**Recommended split into three sequenced epics:** +- **13a — Cutover & bake:** flip default to Avalonia behind the existing `UIMode` setting via a + staged rollout; WinForms code stays in the tree as the live rollback for a defined bake period. +- **13b — Decommission:** delete WinForms/native UI only *after* 13a's bake gate is green; this is + the irreversible step and deserves its own epic with its own ordered deletion plan. +- **13c — Cross-platform & final gates:** Linux/macOS build/headless/smoke + accessibility/ + localization/performance, gated on 13b (the managed surface is the only surface left to validate). + +Keeping these in one row understates the program's tail risk and hides the fact that the most +dangerous action (deletion) is gated on the success of a different action (the flip) that needs +field bake time, not a green CI run, to be trusted. + +## Feasibility (repo-grounded) + +The deletion target is **mostly** as the plan describes, but the plan's interface-deletion claim is +**too broad and would break non-UI code as written**. Specifics: + +**Safe to delete (pure UI/render), confirmed:** +- `Src/views/` native engine — 44 C++ files (`VwRootBox.cpp/h`, `VwSelection`, `VwTextBoxes`, + `VwEnv`, `VwLayoutStream`, `VwNotifier`, etc.). It is the **last** native component built + (`Build/mkall.targets`: `DebugProcs → GenericLib → FwKernel → Views`); nothing non-UI `#include`s + its headers. It only *consumes* Kernel/Generic, never provides to them. Deletable. +- `Src/ManagedVwDrawRootBuffered/` — referenced **only** by + `Src/Common/SimpleRootSite/SimpleRootSite.csproj`. Deletable with SimpleRootSite. +- `Src/Common/Controls/DetailControls/` (DataTree/Slice) and `Src/Common/Controls/XMLViews/` — pure + UI; no domain/LCModel code imports them. Deletable once their UI consumers are gone. +- `Src/Common/SimpleRootSite/` and `Src/Common/RootSite/` — these are the WinForms **hosts of** the + native engine (`SimpleRootSite` implements `IVwRootSite`, depends on `ViewsInterfaces` + + `ManagedVwDrawRootBuffered`). Deletable, but **18+ projects still reference them** (xWorks, + LexEdDll, ITextDll, Discourse, MorphologyEditorDll, FdoUi, Framework, FieldWorks, Widgets…), so + every Interlinear/Discourse/parser surface must be migrated first (Stages 7/9). This is the long + pole that gates 13b, not a Stage-13-internal task. + +**LOAD-BEARING — the plan's "retire the IVwRootBox/IVwGraphics/IVwEnv COM surface" must be scoped to +the *rendering* interfaces only, NOT the whole `ViewsInterfaces` assembly:** +- `Src/Common/ViewsInterfaces/Views.cs` defines both the UI rendering interfaces (`IVwRootBox` + ~line 6627, `IVwEnv` ~line 10200, `IVwGraphics`) **and** `IVwCacheDa` (~line 4122), which is a + **data-access** cache contract, not a render contract. +- `IVwCacheDa` is implemented by `Src/CacheLight/RealDataCache.cs` and referenced by **45+ + projects**. `ISilDataAccess` (the broader data-access interface) is defined in + `Src/Kernel/FwKernel.idh` (~line 475) — i.e., in Kernel, which Stage 13 keeps. +- **Therefore:** deleting `ViewsInterfaces` wholesale, or "the IVwEnv/IVwRootBox COM surface" without + surgically separating it from `IVwCacheDa`/data-access, breaks the data cache and ~45 projects. + The render-interface decommission requires **splitting `ViewsInterfaces`** into a deletable + render-interface set and a surviving data-access interface set (or relocating `IVwCacheDa` into a + Kernel-adjacent assembly) before the render half can be removed. The plan does not name this; it + is real, non-trivial senior work and a hard prerequisite for the COM-surface deletion claim. +- `ITsString`/`TsString` (used in 4000+ places) live in `Src/Kernel/TextServ.idh` — **not** Views — + so the "keep Kernel/Generic" line correctly preserves them. Good. + +**Cutover mechanics already exist and are sound:** +- The flip is a one-property change: `LexicalEditSurfaceResolver` reads `UIMode` + (`Src/Common/FwUtils/Properties/Settings.Designer.cs` defaults `[DefaultSettingValue("Legacy")]`), + with `LegacyUIMode="Legacy"`/`NewUIMode="New"`. Flipping the default to `New` *is* the cutover. + Today only `lexiconEdit`/`lexiconEditPopup` pass `SupportsAvaloniaForTool`; the flip is only safe + once every tool returns Supported — i.e., the gate "all manifests pass" must be **mechanically + enforced**, not asserted. +- The active-host contract in `Src/xWorks/RecordEditView.cs` (only one surface instantiated/driven) + and `EngineIsolationAuditTests.cs` are the right safety rails — see Best practices for extending + them to a whole-codebase gate. + +**Cross-platform:** genuinely blocked until Stage 12 (net48 is Windows-only). The managed-only bet +(no native Views on the Avalonia path) is what makes it *possible* at all. But `Src/views/` and the +WinForms hosts are Windows-only C++/WinForms; any residual reference from a surviving surface will +fail the Linux/macOS build, so 13c is only feasible *after* 13b is genuinely complete. + +**Installer:** `FLExInstaller/wix6/` is WiX6/Windows. Gecko is not named literally in the `.wxi`/ +`.wxs` files (it is harvested from the build output dir, so removal is an upstream +output/dependency change plus a harvest-exclude, not a manifest edit). Cross-platform packaging +(Linux `.deb`/`.rpm`, macOS bundle) is **net-new** and unscoped — the WiX installer does not port. +This alone argues 13c is its own epic. + +## Best practices + +- **Separate "flip" from "delete."** Strangler-fig cutover = flip the toggle, *bake*, then remove the + strangled code in a later change. Deleting in the same step destroys the rollback path. (Fowler + Strangler Fig; the program's own §2 principle 1.) +- **Staged rollout, not big-bang flip.** Use the `UIMode` setting for a ringed rollout + (opt-in → default-on-with-easy-revert → remove legacy). Per-tool granularity already exists + (`SupportsAvaloniaForTool`); flip tool-by-tool, not all-at-once. +- **Mechanical "all manifests pass" gate.** Make the flip blocked by a test that asserts every + registered tool/surface returns `Supported` and has a green region manifest — never a human + checklist. Hallucinated-parity is the program's top AI risk (§9). +- **Promote `EngineIsolationAuditTests` from one assembly to a codebase-wide gate at cutover.** + Today it scans only `FwAvalonia` (via `typeof(LexicalEditRegionView).Assembly` references + a + regex source scan of `Src/Common/FwAvalonia`). At cutover the audit's value is proving **no + surviving production assembly** references `SimpleRootSite`/`RootSite`/`XMLViews`/`DetailControls`/ + Views render symbols — extend it to scan all shipping assemblies before 13b deletes anything. +- **Delete in dependency order, leaf-first.** Repo-grounded order: + consumers' WinForms surfaces → `DetailControls`/`XMLViews` → `RootSite` → `SimpleRootSite` → + `ManagedVwDrawRootBuffered` → split `ViewsInterfaces` (relocate `IVwCacheDa`) → native `Src/views/` + → render COM interfaces. Each deletion behind its own build-green commit for bisectable rollback. +- **Rollback window with data on it.** Define the bake duration and the metric that closes it + (crash-free sessions, parity-incident count) before 13b is allowed to start. + +## Interactions & dependencies + +- **Depends on ALL prior stages** — correctly the terminal stage. Specifically it cannot start the + deletion until Stages 7 (Interlinear/Discourse) and 9 (document engine) have removed the last + `SimpleRootSite`/`IVwRootBox` consumers; the 18+ project references found are the concrete + gate. If any Track-II/III surface slips, 13b stalls — Stage 13 inherits the entire program's tail. +- **Stage 12 is a hard prerequisite for 13c** (net48 → .NET 10; net48 is Windows-only). The plan's + ordering (shell → runtime → cutover) is right for this reason. Confirmed. +- **`retire-linux-era-view-shims` is a *narrow prerequisite*, not part of Stage 13.** It removes the + Linux/Mono-era managed shims (`ViewInputManager`, `ManagedVwWindow`) while **explicitly preserving** + native `VwTextStore`, `IViewInputMgr`, and `ManagedVwDrawRootBuffered` (its non-goals). It is a + Views-IDL/interop cleanup that should land *before* the Stage-13 native decommission so the + `Src/views/` deletion starts from an already-de-shimmed Views surface. Note the irony: that change + preserves `ManagedVwDrawRootBuffered`, which Stage 13 then deletes — fine, because the consumer + (`SimpleRootSite`) is gone by then, but the sequencing should be stated. +- **Decision interactions:** Graphite full removal is Stage 10 (not here) — Stage 13 should *verify* + it is gone, not redo it. The MVVM-dialog decision (Decision 3) means "delete WinForms-only dialogs" + is gated on Stage 5 having replaced them with MVVM content, not on the region pattern. + +## Recommended plan changes (concrete) + +1. **Split Stage 13 into 13a (cutover & bake), 13b (decommission), 13c (cross-platform + final + gates)** with 13b gated on a *field bake* metric from 13a and 13c gated on 13b complete. +2. **Rewrite the COM-surface line** from "retire the `IVwRootBox`/`IVwGraphics`/`IVwEnv` COM surface" + to "**split `ViewsInterfaces`**: relocate/keep the data-access contracts (`IVwCacheDa`, and any + non-render interface `CacheLight`/the 45+ consumers use) behind a service seam; delete only the + render interfaces." Name this as a prerequisite sub-task with the `CacheLight` dependency cited. +3. **Add an explicit, dependency-ordered deletion runbook** (leaf-first order in Best practices) and + require each deletion to be an independently-green, bisectable commit. +4. **Make "all manifests pass" a mechanical gate** (test asserting every tool returns `Supported` + + green manifest) that blocks the flip; flip per-tool via `SupportsAvaloniaForTool`, not globally. +5. **Promote `EngineIsolationAuditTests` to a whole-shipping-assembly audit** as the entry gate to + 13b; deletion may not begin until no surviving assembly references the legacy UI/render symbols. +6. **Scope cross-platform packaging explicitly** in 13c: the WiX6 installer is Windows-only and does + not port; Linux/macOS packaging is net-new work, not a tweak to `FLExInstaller/`. +7. **State the `retire-linux-era-view-shims` ordering** (lands before the `Src/views/` decommission). + +## De-risking cross-platform within the deferral decision + +The decision to hold all cross-platform OS validation to Stage 13 is *made*; the research warning +(regressions surface late) is real, so de-risk **within** the constraint rather than relitigating it: + +- **Run the headless lane on Linux CI continuously from Stage 1** (Avalonia.Headless is + OS-portable; the program's §9 risk row already commits to this). This catches *logic/binding/ + layout* regressions OS-early even though OS *smoke/UIA* is deferred — it is the single biggest + cheap de-risk and costs nothing extra given headless tests already exist. +- **Lint for Windows-only APIs as code is written** (path separators, registry, P/Invoke, `\`), + so the surviving managed code is cross-platform-clean *before* the OS build is ever attempted. +- **Stand up the Linux/macOS *build* (compile-only, no smoke) one stage early (end of 12)** so + build-break surprises (project SDK, runtime IDs, native-dep resolution) are found before they pile + onto the terminal stage. This honors "no *validation* cost earlier" while de-risking the build. +- **Budget an explicit integration-and-verification sub-phase in 13c** (§2 principle 11): clean + cross-platform builds ≠ working software; reserve time for post-green OS smoke debugging. + +## Open questions / risks + +- **`ViewsInterfaces` split feasibility:** can `IVwCacheDa` be cleanly separated from the render + interfaces, or do render interfaces reference data-access types (coupling the split)? This decides + whether the COM-surface deletion is a clean cut or a refactor. Needs a focused audit before 13b. +- **Bake metric undefined:** what closes the 13a rollback window — duration, crash-free rate, + parity-incident count? Without it, 13b's "safe to delete" trigger is subjective. +- **Tail-risk concentration:** Stage 13 inherits slippage from all of 5–12; if any surface still + uses `SimpleRootSite`, the whole deletion stalls. The 18+ project reference list is the live + burn-down to track program-wide, not at Stage 13. +- **Cross-platform packaging is unscoped** (no Linux/macOS installer exists); risk of discovering + this is a multi-week effort at the very end. +- **HarfBuzz/Graphite coverage** must already be proven (Stages 9/10); if not, the cross-platform + text-rendering smoke fails late with no Graphite fallback (it was removed). Verify, don't assume. +- **Big-flip blast radius:** even with per-tool granularity, the *default* flip touches every user; + the ringed rollout and instant `UIMode` revert are the mitigations and must be real, not nominal. + +## Confidence + +**High** on the feasibility findings — the deletable-vs-load-bearing split is grounded in inspected +paths (`Src/views` build order; `IVwCacheDa` in `ViewsInterfaces` consumed by `CacheLight` + 45 +projects; `ITsString` in Kernel; the 18+ `SimpleRootSite` consumers; the `UIMode` flip mechanism; +the audit's single-assembly scope). **High** on the split recommendation — the five workstreams +differ in reversibility and the irreversible one (deletion) is gated on the field success of a +reversible one (the flip), which is the textbook reason to separate them. **Medium** on the exact +`ViewsInterfaces` split mechanics — whether `IVwCacheDa` separates cleanly from the render interfaces +needs the focused audit named in Open questions before the deletion sequence can be committed. diff --git a/openspec/changes/legacy-screenshot-capture/.openspec.yaml b/openspec/changes/legacy-screenshot-capture/.openspec.yaml new file mode 100644 index 0000000000..a4ac4d7883 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-23 diff --git a/openspec/changes/legacy-screenshot-capture/capture-ledger.md b/openspec/changes/legacy-screenshot-capture/capture-ledger.md new file mode 100644 index 0000000000..7e80217af7 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/capture-ledger.md @@ -0,0 +1,244 @@ +# Capture ledger — legacy-screenshot-capture + +Reconciles every reachable Phase-1 capture target to its capture status. Data: Sena 3, UIMode=Legacy. + +## Option 2 — launch-per-tool (tool + list screens) +- Script: `scripts/migration-capture/Capture-LegacyTools.ps1` (run under Windows PowerShell 5.1). +- Manifest: `manifests/tools.csv` (67 targets). Log: `manifests/tools-capture.log`. +- **Result: 67 / 67 captured** (64 in the sweep + 3 captured during proof: lexiconBrowse, concordance, + interlinearEdit). PNGs in `Docs/migration/tools/images/` and `Docs/migration/lists/images/`, + wired into every tool/list doc. +- Recipe (proven): `FieldWorks.exe "silfw://localhost/link?app=flex&database=Sena%203&server=&tool="` + — sole argument, **no `guid` param** (an empty guid crashes `FwLinkArgs` parsing; the link still + switches tools because `LinkListener` publishes `SetToolFromName` regardless of guid). + +## Option 3 — dialog screenshot harness (piggyback test fixture) +- Fixture: `Src/LexText/LexTextControls/LexTextControlsTests/ScreenshotHarnessTests.cs` + (`[Explicit]` + `[Category("ScreenshotHarness")]`; in-memory cache base = reg-free COM + LcmCache + already bootstrapped). Run: `.\test.ps1 -SkipNative -TestProject LexTextControlsTests -TestFilter "FullyQualifiedName~ScreenshotHarness"`. +- Output is `-before.png`; the Avalonia `-after.png` comes from the parity visual test + (see the before/after requirement). Each `Cap(...)` is wrapped so one failure never blocks the batch. + +**Captured headless (the feature-structure-tree family — no FwTextBox/main-window dependency):** +- `feature-chooser` → `Docs/migration/images/feature-chooser-before.png` (`FeatureSystemInflectionFeatureListDlg`; + the same feature-tree control as the two production dialogs the feature-chooser doc covers). +- `msa-inflection-feature-list` → `Docs/migration/dialogs/images/msa-inflection-feature-list-before.png` + (sibling of feature-chooser; same doc family). + + Both flavors proven: `feature-chooser` via the in-memory base, `msa-inflection-feature-list` via the + Sena 3 temp copy. The `CaptureContext` builds the app context (Mediator, PropertyTable, a real + stylesheet carried on a fake "window" form, a non-null help provider). + +**LIVE-CAPTURE / on-pickup (investigated to root cause; NOT headless-capturable):** `insert-entry`, +`add-new-sense`, `merge-entry`, `link-msa`, `entry-go` (the GoDlg family). **The full Sena 3 project + +real stylesheet did NOT unblock them — data/stylesheet was never the cause.** They embed the live +"matching entries" search-browse: `EntryGoDlg.InitializeMatchingObjects` reads the app's +`"WindowConfiguration"` XML and builds an XMLView + `SearchEngine`, which needs a real message loop. +Headless they NRE (no `WindowConfiguration`) or, given more context, show a modal off-screen and HANG +(observed: the off-screen `Show()` blocked for minutes). Faking that is reconstructing the app — +out of scope for throwaway tooling. **Capture these live** (they are reachable in the running app) or +when their JIRA ticket is implemented. `delete-confirmation`/`merge-object`/`RelatedWords` additionally +need the **FdoUi** assembly (not referenced by LexTextControlsTests) — same fixture pattern in an +FdoUi-referencing test project. + +**Rule of thumb (encode):** simple tree/list/feature dialogs capture headless via the harness; +**dialogs that embed a matching-entries/search-browse (or any live XMLView) must be captured live**. +Adding a headless-capturable dialog = one `Cap(...)` line (seed objects + ctor + `SetDlgInfo`). + +**Message-loop harness attempt (option 2) — worked past 3 barriers, then hit a hard wall on the +matching-entries family:** +1. NRE `WindowConfiguration` null → FIXED: load it like `LexEntryUi.EnsureWindowConfiguration` + (`XWindow.LoadConfigurationWithIncludes(Main.xml).SelectSingleNode("window")`), set in CaptureContext. +2. `Debug.Assert` **modal popups** on screen → FIXED: clear `Trace.Listeners` during capture + (restored after). Stops the popups AND makes asserts no-op so dialogs proceed; real failures then + log as catchable traces, never popups. (Also added a minimal `IApp` stub for the `app != null` assert.) +3. With asserts no-op'd, `MatchingObjectsBrowser` builds a full `BrowseViewer` **synchronously inside + `SetDlgInfo`** and **blocks the thread** (the message loop never pumps, so the watchdog can't close + it; one hang kills the batch). This needs the real app (RecordClerk, etc.). **HARD WALL — these stay + live-capture/on-pickup.** `CapLoop` (message-loop + watchdog) remains in the harness for future + app-coupled dialogs that DON'T block synchronously. +The harness is popup-free and hang-free (the matching-entries family is not executed). Captures so far: +feature-chooser, msa-inflection-feature-list. + +**Coverage reality for "all dialogs":** the harness reliably captures **simple** dialogs (no +matching-entries/XMLView). Reaching all ~130 needs per-dialog `Cap(...)` wiring across the dialog +assemblies (LexTextControls done here; FdoUi/FwCoreDlgs/xWorks via the same fixture in their own test +projects — the shared-helper-lib rollout). The matching-entries family (~5) is permanently on-pickup. + +**BREADTH ENGINE (reflection sweep) — 34 dialogs captured + 33 wired into docs.** Instead of +hand-wiring ~130 dialogs, `ReflectSweep(dll)` loads each dialog assembly +(`LexTextControls`/`FwCoreDlgs`/`FdoUi`/`xWorks`), finds every constructable `Form` ending Dlg/Dialog, +matches its ctor + `SetDlgInfo` params from the `CaptureContext`, and captures each on its own timed +STA thread (hang → timeout → logged, sweep continues). The matching-entries family (`BaseGoDlg` +subclasses + InsertEntry/AddNewSense) is auto-skipped → live-capture. `Wire-BeforePngs.ps1` then matches +each `-before.png` to the doc whose declared **legacy class** kebabs the same (so +`fw-proj-properties` → `project-properties.md`), moves the PNG into that doc's `images/`, and inserts the +before/after block. +- **Sweep (80 Forms attempted): 35 captured, ~32 dialog docs wired** (project-properties, font, backup, + find-replace, master lists, custom-field, import/export wizards, add-converter, conflicting-save, + add-new-user, choose-lang-project, occurrence, …) — matched to docs by declared legacy class. +- **35 is the empirical ceiling of GENERIC reflection capture** (confirmed across 5 sweeps): pushing + `ArgFor` harder — empty arrays, hvo-named ints → real hvo, `XmlNode` → WindowConfiguration — *regressed* + ~35→24 (dialogs that worked with null/0 broke on the "smarter" value). Reverted; kept only the safe + `CmObjectUi`/specific-LCModel-type matches. Beyond 35, each dialog needs **bespoke domain objects** + (msa-creator→`SandboxGenericMSA`+`IPersistenceProvider`; restore-project→`BackupFileSettings`; + phon-feature→`IPhRegularRule`; merge-ws→a ws + ws-list; fw-delete-project→on-disk project list) or the + **live app** (matching-entries family; Gecko-backed dictionary-config/export) — not a common fix. +- **Hand-wired bespoke path proven:** `msa-creator` (needs `IPersistenceProvider` + `SandboxGenericMSA`) + captured by a hand-written `Cap(...)` — so any remaining bespoke dialog CAN be added one-by-one with + its specific domain objects (in its own assembly's test project), it's just per-dialog work. +- **Pumping-loop capture (8th barrier):** `Cap` now runs each dialog under `Application.Run` + a + capture-then-close timer on its timed STA thread, so dialogs that init ASYNCHRONOUSLY finish painting + (Show()+DoEvents left them half-built). Net +2 (e.g. `lex-options`, previously a hang). Dialogs that + block SYNCHRONOUSLY in SetDlgInfo (matching-entries; Gecko dictionary-config; clerk-backed export) + still hang → timeout-isolated → live-only. +- **Expanded definition (tabs + populated) + ctor-only chrome breadth — major jump:** **65 dialogs + captured / 99 attempted, 59 dialog docs wired**, plus **18 per-tab snapshots** across 5 tabbed dialogs + (writing-system-setup ×7, options ×4, project-properties ×3, add-converter ×3, find-replace). + - **Two-pass**: Pass A full `SetDlgInfo` over **Sena 3** (populated lists/grids); Pass B **ctor-only + chrome** for the rest (construct the dialog, skip the hanging `SetDlgInfo`, capture the layout) — this + is what unlocked the formerly-hanging export/dictionary-config/backup/custom-list/confirm-delete set. + - **Per-tab**: `Save` walks `TabControl`s, selects each `TabPage`, captures `-tab-.png`. + - **Popups fixed**: the WinForms "COM separated from RCW" unhandled-exception dialog (thrown when a + Views dialog disposes cross-apartment, AFTER its shot) is swallowed via an `Application.ThreadException` + handler + `CatchException` mode — no modal, no block, capture preserved. + - Tuning: capture delay 1.2s, ctor-only timeout 6s / full 10s — keeps the run under the 15-min vstest cap. +### Uncaptured dialogs — per-dialog blocker (from constructor/SetDlgInfo exceptions; option 2 = headless construct, so these need the live app or bespoke objects) +| Dialog | Blocker (exact) | Disposition | +|---|---|---| +| ~~fw-styles~~ | ~~NRE `FillStyleTable`; ctor needs `IVwRootSite`~~ | **CAPTURED LIVE** (Format→Styles…) — option 2b below | +| ~~insert-record~~ | ~~"Failed to compute height of an FwTextBox"~~ | **CAPTURED LIVE** (Insert→Record) — option 2b below | +| ~~restore-project~~ | ~~ctor needs `BackupFileSettings`~~ | **CAPTURED LIVE** (File→Project Management→Restore a Project…) — option 2b | +| ~~fw-delete-project~~ | ~~NRE enumerating on-disk projects~~ | **CAPTURED LIVE** (File→Project Management→Delete Project…) — option 2b | +| ~~dictionary-configuration~~ | ~~ctor: "requires GeckoWebBrowser … Xpcom.IsInitialized=False"~~ | **CAPTURED LIVE** (Tools→Configure→[1st `{0}`]) — Gecko preview rendered real data — option 2b | +| xml-diagnostics | ctor takes `GeckoElement` | Gecko → live | +| sfm-to-texts-and-words-mapping | `MissingManifestResourceException` (designer resource) | live cascade (File→Import→…) — attemptable | +| fw-apply-style | greyed unless live text selection (verified live) | live; needs editing context | +| add-list / configure-list / picture-properties / valid-characters | ctor hangs (even ctor-only timed out) | live; sub-dialog or menu cascade | +| merge-writing-system | ctor wants `IEnumerable` (internal model) | sub-dialog of WS-properties (captured as parent) | +| merge-object | `InitBrowseView` needs a populated candidate list (XMLView) | live/bespoke | +| missing-old-field-works | ctor needs `RestoreProjectSettings` | live; only appears mid-restore | +| phonological-feature-chooser | SetDlgInfo needs `IPhRegularRule` (none in Sena 3) | bespoke data | +| dictionary-config-mgr | NRE `LoadDataFromInventory(XmlNode)` (specific inventory node) | live cascade (Tools→Configure→…) — attemptable | + +These were verified, not assumed — each fails in its **constructor** (or a Views/Gecko/XMLView init), so a +headless harness cannot produce them. Captured via per-dialog override this round: **fw-chooser, utility**. + +## Option 2b — LIVE-APP menu-launched capture (native UIA + Win32) — unblocks the headless-impossible set +- Script: `scripts/migration-capture/Capture-MenuDialogs.ps1`; manifest `manifests/menu-dialogs.csv` + (run under Windows PowerShell 5.1). Full technique recorded in memory `fieldworks-live-menu-dialog-capture`. +- **Mechanism (single-level)**: launch FLEx live at the row's tool (guid-less silfw link, same as option 2), + find the top-level `MenuItem` via native `System.Windows.Automation`, `Invoke()` the leaf. Invoking a + modal-opening item makes UIA `Invoke()` **time out (0x80131505)** because the UI never goes idle — that + timeout is the success signal. Capture the modal with **Win32 `EnumWindows`+`PrintWindow`** (UIA hangs + while the modal blocks the UI thread); close read-only with `WM_CLOSE` (= Cancel). +- **Mechanism (multi-level cascades — the part that took 7 attempts):** UIA `Expand`/`Invoke` does NOT open + FLEx's nested dropdowns. The working recipe is **real synthesized mouse input** on a **genuinely + foregrounded** window: + 1. **Defeat the foreground lock** — a background process's `SetForegroundWindow` is ignored; tapping a + synthetic **Alt key** (`keybd_event` VK_MENU) first makes the OS honor it (verified: foreground match + False→True). Without this the clicks land on whatever window is actually on top (e.g. VS Code). + 2. **Real mouse click** (`SetCursorPos`+`mouse_event`) at each item's screen rect opens a dropdown that + genuinely renders (the popup appears as a new top-level window) — unlike UIA Expand. + 3. **Find items via Win32 `EnumWindows` → `AutomationElement.FromHandle`** per popup window + (`root::Descendants` never lists the popups). + 4. **Match on a whitespace-normalized name** — FLEx sets each item's UIA `Name` to a DESPACED form + ("Project Management" → `ProjectManagement`, "Send/Receive" → `SendReceive`); exact-name matching is why + every multi-word label failed for 6 attempts. +- **CAPTURED — 14 dialogs, all previously headless-impossible or uncaptured** (manifests + `menu-dialogs.csv` + `menu-dialogs-batch{2..5}.csv`): + - `fw-styles` (Format→Styles…) — full styles list + 5 tabs. → `styles.md`. + - `insert-record` (Insert→Record) — "New Record". → `insert-record-dlg.md`. + - `restore-project` (File→Project Management→Restore a Project…). → `restore-project.md`. + - `fw-delete-project` (File→Project Management→Delete Project…). → `delete-project.md`. + - `dictionary-configuration` (Tools→Configure→[1st `{0}`]) — layout tree + **Gecko preview with real Sena 3 + data** ("kula V crecer"); the Gecko the headless ctor demanded is initialized in the running app. → `dictionary-configuration.md`. + - `restore-defaults` (Tools→Configure→Restore Defaults…). → `restore-defaults-dlg.md`. + - `configure-interlin` (Tools→Configure→Interlinear… "Configure Interlinear Lines"). → `configure-interlin-dialog.md`. + - `try-a-word` (Parser→Try a Word…). → `try-a-word-dlg.md`. + - `parser-parameters` (Parser→Edit Parser Parameters…). → `parser-parameters-dlg.md`. + - `interlinear-import` (File→Import→FLExText Interlinear…). → `interlinear-import-dlg.md`. + - `sfm-to-texts-and-words-mapping` (File→Import→Standard Format Words and Glosses…) — was the + `MissingManifestResourceException` headless blocker; renders fine live. → `sfm-to-texts-and-words-mapping-dlg.md`. + - `interlinear-export` (File→Export Interlinear…). → `interlinear-export-dialog.md`. + - `export-dialog` (File→Export… — base lexicon export chooser). → `export-dialog.md`. + - `help-about` (Help→About Language Explorer…). → `help-about.md`. + - `dictionary-configuration-manager` — **in-modal sub-dialog**: open Configure Dictionary, then click the + **Manage Layouts…** button (postClicks). Populated "Manage Dictionary Layouts" (layouts + publications). → `dictionary-configuration-manager.md`. + - (`notebook-export` existing headless capture re-wired to `notebook-export.md`.) +- **KEY: FLEx dialog buttons are exposed to UIA as controlType `Pane`, not `Button`** (only combobox + dropdowns are `Button`). The in-modal `postClicks` matcher must search Name across {Button, **Pane**, + MenuItem, TabItem, Hyperlink, ListItem} — once it did, the button-launched sub-dialog set opened up + (dict-config-manager captured via "Manage Layouts…"). This corrects the earlier (wrong) conclusion that + "FLEx dialog buttons aren't UIA-discoverable" — they are, just as Pane. +- **Context-gated leaves detected & reported `IsEnabled=false`** (need a specific tool/selection/file — + capturable live from the right context on pickup): `lingua-links-import` (needs a LinguaLinks file), + `discourse-export` (needs an active chart), `configure-list`/`add-list` (custom-list tool), + `merge-entry` (selected entry; already has a capture), `fw-apply-style` (text selection). + `filter-texts`/`import-word-set` use `defaultVisible=false` items not present in the tools tried. +- **Read-only preserved**: each dialog opened and was closed with `WM_CLOSE` (= Cancel); no project was + restored/deleted (verified: restore showed "No project backups found"; delete's Delete button stayed disabled). +- **Remaining floor (still genuinely uncaptured):** + - **Parent→child sub-dialogs** (valid-characters, merge-writing-system are buttons *inside* the + Writing-System-Properties dialog — itself captured headless as `writing-system-setup`, 7 tabs; + overwrite-existing-project only appears mid-restore). + - **Context-gated leaves** (greyed unless the right tool/selection is active, so unattended capture skips + them — the script detects `IsEnabled=false` and reports it): `configure-list`/`add-list` (Tools→Configure→ + List… — needs a custom-list tool), `merge-entry` (Tools→Merge with entry… — needs an entry selected), + `fw-apply-style` (Format→Apply Style — needs a text selection). Capturable live from the right context on pickup. + - **Button sub-dialogs reachable via `postClicks`** (now working with Pane matching) but needing TAB + navigation first: valid-characters & merge-writing-system are buttons on non-default tabs of the + Writing-System-Properties dialog; the WS-Props tabs are a Win32 `SysTabControl` not cleanly exposed as + UIA `TabItem`, so selecting the right tab needs a coordinate click — a per-dialog discovery step left for pickup. + - **Flow-only dialogs**: overwrite-existing-project (appears only mid-restore), update-report (only on update), + sfm-to-texts later wizard pages — require performing the actual (destructive/stateful) action. + - **xml-diagnostics**: hidden Gecko diagnostic, no standard menu entry. +- **Right-click context-menu capability built** (`rightClick` manifest field: right-click the first browse row, + then navigate the context menu via the same Pane-aware engine). Available for pickup, but the remaining + context-menu dialogs (respeller, related-words, edit-morph-breaks, MGA, complex-conc-*, find-example-sentence, + …) are each gated on a SPECIFIC tool + selected item type + context path that isn't reliably discoverable + unattended. These need a human to set the context, after which the built engine captures them. +- **ROOT CAUSE of the unattended floor (verified empirically, not assumed):** FLEx's custom-drawn surfaces are + not exposed to UI Automation in the standard way, so the elements needed to *establish context* can't be + located unattended: + - **Dialog tabs** → WS-Properties reports **`TabItems found: 0`** (its tabs are a Win32 `SysTabControl` UIA + doesn't surface) — so valid-characters/merge-writing-system (buttons on non-default tabs) are unreachable. + - **Browse-view rows** → `Find-FirstRowCenter` returns nothing (rows aren't ListItem/DataItem/Custom in UIA), + so the right-click engine can't locate a data item to invoke a context menu (`rightclick-target-not-found`). + - **Context-gated menu items** → respeller's Tools→Spelling submenu is absent even in the Analyses tool with a + pre-selected row (the menu only materializes from a real in-view wordform selection UIA can't make). + (Buttons exposed as `Pane` and despaced menu names were solvable — these three are not, at the UIA layer.) + This is the true unattended boundary: **everything reachable by a main-menu path or an in-modal Pane button is + captured; the residue needs a live selection/tab/context that FLEx doesn't expose to UI automation, or appears + only during a destructive/error flow. The engine + per-dialog entry points are committed for interactive pickup.** + +- **Total: ~150 legacy truth PNGs across 132 docs** (67 tool/list + 66 dialog "before" + 18 per-tab, + wired into 5 tabbed-dialog docs). Canonical-kept legacy dialogs (insert-entry/entry-go/lex-options) got + brief docs so their captured PNGs land in markdown too. Abstract base classes (BaseGoDlg/MasterListDlg) + excluded — not real dialogs. **66 of 98 constructable dialog Forms captured (~67%);** the remaining ~32 + are the per-dialog-blocker table above (Gecko/IVwRootSite/ctor-hang/bespoke-object/internal-model) → + live-capture / on-pickup, each with its exact blocking exception. Remaining un-captured ≈ abstract bases, dialogs whose CTOR itself + NREs/hangs (some Gecko-in-ctor), and canonical-kept dialogs (no deferred doc: insert-entry/entry-go/ + options — their PNGs are on disk). The matching-entries family is captured as **chrome** (ctor-only); + fully-**populated** matching-entries shots still need the live app. +- **Two build/run gotchas hit + cleared:** (a) a stale FieldWorks/testhost process locks `Output/Debug` + audio DLLs → `MSB3021 Access denied`; kill strays before a sweep. (b) "smarter" generic `ArgFor` + (empty arrays / hvo-ints / config XmlNode) REGRESSES captures — null/0 is correct for many dialogs. +- On-pickup categories among the 46: ~12 matching-entries (live), ~16 hang/timeout (need a clerk/Gecko + or pop a sub-modal — dictionary-config, export*, valid-characters, lex-options, …), ~11 ctor/SetDlgInfo + errors, ~5 arg type-mismatch (FIXED: `ArgFor` now supplies the right `ICmPossibilityList`/`ICmPicture`/ + `IFsFeatStruc` and passes null instead of a wrong type). +- Total legacy truth PNGs now in docs: **67 tool/list + 34 dialogs ≈ 100+.** Remaining dialogs are the + matching-entries family (live) + the hang/ctor-error tail (per-dialog or live, on pickup). + +## On-pickup / not captured (recorded, not silently skipped) +- **Views/Gecko-coupled dialogs** (need a live `IVwRootBox`/`IVwSelection`): e.g. `RelatedWords`, + `SummaryDialogForm`, `MergeObjectDlg` (FwTextBox), dictionary HTML preview. Capture when their ticket + is worked, from the workflow that supplies the selection. +- **Phase-2 non-visual internals** (12, `Docs/migration/phase2/`): Views engine, mediator/PropertyTable, + buffered draw, etc. — not screens; no PNG. + +## Verification +- Option 2: 3 tools spot-verified (lexiconBrowse, concordance, semanticDomainEdit) — correct tool, non-blank. +- Option 3: harness `[Explicit]` run green; PNG asserted non-blank (>2 KB) and visually verified. +- `./test.ps1 -TestProject LexTextControlsTests` build green with the fixture added. diff --git a/openspec/changes/legacy-screenshot-capture/design.md b/openspec/changes/legacy-screenshot-capture/design.md new file mode 100644 index 0000000000..a0717ee65b --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/design.md @@ -0,0 +1,87 @@ +# Design — legacy-screenshot-capture + +Shared language: `Docs/migration/CONTEXT.md`. This change is dev tooling; no product code path +changes, no native (C++) changes. + +## Goal & non-goals +- **Goal:** produce legacy truth PNGs for the reachable Phase-1 capture targets in `INVENTORY.md`, + stored at the image paths the docs already reference, from the Sena 3 language project, in + UIMode=Legacy. +- **Non-goals:** capturing Views/Gecko-coupled dialogs (need live rootbox/selection), the 12 + non-visual Phase-2 internals, or any Avalonia surface (separate headless path). No edits to the + language project. Not a parity *diff* tool (the harness merely makes parity capture possible later). + +## Capture manifest +A single source both tools read, generated from `INVENTORY.md` (+ each doc's front matter table): +- `tools.csv` — `toolId, area, surface, docPath, imagePath` for option 2. +- `dialogs.csv` — `dialogName, factoryKey, docPath, imagePath, status(reachable|views-coupled|on-pickup)` + for option 3. +Generation is a small script step so the manifest stays in sync as docs/inventory evolve. + +## Option 2 — launch-per-tool capture (PowerShell) +`scripts/Capture-LegacyTools.ps1` (worktree-aware; resolves `Output/Debug/FieldWorks.exe` in the +current worktree): +1. For each `tools.csv` row, build `silfw://localhost/link?app=flex&database=Sena 3&tool=&guid=`. +2. Launch `FieldWorks.exe -db "Sena 3" ""`; wait for the main window title to settle. +3. Screenshot the main window to `imagePath` (`-01.png`). +4. Close the process; proceed to the next tool. + +Decisions: +- **Relaunch per tool** (not one long-lived instance): robust and stateless — no fragile in-app + navigation — at the cost of ~one project-load per tool (acceptable, unattended). A future + optimization could reuse one instance + a follow-link, but relaunch is the safe baseline. +- **Screenshot mechanism:** the same window-capture proven to render (PrintWindow via winforms-mcp, + or `Capture-Window.ps1` PrintWindow helper). The screenshot is of the **main window** in the + target tool; Views-rendered content inside is fine as a bitmap (we only need the picture). +- **`guid`:** empty (open the tool's default record) unless a doc needs a specific object. + +## Option 3 — dialog screenshot harness (net48 WinForms) +New MSBuild project `Src/Utilities/ScreenshotHarness/ScreenshotHarness.csproj` (`net48`, `x64`, +`WinExe`), modeled on `LCMBrowser` (the precedent for a standalone tool that opens a project cache). +- **Cache open:** open the Sena 3 project read-only via the `ProjectId` / `LcmCache` open path + `LCMBrowser` uses; build the minimal `Mediator`/`PropertyTable`/`IHelpTopicProvider`/stylesheet + context the dialogs need. +- **Dialog registry:** `Dictionary>` mapping a dialog key to a + factory. Most factories are `new XDlg()` then `SetDlgInfo(cache, …)` (FLEx's two-phase init); + light ones (`ConfirmDeleteObjectDlg(IHelpTopicProvider)`, `MergeObjectDlg(IHelpTopicProvider)`) + need only the help provider + seeded args. Each factory seeds representative data so the shot is + meaningful (e.g. a real entry for delete-confirmation). +- **Render:** show the form off-screen and capture via `Control.DrawToBitmap` into `imagePath`; if a + dialog embeds native Views and `DrawToBitmap` is blank, fall back to a visible-desktop window + screenshot and mark it; if it can't construct headless, mark `views-coupled`/`on-pickup`. +- **Invocation:** `ScreenshotHarness.exe --project "Sena 3" --manifest dialogs.csv --out `; + one dialog per `--only ` for iteration. Runs unattended over the manifest. + +Build/run posture: built by the normal traversal (added to the solution) but **not shipped/installed** +— it is a dev utility, like `LCMBrowser`. It references existing legacy assemblies (FwCoreDlgs, +xWorks, LexText.*, FdoUi, Common.Controls); per the repo's dependency-justification invariant, this +is a one-way dev-tool→product reference (no product code references the harness), so it introduces +no new product coupling. + +## Decision (2026-06-23): piggyback test fixture, not a standalone exe +The standalone `ScreenshotHarness.exe` proved heavier than expected: dialogs need not just a cache +but a `Mediator` + `PropertyTable` + a `CmObjectUi` per object, and a fresh exe also needs its own +registration-free COM manifest + directory/registry bootstrap. Instead, **piggyback on an existing +test project** (`LexTextControlsTests`) whose `MemoryOnlyBackendProviderRestoredForEachTestTestBase` +already bootstraps reg-free COM + an `LcmCache`. The "harness" is an `[Explicit]` NUnit fixture +(`ScreenshotHarnessTests`) — each capture is a `[Test]` that seeds objects, constructs the dialog +(ctor + `SetDlgInfo`), and renders via `DrawToBitmap`. Lower risk, runs under `./test.ps1`, no new +project/COM manifest. Trade-off: in-memory seeded data instead of full Sena 3 (fine for layout +fidelity), and dialogs split across assemblies use the same fixture pattern in their own test project. +Proven on `FeatureSystemInflectionFeatureListDlg` (feature-chooser). + +## Risks & mitigations +- **Headless render blank for native Views dialogs** → detect (blank/near-uniform bitmap), fall back + to visible-desktop capture, else mark on-pickup. Bounded: most dialogs are plain WinForms. +- **Heavy/variable dialog constructors** → registry is incremental; unmapped/failing dialogs are + logged and left on-pickup, never block the batch. +- **Link navigation lands on the wrong tool** → verify on 2–3 tools first (title + a known control) + before the full run; log mismatches. +- **Build state** → the harness needs the managed build; reuse the existing `Output/Debug` assemblies. + +## Verification +- Option 2 proven on ≥3 tools (correct tool in shot) before the full run. +- Option 3 proven on `ConfirmDeleteObjectDlg` + `MergeObjectDlg` (non-blank, correct dialog) before + the full run. +- `capture-ledger.md` reconciles every `INVENTORY.md` reachable target to captured / skipped+reason. +- `./build.ps1` stays green with the harness project added; no product test changes. diff --git a/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch2.csv b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch2.csv new file mode 100644 index 0000000000..b4212edd83 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch2.csv @@ -0,0 +1,8 @@ +name,imagePath,tool,menuPath +restore-defaults,Docs/migration/dialogs/images/restore-defaults-before.png,lexiconDictionary,Tools|Configure|Restore Defaults... +configure-interlin,Docs/migration/dialogs/images/configure-interlin-before.png,interlinearEdit,Tools|Configure|Interlinear... +try-a-word,Docs/migration/dialogs/images/try-a-word-before.png,interlinearEdit,Parser|Try a Word... +parser-parameters,Docs/migration/dialogs/images/parser-parameters-before.png,interlinearEdit,Parser|Edit Parser Parameters... +interlinear-import,Docs/migration/dialogs/images/interlinear-import-before.png,interlinearEdit,File|Import|FLExText Interlinear... +lingua-links-import,Docs/migration/dialogs/images/lingua-links-import-before.png,interlinearEdit,File|Import|LinguaLinks Data... +sfm-to-texts-and-words-mapping,Docs/migration/dialogs/images/sfm-to-texts-and-words-mapping-before.png,interlinearEdit,File|Import|Standard Format Words and Glosses... diff --git a/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch3.csv b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch3.csv new file mode 100644 index 0000000000..b53db70e10 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch3.csv @@ -0,0 +1,5 @@ +name,imagePath,tool,menuPath +interlinear-export,Docs/migration/dialogs/images/interlinear-export-before.png,interlinearEdit,File|Export Interlinear... +notebook-export,Docs/migration/dialogs/images/notebook-export-before.png,notebookEdit,File|Export... +filter-texts,Docs/migration/dialogs/images/filter-texts-before.png,interlinearEdit,View|Choose Texts... +import-word-set,Docs/migration/dialogs/images/import-word-set-before.png,Analyses,Insert|Import Word Set... diff --git a/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch4.csv b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch4.csv new file mode 100644 index 0000000000..799ab2b9ac --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch4.csv @@ -0,0 +1,3 @@ +name,imagePath,tool,menuPath +help-about,Docs/migration/dialogs/images/help-about-before.png,lexiconEdit,Help|About Language Explorer... +discourse-export,Docs/migration/dialogs/images/discourse-export-before.png,chartmarkEdit,File|Export Discourse Chart... diff --git a/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch5.csv b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch5.csv new file mode 100644 index 0000000000..33c6dae2f7 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs-batch5.csv @@ -0,0 +1,2 @@ +name,imagePath,tool,menuPath +export-dialog,Docs/migration/dialogs/images/export-dialog-before.png,lexiconEdit,File|Export... diff --git a/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs.csv b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs.csv new file mode 100644 index 0000000000..cba735675f --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/manifests/menu-dialogs.csv @@ -0,0 +1,6 @@ +name,imagePath,tool,menuPath +fw-styles,Docs/migration/dialogs/images/fw-styles-before.png,lexiconEdit,Format|Styles... +restore-project,Docs/migration/dialogs/images/restore-project-before.png,lexiconEdit,File|Project Management|Restore a Project... +fw-delete-project,Docs/migration/dialogs/images/fw-delete-project-before.png,lexiconEdit,File|Project Management|Delete Project... +insert-record,Docs/migration/dialogs/images/insert-record-before.png,notebookEdit,Insert|Record +dictionary-configuration,Docs/migration/dialogs/images/dictionary-configuration-before.png,lexiconDictionary,Tools|Configure|{0} diff --git a/openspec/changes/legacy-screenshot-capture/manifests/styles-tabs.csv b/openspec/changes/legacy-screenshot-capture/manifests/styles-tabs.csv new file mode 100644 index 0000000000..044d4cf882 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/manifests/styles-tabs.csv @@ -0,0 +1,2 @@ +name,imagePath,tool,menuPath,tabLabels,tabClicks +fw-styles,Docs/migration/dialogs/images/fw-styles-before.png,lexiconEdit,Format|Styles...,general|font|paragraph|bullets|border,237:53|266:53|308:53|393:53|503:53 diff --git a/openspec/changes/legacy-screenshot-capture/manifests/sub-dialogs.csv b/openspec/changes/legacy-screenshot-capture/manifests/sub-dialogs.csv new file mode 100644 index 0000000000..6d98501245 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/manifests/sub-dialogs.csv @@ -0,0 +1,2 @@ +name,imagePath,tool,menuPath,postClicks +dictionary-configuration-manager,Docs/migration/dialogs/images/dictionary-configuration-manager-before.png,lexiconDictionary,Tools|Configure|{0},Manage Layouts... diff --git a/openspec/changes/legacy-screenshot-capture/manifests/tools.csv b/openspec/changes/legacy-screenshot-capture/manifests/tools.csv new file mode 100644 index 0000000000..10a83f9729 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/manifests/tools.csv @@ -0,0 +1,68 @@ +toolId,folder,imagePath +AdhocCoprohibEdit,tools,Docs/migration/tools/images/adhoc-coprohib-edit-01.png +Analyses,tools,Docs/migration/tools/images/analyses-01.png +bulkEditEntriesOrSenses,tools,Docs/migration/tools/images/bulk-edit-entries-or-senses-01.png +categoryBrowse,tools,Docs/migration/tools/images/category-browse-01.png +complexConcordance,tools,Docs/migration/tools/images/complex-concordance-01.png +compoundRuleAdvancedEdit,tools,Docs/migration/tools/images/compound-rule-advanced-edit-01.png +concordance,tools,Docs/migration/tools/images/concordance-01.png +corpusStatistics,tools,Docs/migration/tools/images/corpus-statistics-01.png +EnvironmentEdit,tools,Docs/migration/tools/images/environment-edit-01.png +featuresAdvancedEdit,tools,Docs/migration/tools/images/features-advanced-edit-01.png +grammarSketch,tools,Docs/migration/tools/images/grammar-sketch-01.png +interlinearEdit,tools,Docs/migration/tools/images/interlinear-edit-01.png +lexiconBrowse,tools,Docs/migration/tools/images/lexicon-browse-01.png +lexiconClassifiedDictionary,tools,Docs/migration/tools/images/lexicon-classified-dictionary-01.png +lexiconDictionary,tools,Docs/migration/tools/images/lexicon-dictionary-01.png +lexiconEditPopup,tools,Docs/migration/tools/images/lexicon-edit-popup-01.png +lexiconEdit,tools,Docs/migration/tools/images/lexicon-edit-01.png +lexiconProblems,tools,Docs/migration/tools/images/lexicon-problems-01.png +naturalClassedit,tools,Docs/migration/tools/images/natural-classedit-01.png +notebookBrowse,tools,Docs/migration/tools/images/notebook-browse-01.png +notebookDocument,tools,Docs/migration/tools/images/notebook-document-01.png +notebookEdit,tools,Docs/migration/tools/images/notebook-edit-01.png +phonemeEdit,tools,Docs/migration/tools/images/phoneme-edit-01.png +phonologicalFeaturesAdvancedEdit,tools,Docs/migration/tools/images/phonological-features-advanced-edit-01.png +PhonologicalRuleEdit,tools,Docs/migration/tools/images/phonological-rule-edit-01.png +posEdit,tools,Docs/migration/tools/images/pos-edit-01.png +ProdRestrictEdit,tools,Docs/migration/tools/images/prod-restrict-edit-01.png +rapidDataEntry,tools,Docs/migration/tools/images/rapid-data-entry-01.png +reversalToolBulkEditReversalEntries,tools,Docs/migration/tools/images/reversal-tool-bulk-edit-reversal-entries-01.png +reversalToolEditComplete,tools,Docs/migration/tools/images/reversal-tool-edit-complete-01.png +spelling,tools,Docs/migration/tools/images/spelling-01.png +toolBulkEditPhonemes,tools,Docs/migration/tools/images/tool-bulk-edit-phonemes-01.png +toolBulkEditWordforms,tools,Docs/migration/tools/images/tool-bulk-edit-wordforms-01.png +wordListConcordance,tools,Docs/migration/tools/images/word-list-concordance-01.png +affixCategoryEdit,lists,Docs/migration/lists/images/affix-category-edit-01.png +annotationDefEdit,lists,Docs/migration/lists/images/annotation-def-edit-01.png +anthroEdit,lists,Docs/migration/lists/images/anthro-edit-01.png +chartmarkEdit,lists,Docs/migration/lists/images/chartmark-edit-01.png +charttempEdit,lists,Docs/migration/lists/images/charttemp-edit-01.png +complexEntryTypeEdit,lists,Docs/migration/lists/images/complex-entry-type-edit-01.png +confidenceEdit,lists,Docs/migration/lists/images/confidence-edit-01.png +dialectsListEdit,lists,Docs/migration/lists/images/dialects-list-edit-01.png +domainTypeEdit,lists,Docs/migration/lists/images/domain-type-edit-01.png +educationEdit,lists,Docs/migration/lists/images/education-edit-01.png +extNoteTypeEdit,lists,Docs/migration/lists/images/ext-note-type-edit-01.png +featureTypesAdvancedEdit,lists,Docs/migration/lists/images/feature-types-advanced-edit-01.png +genresEdit,lists,Docs/migration/lists/images/genres-edit-01.png +languagesListEdit,lists,Docs/migration/lists/images/languages-list-edit-01.png +lexRefEdit,lists,Docs/migration/lists/images/lex-ref-edit-01.png +locationsEdit,lists,Docs/migration/lists/images/locations-edit-01.png +morphTypeEdit,lists,Docs/migration/lists/images/morph-type-edit-01.png +peopleEdit,lists,Docs/migration/lists/images/people-edit-01.png +positionsEdit,lists,Docs/migration/lists/images/positions-edit-01.png +publicationsEdit,lists,Docs/migration/lists/images/publications-edit-01.png +recTypeEdit,lists,Docs/migration/lists/images/rec-type-edit-01.png +restrictionsEdit,lists,Docs/migration/lists/images/restrictions-edit-01.png +reversalToolReversalIndexPOS,lists,Docs/migration/lists/images/reversal-tool-reversal-index-pos-01.png +roleEdit,lists,Docs/migration/lists/images/role-edit-01.png +semanticDomainEdit,lists,Docs/migration/lists/images/semantic-domain-edit-01.png +senseStatusEdit,lists,Docs/migration/lists/images/sense-status-edit-01.png +senseTypeEdit,lists,Docs/migration/lists/images/sense-type-edit-01.png +statusEdit,lists,Docs/migration/lists/images/status-edit-01.png +textMarkupTagsEdit,lists,Docs/migration/lists/images/text-markup-tags-edit-01.png +timeOfDayEdit,lists,Docs/migration/lists/images/time-of-day-edit-01.png +translationTypeEdit,lists,Docs/migration/lists/images/translation-type-edit-01.png +usageTypeEdit,lists,Docs/migration/lists/images/usage-type-edit-01.png +variantEntryTypeEdit,lists,Docs/migration/lists/images/variant-entry-type-edit-01.png diff --git a/openspec/changes/legacy-screenshot-capture/proposal.md b/openspec/changes/legacy-screenshot-capture/proposal.md new file mode 100644 index 0000000000..51571b8895 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/proposal.md @@ -0,0 +1,51 @@ +## Why + +The Phase-1 migration backlog now has a markdown doc for every in-scope WinForms component +(`Docs/migration/` — 227 stubs + `INVENTORY.md`), but the docs have **no legacy "truth" +screenshots**. Those PNGs are the reliable visual baseline that guides faithful Avalonia +recreation. The obvious capture route — UIA2 automation via winforms-mcp — **cannot reach most +surfaces**: FLEx's left Area/Tool navigator is a custom-drawn `SilSidePane`/`OutlookBar` not +exposed to UIA2 (no element, no coordinate-click), menu popups are flaky, and ~100 dialogs are +conditional (only appear on specific data/error states). winforms-mcp renders the main window and +open modal dialogs fine, but it cannot *navigate* to them. + +Two non-UIA routes, validated against the code, get past this: +- The shell already supports **programmatic navigation**: `FieldWorks.exe` parses its args into + `FwAppArgs` and, when the link `HasLinkInformation`, opens the named tool at startup + (`Src/Common/FieldWorks/FieldWorks.cs`; link format `FwLinkArgs.kFwUrlPrefix` = + `silfw://localhost/link?app=flex&database=&tool=&guid=`). +- A standalone tool can open a **language project** `LcmCache` and host FLEx controls + (the `LCMBrowser` pattern), so legacy dialogs can be constructed and rendered directly — + reaching even the conditional dialogs navigation can't. + +## What Changes + +- **Option 2 — launch-per-tool capture script.** A worktree-aware PowerShell script reads a + **capture manifest** of tools/lists (derived from `INVENTORY.md`), and for each: launches + `FieldWorks.exe -db "Sena 3" ""`, waits for the main window, screenshots it to + the target doc's `images/` path, and closes. Covers the ~67 navigable Area/Tool and list-editor + screens with real data. +- **Option 3 — dialog screenshot harness.** A standalone net48 WinForms host (`ScreenshotHarness`) + opens the Sena 3 `LcmCache`, and for each entry in a **dialog registry** (dialog name → factory + that does `new XDlg()` + `SetDlgInfo(cache, …)` with seeded data), shows the dialog and renders it + to a PNG. Covers the constructable dialogs — including conditional delete/restore/warning dialogs + that navigation cannot reach. The harness is also the reusable rig for future legacy↔Avalonia + parity screenshots. +- **Wire-in + ledger.** Each captured PNG is referenced from its `Docs/migration/**` doc; a + `capture-ledger.md` records captured / skipped (with reason) / on-pickup, and `INVENTORY.md` + is annotated with capture state. + +Scope = the **reachable Phase-1 set**, data from the **Sena 3** language project, **UIMode=Legacy** +only. Explicitly out of scope (stay on-pickup): Views/Gecko-coupled dialogs that need a live +`IVwRootBox`/`IVwSelection` (e.g. `RelatedWords`, `SummaryDialogForm`, dictionary HTML preview) and +the 12 non-visual Phase-2 internals. + +This is **tooling only** — a dev script plus a standalone harness project. It adds no product +dependency, changes no shipping code path, and never writes to the language project. + +## Capabilities + +### New Capabilities +- `legacy-screenshot-capture`: A repeatable way to produce legacy-WinForms truth PNGs for the + migration docs via (a) link-driven launch-per-tool capture and (b) a dialog-construction harness, + with a manifest, an output convention, and an explicit reachable-vs-on-pickup boundary. diff --git a/openspec/changes/legacy-screenshot-capture/specs/legacy-screenshot-capture/spec.md b/openspec/changes/legacy-screenshot-capture/specs/legacy-screenshot-capture/spec.md new file mode 100644 index 0000000000..f507d0e128 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/specs/legacy-screenshot-capture/spec.md @@ -0,0 +1,60 @@ +## ADDED Requirements + +### Requirement: Legacy truth PNGs are captured for the reachable Phase-1 set + +The toolkit SHALL produce a legacy-WinForms screenshot for each reachable Phase-1 capture target in +`Docs/migration/INVENTORY.md`, written to the image path that target's doc references, captured from +the Sena 3 language project with `UIMode=Legacy`. It SHALL NOT modify the language project. + +#### Scenario: A tool screen is captured by link-driven launch +- **WHEN** the capture script processes a tool target with id `` +- **THEN** it SHALL launch `FieldWorks.exe -db "Sena 3"` with a `silfw://…&tool=` link, wait + for the main window, and save a non-blank screenshot to the target's `images/-NN.png` + +#### Scenario: A constructable dialog is captured by the harness +- **WHEN** the harness processes a dialog target registered with a factory +- **THEN** it SHALL open the Sena 3 cache, construct the dialog via its factory with seeded data, + render it, and save a non-blank screenshot to the target's image path + +#### Scenario: An unreachable target is recorded, not silently skipped +- **WHEN** a target is Views/Gecko-coupled, fails to construct headless, or is a non-visual internal +- **THEN** it SHALL be recorded in `capture-ledger.md` with a reason and an `on-pickup` disposition, + and SHALL NOT be reported as captured + +### Requirement: Each surface gets a before/after pair from the same data, attached to its JIRA ticket + +Each migration doc SHALL present the legacy "before" PNG and the Avalonia "after" PNG of the SAME +seeded data side by side (`-before.png` / `-after.png` in the doc's `images/`). The +"after" SHALL be produced by the surface's Avalonia visual test (the `fieldworks-semantic-render-parity` +lane), not an ad-hoc screenshot. Both PNGs SHALL be attached to the surface's JIRA ticket. + +#### Scenario: Before/after use the same data +- **WHEN** a surface has both a legacy and an Avalonia implementation +- **THEN** the "before" and "after" PNGs SHALL be rendered from the same seeded objects/input so the + visual comparison is honest, and the doc SHALL show them side by side + +#### Scenario: The after-lane is the parity visual test, not a one-off +- **WHEN** the Avalonia "after" PNG is produced +- **THEN** it SHALL come from the surface's render/visual parity test so it doubles as a parity baseline + +### Requirement: Navigation and capture use supported, non-destructive paths + +The toolkit SHALL drive surfaces through supported code paths — the `FwAppArgs`/`FwLinkArgs` link +mechanism for tools and `new Dlg()` + `SetDlgInfo(...)` construction for dialogs — and SHALL leave +the language project unchanged (open read-only or discard; dialogs closed via Cancel/Escape). + +#### Scenario: Capture never commits project changes +- **WHEN** any capture target has been processed +- **THEN** the Sena 3 project data SHALL be unchanged from before the run + +### Requirement: The harness is a non-shipping capture utility + +The screenshot harness SHALL be non-shipping: either an `[Explicit]` test fixture in an existing +test project or a standalone dev tool. It SHALL NOT be installed, SHALL NOT run in the normal test +suite, and SHALL NOT be referenced by any product code path. + +#### Scenario: No product code depends on the harness, and it stays out of the normal suite +- **WHEN** the harness is added to the build +- **THEN** no product (shipping) MSBuild project SHALL reference it, the installer SHALL NOT include + it, and a default `./test.ps1` run SHALL NOT execute its captures (it runs only under an explicit + capture filter) diff --git a/openspec/changes/legacy-screenshot-capture/tasks.md b/openspec/changes/legacy-screenshot-capture/tasks.md new file mode 100644 index 0000000000..3041bfcfc4 --- /dev/null +++ b/openspec/changes/legacy-screenshot-capture/tasks.md @@ -0,0 +1,31 @@ +# Tasks — legacy-screenshot-capture + +Tooling-only change. Scope = reachable Phase-1 set, Sena 3 data, UIMode=Legacy. Validate with +`openspec validate legacy-screenshot-capture --strict`. + +## Block 1 — Capture manifest + shared infra +- [x] 1.1 Generate `manifests/tools.csv` (`toolId, folder, imagePath`) from `INVENTORY.md` + tool docs. + (dialogs registry lives in the harness fixture, not a CSV — see Block 3.) +- [x] 1.2 UIMode=Legacy is the default; the worktree's `Output/Debug/FieldWorks.exe` is launched. +- [x] 1.3 Window-capture via PrintWindow (PW_RENDERFULLCONTENT) in the script; blank-detection via file size. + +## Block 2 — Option 2: launch-per-tool capture script +- [x] 2.1 `scripts/migration-capture/Capture-LegacyTools.ps1`: guid-less silfw link per tool, launch, + wait for load, PrintWindow capture → imagePath, graceful close (releases project lock). +- [x] 2.2 Proven on lexiconBrowse / concordance / semanticDomainEdit (correct tool, non-blank). +- [x] 2.3 Ran all 67 targets: **67/67 captured**; log `manifests/tools-capture.log`; wired into docs. + +## Block 3 — Option 3: dialog screenshot harness (PIVOT: piggyback test fixture, not standalone exe) +- [x] 3.1 `LexTextControlsTests/ScreenshotHarnessTests.cs` — `[Explicit]` `[Category("ScreenshotHarness")]` + fixture on the in-memory-cache base (reg-free COM + LcmCache already bootstrapped). See design.md decision. +- [x] 3.2 Generic `Capture(Form, file, docRelDir)` helper (off-screen Show + DrawToBitmap + non-blank assert). +- [x] 3.4 Proven on `FeatureSystemInflectionFeatureListDlg` → `feature-chooser-01.png` (green, visually verified). +- [ ] 3.5 Wire the remaining constructable dialogs (one `[Test]` each; bespoke seed + `SetDlgInfo`). + Dialogs outside LexTextControls reuse the same fixture pattern in their own test project. INCREMENTAL — + best done as each dialog's JIRA is worked; unmapped/Views-coupled → recorded `on-pickup` in the ledger. + +## Block 4 — Wire-in, ledger, gates, retrospective +- [x] 4.1 Tool/list docs (66) + feature-chooser doc carry their `## What it looks like` image ref. +- [x] 4.2 `capture-ledger.md` reconciles targets → captured / on-pickup; `INVENTORY.md` capture state. +- [x] 4.3 `./test.ps1 -TestProject LexTextControlsTests` build green with the fixture added. +- [x] 4.4 Retrospective: capture recipes folded into `fieldworks-winapp`; ledger entry in the hub skill. diff --git a/scripts/migration-capture/Capture-LegacyTools.ps1 b/scripts/migration-capture/Capture-LegacyTools.ps1 new file mode 100644 index 0000000000..4beca5eea4 --- /dev/null +++ b/scripts/migration-capture/Capture-LegacyTools.ps1 @@ -0,0 +1,100 @@ +<# +.SYNOPSIS + Option 2 — launch-per-tool legacy screenshot capture for the migration docs. + + For each tool in the manifest, launches legacy FLEx pointed at that tool via a + guid-less silfw link (FwLinkArgs / FieldWorks.cs FwAppArgs), waits for the project + to load, captures the main window with PrintWindow, and closes gracefully (releasing + the project lock) before the next tool. + + Capture is read-only: it never edits the language project. UIMode is left at its + default (Legacy). Runs unattended; no MCP required. + +.NOTES + Proven recipe: FieldWorks.exe "silfw://localhost/link?app=flex&database=&server=&tool=" + (sole argument, NO guid param — an empty guid crashes FwLinkArgs parsing). +#> +[CmdletBinding()] +param( + [string]$Worktree, + [string]$Project = "Sena 3", + [string]$Manifest, + [string]$Only, # capture only this toolId (proof runs) + [int]$TimeoutSec = 150, # max wait for project load + [int]$PaintDelayMs = 4000, # let the tool finish painting before capture + [switch]$Force # recapture even if the PNG already exists +) +$ErrorActionPreference = 'Stop' +# Resolve defaults in the body where $PSScriptRoot is reliably bound. +if (-not $Worktree) { $Worktree = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path } +if (-not $Manifest) { $Manifest = Join-Path $Worktree 'openspec\changes\legacy-screenshot-capture\manifests\tools.csv' } +$exe = Join-Path $Worktree 'Output\Debug\FieldWorks.exe' +if (-not (Test-Path $exe)) { throw "FieldWorks.exe not found: $exe (build the worktree first)" } + +Add-Type -ReferencedAssemblies System.Drawing -TypeDefinition @" +using System;using System.Runtime.InteropServices;using System.Drawing;using System.Drawing.Imaging; +public struct CapRect { public int Left, Top, Right, Bottom; } +public static class Cap { + [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr hdc, uint flags); + [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out CapRect r); + public static void Save(IntPtr hwnd, string path) { + CapRect r; GetWindowRect(hwnd, out r); + int w = r.Right - r.Left, h = r.Bottom - r.Top; + if (w < 1 || h < 1) throw new Exception("zero-size window"); + using (var bmp = new Bitmap(w, h)) + using (var g = Graphics.FromImage(bmp)) { + IntPtr hdc = g.GetHdc(); + PrintWindow(hwnd, hdc, 2u); // PW_RENDERFULLCONTENT + g.ReleaseHdc(hdc); + bmp.Save(path, ImageFormat.Png); + } + } +} +"@ + +function Wait-Loaded([System.Diagnostics.Process]$p, [int]$timeoutSec) { + $sw = [Diagnostics.Stopwatch]::StartNew() + while ($sw.Elapsed.TotalSeconds -lt $timeoutSec) { + Start-Sleep -Milliseconds 600 + if ($p.HasExited) { return "exited" } + $p.Refresh(); $t = $p.MainWindowTitle + if ($t -like '*FieldWorks Language Explorer*') { return "loaded" } + if ($t -eq 'An error has occurred') { return "error-dialog" } + } + return "timeout" +} + +function Close-Flex([System.Diagnostics.Process]$p) { + if ($p -and -not $p.HasExited) { + $null = $p.CloseMainWindow() + if (-not $p.WaitForExit(8000)) { try { $p.Kill() } catch {} ; $p.WaitForExit(5000) | Out-Null } + } + # ensure no stray instance holds the project lock before the next relaunch + Get-Process FieldWorks -ErrorAction SilentlyContinue | ForEach-Object { try { $_.Kill(); $_.WaitForExit(5000) } catch {} } +} + +$rows = Import-Csv $Manifest +if ($Only) { $rows = $rows | Where-Object { $_.toolId -eq $Only } } +$results = @() +foreach ($row in $rows) { + $img = Join-Path $Worktree $row.imagePath + if ((Test-Path $img) -and -not $Force) { $results += [pscustomobject]@{tool=$row.toolId; status='skip-exists'}; continue } + New-Item -ItemType Directory -Force -Path (Split-Path $img) | Out-Null + $enc = [uri]::EscapeDataString($Project) + $link = "silfw://localhost/link?app=flex&database=$enc&server=&tool=$($row.toolId)" + Get-Process FieldWorks -ErrorAction SilentlyContinue | ForEach-Object { try { $_.Kill(); $_.WaitForExit(5000) } catch {} } + $p = Start-Process -FilePath $exe -ArgumentList $link -PassThru + $state = Wait-Loaded $p $TimeoutSec + $status = $state + if ($state -eq 'loaded') { + Start-Sleep -Milliseconds $PaintDelayMs + $p.Refresh() + try { [Cap]::Save($p.MainWindowHandle, $img); $status = 'captured' } + catch { $status = "capture-failed: $($_.Exception.Message)" } + } + Close-Flex $p + $results += [pscustomobject]@{tool=$row.toolId; status=$status} + Write-Host ("{0,-40} {1}" -f $row.toolId, $status) +} +Write-Host "`n=== summary ===" +$results | Group-Object status | ForEach-Object { Write-Host ("{0,5} {1}" -f $_.Count, $_.Name) } diff --git a/scripts/migration-capture/Capture-MenuDialogs.ps1 b/scripts/migration-capture/Capture-MenuDialogs.ps1 new file mode 100644 index 0000000000..f566dc2099 --- /dev/null +++ b/scripts/migration-capture/Capture-MenuDialogs.ps1 @@ -0,0 +1,400 @@ +<# +.SYNOPSIS + Option 2 (live-app) capture for MENU-LAUNCHED legacy dialogs that cannot be + constructed headless (Gecko / live IVwRootSite / clerk-backed / bespoke objects). + + For each manifest row: launch legacy FLEx live at the row's tool (silfw link), + drive the real WinForms menu via native System.Windows.Automation (UIA) -- expand + each parent menu, Invoke the leaf -- then detect the modal dialog with Win32 + EnumWindows, PrintWindow-capture it, and close it read-only (WM_CLOSE = Cancel). + + Why native UIA (not winforms-mcp, not keystrokes): + * winforms-mcp keystrokes are window-station isolated (Alt+mnemonic never lands); + * winforms-mcp click_menu_item only walks the main-window tree, so it can't see + the dropdown's separate top-level popup window (FLEx populates items lazily); + * UIA InvokePattern.Invoke() is a direct accessibility call -- no focus needed. + + Invoking a control that opens a MODAL dialog makes UIA Invoke() block until the UI + goes idle (which a modal never does) -> it times out (0x80131505). That timeout is + EXPECTED and means the dialog opened; we then capture via Win32 (reliable while the + modal blocks the UI thread, unlike UIA). + + Read-only: never edits the project; closes each dialog with WM_CLOSE (Cancel). +#> +[CmdletBinding()] +param( + [string]$Worktree, + [string]$Project = "Sena 3", + [string]$Manifest, + [string]$Only, # capture only this row name (proof runs) + [int]$TimeoutSec = 150, # max wait for project load + [int]$PaintDelayMs = 4000, # let the tool paint before driving menus + [int]$DialogWaitMs = 8000, # max wait for the modal window to appear (post-invoke) + [switch]$Force # recapture even if the PNG already exists +) +$ErrorActionPreference = 'Stop' +if (-not $Worktree) { $Worktree = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path } +if (-not $Manifest) { $Manifest = Join-Path $Worktree 'openspec\changes\legacy-screenshot-capture\manifests\menu-dialogs.csv' } +$exe = Join-Path $Worktree 'Output\Debug\FieldWorks.exe' +if (-not (Test-Path $exe)) { throw "FieldWorks.exe not found: $exe" } + +Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes +Add-Type -ReferencedAssemblies System.Drawing -TypeDefinition @" +using System;using System.Collections.Generic;using System.Runtime.InteropServices;using System.Drawing;using System.Drawing.Imaging;using System.Text; +public struct R{public int L,T,Rt,B;} +public static class W{ + public delegate bool EnumProc(IntPtr h,IntPtr p); + [DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc cb,IntPtr p); + [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h,out uint pid); + [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h); + [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr h,StringBuilder s,int n); + [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h,out R r); + [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h,IntPtr hdc,uint f); + [DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr h,uint msg,IntPtr w,IntPtr l); + [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); + [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); + [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h); + [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h,int n); + [DllImport("user32.dll")] public static extern bool AttachThreadInput(uint a,uint b,bool f); + [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId(); + [DllImport("user32.dll")] public static extern bool SetCursorPos(int x,int y); + [DllImport("user32.dll")] public static extern void mouse_event(uint f,uint dx,uint dy,uint d,IntPtr e); + [DllImport("user32.dll")] public static extern void keybd_event(byte vk,byte scan,uint f,IntPtr e); + public const uint WM_CLOSE=0x0010; + // Real synthesized mouse input — the actual path WinForms menus expect. Unlike UIA Expand/Invoke + // (which never truly opens FLEx's nested submenus), a real click opens a dropdown that renders and + // stays open, so cascades work and the items become UIA-readable for the next level. + public static void Move(int x,int y){ SetCursorPos(x,y); } + public static void Click(int x,int y){ SetCursorPos(x,y); System.Threading.Thread.Sleep(60); mouse_event(0x0002,0,0,0,IntPtr.Zero); mouse_event(0x0004,0,0,0,IntPtr.Zero); } + public static void RightClick(int x,int y){ SetCursorPos(x,y); System.Threading.Thread.Sleep(60); mouse_event(0x0008,0,0,0,IntPtr.Zero); mouse_event(0x0010,0,0,0,IntPtr.Zero); } + // Ctrl+Tab: advances a focused WinForms TabControl to the next tab (used to walk tabs that aren't UIA TabItems). + public static void CtrlTab(){ keybd_event(0x11,0,0,IntPtr.Zero); keybd_event(0x09,0,0,IntPtr.Zero); keybd_event(0x09,0,0x0002,IntPtr.Zero); keybd_event(0x11,0,0x0002,IntPtr.Zero); } + // Defeat the Windows foreground lock so the FLEx window becomes ACTIVE -> its WinForms menu + // dropdowns/flyouts stay open through a multi-level cascade (a non-active window's dropdowns + // collapse instantly, which is why background-driven cascades failed). + public static bool Foreground(IntPtr h){ + // Tapping Alt makes the OS treat our process as having just received input, which lifts the + // foreground lock so SetForegroundWindow is honored (the documented workaround). + keybd_event(0x12,0,0,IntPtr.Zero); // VK_MENU down + keybd_event(0x12,0,0x0002,IntPtr.Zero); // VK_MENU up (KEYEVENTF_KEYUP) + uint pp; uint tgt=GetWindowThreadProcessId(h,out pp); + uint cur=GetCurrentThreadId(); + AttachThreadInput(cur,tgt,true); + ShowWindow(h,5); BringWindowToTop(h); SetForegroundWindow(h); + AttachThreadInput(cur,tgt,false); + return GetForegroundWindow()==h; + } + public static List Wins(uint target){var L=new List();EnumWindows((h,p)=>{uint pp;GetWindowThreadProcessId(h,out pp);if(pp==target&&IsWindowVisible(h))L.Add(h);return true;},IntPtr.Zero);return L;} + public static string Title(IntPtr h){var sb=new StringBuilder(512);GetWindowText(h,sb,512);return sb.ToString();} + public static void Save(IntPtr h,string path){R r;GetWindowRect(h,out r);int w=r.Rt-r.L,ht=r.B-r.T;if(w<1||ht<1)throw new Exception("zero-size window");using(var b=new Bitmap(w,ht))using(var g=Graphics.FromImage(b)){IntPtr hdc=g.GetHdc();PrintWindow(h,hdc,2u);g.ReleaseHdc(hdc);b.Save(path,ImageFormat.Png);}} + public static void Close(IntPtr h){SendMessage(h,WM_CLOSE,IntPtr.Zero,IntPtr.Zero);} + public static int[] Rect(IntPtr h){R r;GetWindowRect(h,out r);return new int[]{r.L,r.T,r.Rt,r.B};} +} +"@ + +$AE = [System.Windows.Automation.AutomationElement] +$TS = [System.Windows.Automation.TreeScope] +$CTRL = [System.Windows.Automation.ControlType] +$root = $AE::RootElement + +function New-PidCond([int]$processId) { [System.Windows.Automation.PropertyCondition]::new($AE::ProcessIdProperty, $processId) } +function New-MenuItemCond([string]$name) { + $cn = [System.Windows.Automation.PropertyCondition]::new($AE::NameProperty, $name) + $ct = [System.Windows.Automation.PropertyCondition]::new($AE::ControlTypeProperty, $CTRL::MenuItem) + [System.Windows.Automation.AndCondition]::new($cn, $ct) +} +function Get-AllTopWindows { + for ($a = 0; $a -lt 5; $a++) { try { return $root.FindAll($TS::Children, [System.Windows.Automation.Condition]::TrueCondition) } catch { Start-Sleep -Milliseconds 150 } } + return @() +} +# Menu-bar items live in the main window; dropdown/flyout items live in separate top-level POPUP +# windows that aren't enumerable as process children. Search popups FIRST (small/fast, before the +# flyout collapses), main window LAST (its huge subtree is the slow fallback for menu-bar items). +function Find-MenuItemAnywhere([IntPtr]$mainHandle, [string]$name) { + $and = New-MenuItemCond $name + $wins = Get-AllTopWindows + $popups = @(); $mains = @() + foreach ($w in $wins) { + $h = [IntPtr]::Zero + try { $h = [IntPtr]$w.Current.NativeWindowHandle } catch {} + if ($h -eq $mainHandle) { $mains += $w } else { $popups += $w } + } + foreach ($w in ($popups + $mains)) { try { $hit = $w.FindFirst($TS::Descendants, $and); if ($hit) { return $hit } } catch {} } + return $null +} +# WinForms MenuStrip items expose ExpandCollapse (top-level) / Invoke; expand is idempotent. +function Expand-Item([System.Windows.Automation.AutomationElement]$el) { + try { $el.GetCurrentPattern([System.Windows.Automation.ExpandCollapsePattern]::Pattern).Expand(); return $true } catch {} + try { $el.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke(); return $true } catch {} + return $false +} +function Get-ItemCenter([System.Windows.Automation.AutomationElement]$el) { + try { $r = $el.Current.BoundingRectangle; if ($r.Width -lt 1 -or $r.Height -lt 1) { return $null }; return @([int]($r.X + $r.Width/2), [int]($r.Y + $r.Height/2)) } catch { return $null } +} +# Find a clickable element by normalized name inside one window (a dialog), via FromHandle. +# IMPORTANT: FLEx exposes its named buttons (OK/Cancel/"Change..."/"Manage...") as controlType **Pane**, +# not Button (only combobox dropdowns are Button) — so match by name across clickable-ish controlTypes +# (Button, Pane, MenuItem, TabItem, Hyperlink, ListItem), not just Button. +function Find-ClickableCenter([IntPtr]$hwnd, [string]$name) { + $target = Norm $name + $types = @($CTRL::Button, $CTRL::Pane, $CTRL::MenuItem, $CTRL::TabItem, $CTRL::Hyperlink, $CTRL::ListItem) + try { + $el = $AE::FromHandle($hwnd) + foreach ($ct in $types) { + $c = [System.Windows.Automation.PropertyCondition]::new($AE::ControlTypeProperty, $ct) + foreach ($it in $el.FindAll($TS::Descendants, $c)) { + try { if ((Norm $it.Current.Name) -eq $target -and $it.Current.IsEnabled) { return (Get-ItemCenter $it) } } catch {} + } + } + } catch {} + return $null +} +# topmost FLEx process window that is new (not in $seen) and has a real (non-empty, non-main) title +function Find-NewDialog([int]$processId, [hashtable]$seen, [string]$mainTitle) { + foreach ($h in [W]::Wins([uint32]$processId)) { + if (-not $seen.ContainsKey([int64]$h)) { + $t = [W]::Title($h) + if ($t -and $t.Trim().Length -gt 0 -and $t -ne $mainTitle) { return @($h, $t) } + } + } + return $null +} +# FLEx sets each menu item's UIA Name to a DESPACED form ("Project Management" -> "ProjectManagement", +# "Send/Receive" -> "SendReceive"), so match on a whitespace-stripped, case-insensitive comparison rather +# than an exact Name condition (that despacing is why every multi-word menu label failed to match). +function Norm([string]$s) { if (-not $s) { return '' } ; return ($s -replace '\s', '').ToLowerInvariant() } +# Walk each of the process's visible top-level windows (main window for menu-bar items; the dropdown/flyout +# POPUP windows for nested items). Win32 EnumWindows reliably lists the popups (root::Descendants does not); +# AutomationElement.FromHandle gives each popup's small, fast UIA subtree. +function Find-MenuItemInProcWins([int]$processId, [string]$name) { + $target = Norm $name + $ct = [System.Windows.Automation.PropertyCondition]::new($AE::ControlTypeProperty, $CTRL::MenuItem) + foreach ($h in [W]::Wins([uint32]$processId)) { + try { + $el = $AE::FromHandle([IntPtr]$h) + foreach ($it in $el.FindAll($TS::Descendants, $ct)) { + try { if ((Norm $it.Current.Name) -eq $target) { return $it } } catch {} + } + } catch {} + } + return $null +} +# Drive the menu with REAL mouse clicks: foreground FLEx (Alt-tap defeats the foreground lock), click the +# top-level item (opens dropdown), click each nested submenu parent (opens its flyout), click the leaf +# (invokes). Real clicks open dropdowns that genuinely render — unlike UIA Expand. Returns 'ok' or "stage:label". +function Invoke-MenuViaMouse([int]$processId, [IntPtr]$mainHandle, [string[]]$menu, [bool]$skipForeground = $false) { + if (-not $skipForeground) { + if (-not ([W]::Foreground($mainHandle))) { Start-Sleep -Milliseconds 200; [void][W]::Foreground($mainHandle) } + Start-Sleep -Milliseconds 350 + } + for ($i = 0; $i -lt $menu.Count; $i++) { + $label = $menu[$i] + $el = $null; $t = 0 + while (-not $el -and $t -lt 30) { $el = Find-MenuItemInProcWins $processId $label; if (-not $el) { Start-Sleep -Milliseconds 120; $t++ } } + if (-not $el) { return "not-found:$label" } + if ($i -eq ($menu.Count - 1)) { try { if (-not $el.Current.IsEnabled) { return "disabled:$label" } } catch {} } + $c = Get-ItemCenter $el + if (-not $c) { return "no-rect:$label" } + [W]::Click($c[0], $c[1]) + Start-Sleep -Milliseconds 500 + } + return 'ok' +} +# Find the first data row in the main window's browse/list view, to right-click for a context menu. +function Find-FirstRowCenter([IntPtr]$mainHandle) { + $types = @($CTRL::ListItem, $CTRL::DataItem, $CTRL::TreeItem, $CTRL::Custom) + try { + $el = $AE::FromHandle($mainHandle) + foreach ($ct in $types) { + $cond = [System.Windows.Automation.PropertyCondition]::new($AE::ControlTypeProperty, $ct) + foreach ($it in $el.FindAll($TS::Descendants, $cond)) { + try { + $r = $it.Current.BoundingRectangle + if ($r.Width -gt 20 -and $r.Height -gt 5 -and $r.Y -gt 80) { return @([int]($r.X + 30), [int]($r.Y + $r.Height/2)) } + } catch {} + } + } + } catch {} + return $null +} +function Expand-Chain([IntPtr]$mainHandle, [string[]]$labels) { + try { [W]::Foreground($mainHandle) } catch {} # make FLEx active so the cascade stays open + foreach ($lbl in $labels) { + $node = $null; $t = 0 + while (-not $node -and $t -lt 12) { $node = Find-MenuItemAnywhere $mainHandle $lbl; if (-not $node) { Start-Sleep -Milliseconds 120; $t++ } } + if (-not $node) { return $false } + [void](Expand-Item $node) + Start-Sleep -Milliseconds 200 + } + return $true +} + +function Close-Flex([System.Diagnostics.Process]$p) { + if ($p -and -not $p.HasExited) { try { $p.Kill(); [void]$p.WaitForExit(5000) } catch {} } + Get-Process FieldWorks -ErrorAction SilentlyContinue | ForEach-Object { try { $_.Kill(); [void]$_.WaitForExit(5000) } catch {} } +} + +function Capture-One($row) { + $img = Join-Path $Worktree $row.imagePath + if ((Test-Path $img) -and -not $Force) { return 'skip-exists' } + New-Item -ItemType Directory -Force -Path (Split-Path $img) | Out-Null + $menu = @($row.menuPath -split '\|') + $tool = $row.tool; if (-not $tool) { $tool = 'lexiconEdit' } + + Get-Process FieldWorks -ErrorAction SilentlyContinue | ForEach-Object { try { $_.Kill(); [void]$_.WaitForExit(5000) } catch {} } + $enc = [uri]::EscapeDataString($Project) + $link = "silfw://localhost/link?app=flex&database=$enc&server=&tool=$tool" + $p = Start-Process -FilePath $exe -ArgumentList $link -PassThru + + $sw = [Diagnostics.Stopwatch]::StartNew(); $loaded = $false + while ($sw.Elapsed.TotalSeconds -lt $TimeoutSec) { + Start-Sleep -Milliseconds 600 + if ($p.HasExited) { Close-Flex $p; return 'exited-during-load' } + $p.Refresh(); if ($p.MainWindowTitle -like '*FieldWorks Language Explorer*') { $loaded = $true; break } + } + if (-not $loaded) { Close-Flex $p; return 'load-timeout' } + Start-Sleep -Milliseconds $PaintDelayMs + $mainTitle = $p.MainWindowTitle + $mainHandle = [IntPtr]$p.MainWindowHandle + + # the snapshot of top-level windows BEFORE opening the dialog (Win32, reliable) + $before = @{}; foreach ($h in [W]::Wins([uint32]$p.Id)) { $before[[int64]$h] = $true } + + # Optional: LEFT-click a data row first to establish a selection, so selection-gated menu items + # (e.g. Tools->Spelling->Change Spelling on a selected wordform) become visible/enabled. + $preClick = $null + if ($row.PSObject.Properties['preClick']) { $preClick = $row.preClick } + if ($preClick) { + [void][W]::Foreground($mainHandle); Start-Sleep -Milliseconds 400 + $pc = $null; $pt = 0 + while (-not $pc -and $pt -lt 15) { + if ($preClick -eq 'firstrow') { $pc = Find-FirstRowCenter $mainHandle } else { $pc = Find-ClickableCenter $mainHandle $preClick } + if (-not $pc) { Start-Sleep -Milliseconds 200; $pt++ } + } + if ($pc) { [W]::Click($pc[0], $pc[1]); Start-Sleep -Milliseconds 600 } + } + + # Optional: right-click a data row first to open a CONTEXT menu, then navigate $menu within it. + $rightClick = $null + if ($row.PSObject.Properties['rightClick']) { $rightClick = $row.rightClick } + if ($rightClick) { + $rc = $null; $rt = 0 + [void][W]::Foreground($mainHandle); Start-Sleep -Milliseconds 400 + while (-not $rc -and $rt -lt 15) { + if ($rightClick -eq 'firstrow') { $rc = Find-FirstRowCenter $mainHandle } + else { $rc = Find-ClickableCenter $mainHandle $rightClick } + if (-not $rc) { Start-Sleep -Milliseconds 200; $rt++ } + } + if (-not $rc) { Close-Flex $p; return "rightclick-target-not-found: $rightClick" } + $invoked = $false; $lastMouse = '' + for ($attempt = 1; $attempt -le 4 -and -not $invoked; $attempt++) { + [void][W]::Foreground($mainHandle); Start-Sleep -Milliseconds 200 + [W]::RightClick($rc[0], $rc[1]); Start-Sleep -Milliseconds 700 + $lastMouse = Invoke-MenuViaMouse $p.Id $mainHandle $menu $true # context menu already open; don't re-foreground + if ($lastMouse -eq 'ok') { $invoked = $true } + elseif ($lastMouse -like 'disabled:*') { Close-Flex $p; return "context-leaf-disabled: $($menu -join '>')" } + else { Start-Sleep -Milliseconds 300 } + } + if (-not $invoked) { Close-Flex $p; return "context-menu-failed ($lastMouse): $($menu -join '>')" } + } else { + # Drive the MAIN menu with REAL mouse clicks (opens dropdowns that genuinely render + cascade, unlike UIA + # Expand which never opens FLEx's nested submenus). Retry the whole path a few times. + $invoked = $false; $lastMouse = '' + for ($attempt = 1; $attempt -le 4 -and -not $invoked; $attempt++) { + $lastMouse = Invoke-MenuViaMouse $p.Id $mainHandle $menu + if ($lastMouse -eq 'ok') { $invoked = $true } + elseif ($lastMouse -like 'disabled:*') { Close-Flex $p; return "leaf-disabled (needs context): $($menu -join '>')" } + else { Start-Sleep -Milliseconds 300 } + } + if (-not $invoked) { Close-Flex $p; return "menu-invoke-failed ($lastMouse): $($menu -join '>')" } + } + + # find the NEW top-level window (the modal dialog) via Win32 -- works while modal blocks UI thread + $dlg = [IntPtr]::Zero; $dtitle = '' + $sw2 = [Diagnostics.Stopwatch]::StartNew() + while ($sw2.Elapsed.TotalMilliseconds -lt $DialogWaitMs -and $dlg -eq [IntPtr]::Zero) { + Start-Sleep -Milliseconds 300 + foreach ($h in [W]::Wins([uint32]$p.Id)) { + if (-not $before.ContainsKey([int64]$h)) { + $t = [W]::Title($h) + # a real dialog has a non-empty title that isn't the main window; menu/dropdown + # popups have empty titles -> reject them so we never capture a leftover dropdown. + if ($t -and $t.Trim().Length -gt 0 -and $t -ne $mainTitle) { $dlg = $h; $dtitle = $t; break } + } + } + } + if ($dlg -eq [IntPtr]::Zero) { Close-Flex $p; return 'no-dialog' } + + # Optional: reach a SUB-dialog by clicking button(s) INSIDE the opened dialog (pipe-separated names). + # e.g. Configure Dictionary -> "Manage Layouts..." opens the configuration-manager sub-dialog. + $postClicks = $null + if ($row.PSObject.Properties['postClicks']) { $postClicks = $row.postClicks } + if ($postClicks) { + $seen = @{}; foreach ($h in [W]::Wins([uint32]$p.Id)) { $seen[[int64]$h] = $true } # parent dialog now counts as seen + $cur = $dlg + foreach ($btn in @($postClicks -split '\|')) { + Start-Sleep -Milliseconds 600 + [void][W]::Foreground($cur) + $c = $null; $bt = 0 + while (-not $c -and $bt -lt 20) { $c = Find-ClickableCenter $cur $btn; if (-not $c) { Start-Sleep -Milliseconds 150; $bt++ } } + if (-not $c) { try { [W]::Close($dlg) } catch {} ; Close-Flex $p; return "subbutton-not-found: $btn" } + [W]::Click($c[0], $c[1]) + $sub = $null; $st3 = [Diagnostics.Stopwatch]::StartNew() + while ($st3.Elapsed.TotalMilliseconds -lt $DialogWaitMs -and -not $sub) { + Start-Sleep -Milliseconds 300 + $sub = Find-NewDialog $p.Id $seen $mainTitle + } + if (-not $sub) { try { [W]::Close($dlg) } catch {} ; Close-Flex $p; return "subdialog-not-opened after: $btn" } + $cur = [IntPtr]$sub[0]; $dtitle = $sub[1]; $seen[[int64]$cur] = $true + } + $dlg = $cur + } + + Start-Sleep -Milliseconds 1200 # let it finish painting + $status = 'no-dialog' + try { [W]::Save($dlg, $img); $status = "captured: '$dtitle'" } catch { $status = "capture-failed: $($_.Exception.Message)" } + + # Optional: walk the dialog's tabs and capture each as -tab-