Skip to content

LT-22691: Answer the Writing Systems menu natively on multi-string rows - #1169

Merged
mark-sil merged 5 commits into
mainfrom
LT-22691j
Oct 7, 2026
Merged

mark-sil merged 5 commits into
mainfrom
LT-22691j

Conversation

@mark-sil

@mark-sil mark-sil commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Start here: Src/xWorks/Avalonia/Hosting/MultiStringMenuAuthority.cs, then Src/FdoUi/DetailRules/FieldWritingSystemOptions.cs, the rule it answers from.

On a multi-string row in the Avalonia detail view, the Writing Systems submenu is now built and executed natively. Toggles, Show all right now and Configure write the view override directly, a Pronunciation row also keeps the project's pronunciation writing systems in step, and nothing in that menu reaches the hidden WinForms tree. Both detail views take the option list, the default set, the checkmarks and the cannot-empty rule from one shared rule, which also fixes the Avalonia composer rendering a narrower set than its own menu offered.

Two commits, reviewable in order: the shared rule and the WinForms rewire (f3fa244), then the native menu (114ed0a).

The question you arrive with is whether the legacy UI moved. It did not. Equivalence tests compare the rule with the slice's own writing-system query across four shapes, and the one place the first commit did change the slice, rendering a stored selection in stored order, is reverted in the second: rows render in option order, as the slice always did.

Where to look

  • MultiStringMenuAuthority.Owns claims the id only on a genuine multi-string row. The Reversal Entries row binds the same id and keeps the mediator path. Contract test in DetailObjectCommandExecutionTests.
  • RecordEditView.ShowWritingSystems is the one write path for toggles and Configure. The pronunciation sync runs only when every chosen id resolves, so a stale id can never reset the project list. Toggle tests reduce and restore the set.
  • XCoreMenuBridge.ConvertOwnedSubmenu takes a list-populated submenu from IDetailMenuAuthority.BuildList; the bridge's not-supported path is gone. One bridge test.
  • PronunciationWritingSystems.Sync is the only shared rule that writes. Both slices call it, and it joins a unit of work already open.
  • FieldWritingSystemOptions.Select returns option order. Its test was renamed to say so.

Not here: the Configure dialog stays WinForms, its conversion being outside LT-22691; the host and composer each build the rule's spec from the same four facts, kept because unifying them is a layering decision for FwAvalonia; the Reversal Entries row keeps the mediator path; LT-22777, the always-show-data rule, is a composer change. Details below.

Verification: build and both hygiene gates clean. Targeted suites in the table below; full suite not run. Six manual scenario groups by the author on Sena 3, in both views; details below.

Next: approve, or tell me to split the two commits into separate PRs.


Reading this a year from now -- start here

This branch is one step of LT-22691, which retires the invisible WinForms DataTree that still answers the Avalonia detail view's context-menu commands. The work goes one menu id at a time, and each id needs its display and execution rules answered from the Avalonia row alone.

mnuDataTree-MultiStringSlice was the last shared menu group on the hidden path. Counted from the shipped configuration, 111 of the 118 multi-string parts bind either no menu or the empty Help menu, so after this PR their label menus build without the hidden adapter at all. The other seven keep it only for their own menu id.

The first commit makes the rule behind the Writing Systems submenu exist somewhere both UI stacks can reach, and rewires the WinForms slice to it. The second owns the menu. They are separately reviewable, and the first also fixes a live defect on its own.

Decisions, and why

Five design questions were settled before the second commit was written. (1) IDetailMenuAuthority gains BuildList(menuId, listId) for list-populated submenus, since a ListPropertyChoice has no configuration node and no help id to answer from. (2) The native toggle writes the JSON view override only, never the mediator's selected-writing-systems property. (3) The shared rule lives in Src/FdoUi/DetailRules/. (4) The WinForms ConfigureWritingSystemsDlg is reused for now. (5) A new authority owns the id and delegates the six shared leaves to the per-object authority rather than extending it.

Authorities partition by row context, not one per menu id. Measured across the in-scope ids: 110 ids, 77 distinct messages, 71 ids using one message or none. Expect a handful of authority classes in total.

The option list is built from the same writing-system queries the WinForms view makes, argument for argument. Equivalence by construction rather than by inspection. This is why the rule takes the object and the force-English flag.

A spec naming no writing-system set offers nothing, deliberately unlike the render-side resolver, which falls back to the analysis set. Without the guard, every part that omits optionalWs, which is all but one, would offer the analysis writing systems.

Shared rules read the model. The one write, the pronunciation list, is its own rule, and it joins a unit of work already open rather than failing inside one. Seeding the project's pronunciation list stays with the composer; putting it in the shared rule made a WinForms Pronunciation row gain a writing system and broke a render baseline.

Configure treats the same set in any order as no change, and a row renders its selection in option order whatever order it was stored in. xCore appends a re-checked writing system at the end of the stored list, so rendering in stored order made a WinForms row reorder after leaving the entry and coming back.

Single-writing-system rows keep the old resolver. They have no Writing Systems menu and collapse to one writing system regardless.

Surprising findings

The WinForms slice always shows a writing system whose alternative holds data, checked or not; the checkmarks govern only empty alternatives. Nothing on this branch touches that, but it made the manual test look broken: three rows shown with one checked, and unchecking a writing system that held data did not remove its row. The Avalonia composer shows exactly the selection and hides data. That is LT-22777, whose Show-all half this PR fixes and whose data-bearing half remains.

The two views do not share a stored selection. WinForms persists a row's choice as a visibleWritingSystems attribute in the project's .fwlayout file; the Avalonia view persists it in the .viewoverride.json files beside it. A toggle in one view never shows in the other. The one thing both write is the project's pronunciation list, under LT-9620.

Seeding the pronunciation list is not a neutral read. Folding it into the shared rule grew a WinForms Pronunciation row by one writing system and three pixels. A render baseline caught it.

An empty stored selection is unreachable through the UI. The never-blank fallback cannot override a deliberate "show nothing" choice because three guards block an empty selection: the project writing-systems dialog refuses to close, the per-field Configure dialog's list box refuses the last uncheck, and the menu does not offer the last shown entry for unchecking. The trap when checking this is that the Configure dialog's guard lives in the list box's item-check handler, not beside OK or the selection property.

The Reversal Entries row's hidden-tree target is another row. Its field is a virtual property the metadata cache does not know, so the host's slice match falls back to the first realized slice on the sense. The Writing Systems submenu it shows on the mediator path is that other row's. Pre-existing, recorded on the plan.

Paths not taken

Having the bridge walk the menu XML itself for an owned id, instead of going through ChoiceGroup. This is the agreed long-term shape and is booked as the first item of the next stage, because it changes the authority contract and every authority written before it must migrate. Not for this stage.

Teaching ChoiceGroup a non-mediator population path for list submenus. Rejected: it puts Avalonia knowledge into xCore, which is being retired.

Extending the per-object authority to own this id too. Rejected: ownership is per menu id, and the per-object authority would then claim an id on rows where it is not a multi-string row's menu.

Narrowing the menu to match the composer. Loses parity: the Pronunciation field would stop offering the vernacular writing systems and every project would stop offering its active-but-unchecked ones.

Carrying one spec object on the row to remove the duplicate spec builder. The spec type lives in FdoUi and the row type in FwAvalonia, which has no LCModel or FdoUi reference and is kept apart from DetailRules by a boundary test in the other direction. Whether FwAvalonia may depend on LCModel is a decision for the whole Avalonia model layer.

Rewiring the Reversal Entries row. It binds the id but is not a multi-string row, and routing it through the shared rule would narrow what it shows.

Deferred, and what would unblock it
  • The Configure dialog stays WinForms. Converting it is outside LT-22691 and not planned here.
  • The duplicate spec builder in host and composer. Unblocked by a layering decision on FwAvalonia and LCModel.
  • The Reversal Entries row. Needs the plugin row to be owned and to consume the stored selection. On the LT-22691 plan.
  • LT-22777. The composer must union the selection with every option whose alternative holds data. Jira updated to retire its Show-all half.
  • E2b, the bridge walking menu XML for owned ids. First item of the next stage.
Evidence

Build and hygiene: ./build.ps1 -CommentHygiene -TokenHygiene clean, 0 warnings, 0 errors, after each commit.

Targeted suites, final run of the second commit before the row-order fix, then the suites touching the rule re-run after it:

Suite Result
xWorksTests: composer, detail menus, slice filter, the four authority contracts 152 passed
DetailControlsTests: slice, data tree, render baselines, equivalence tests 55 passed, 1 skipped
FdoUiTests: the shared rules and the DetailRules boundary guard 13 passed
FwAvaloniaTests: editor parity 10 passed
MorphologyEditorTests 9 passed
After the row-order fix: FdoUiTests 13, DetailControls equivalence 5, xWorks menu and composer 57 all passed

The full suite was not run. Native tests were skipped; no native code changed.

Manual scenarios, performed by the author on Sena 3 and reported passing. Avalonia detail view unless stated.

  1. Toggle on Citation Form and Gloss: checkmarks match the row, unchecking removes an empty alternative at once, the last one is disabled, re-checking restores it, the set survives a restart, and the old UI's own set is unaffected.
  2. Show all right now reveals every option, is dropped on record change, and is dropped by a toggle.
  3. Configure: title names the field, options and checked set are right, the last uncheck is refused, OK applies, OK-unchanged and Cancel write nothing.
  4. Pronunciation row: a toggle in either view rewrites the project's pronunciation list; Show all does not. Judged by the menu checkmarks, since the old UI always shows data-bearing alternatives.
  5. Field Visibility, Move Field and Help on Lexeme Form, whose menu is half mediator and half native.
  6. Reversal Entries row: the three shared leaves work; the Writing Systems submenu is the mediator path's, see Surprising findings. Grammar > Categories was skipped because that tool has no Avalonia detail view.
Review details

First commit: findings, interview notes and the validation log are in .review/summary.md on the author's working copy; .review/ is gitignored. Six findings raised and fixed, one retracted after the code disproved it, one accepted as a deliberate decision. Second commit: a high-effort agent review raised seven findings; six fixed (a displaced doc comment, two per-object authorities built per menu, Configure treating a reordered set as a change, the pronunciation write using the unit-of-work form that fails inside an open task, the sync resolving through a fallback, the dead interceptor path), one skipped (the duplicate spec builder, above). The author reviewed every line before each commit.

🤖 Generated with Claude Code


This change is Reviewable

A multi-string field's Writing Systems menu offers more writing systems
than the Avalonia composer would resolve, so choosing one the project had
not checked did nothing, and then clearing the others showed every writing
system instead of the one chosen. Each detail view also worked out the
menu for itself, four rules apiece, free to drift.

FieldWritingSystemOptions now answers what a field may offer, what it
shows and what may be switched off. Both views call it: the WinForms slice
for its menu and its visible set, the Avalonia composer for what a
multi-string row renders. The Avalonia menu still runs on the old path;
that part follows.

Two visible changes, both in the Avalonia detail view. Choosing a writing
system the project has not checked now shows it in the row, where before
nothing happened. Show all right now reveals every option rather than only
the checked ones, as the WinForms slice does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.60726% with 83 lines in your changes missing coverage. Please review.
✅ Project coverage is 39.36%. Comparing base (a6d34bd) to head (16784be).

Files with missing lines Patch % Lines
...xWorks/Avalonia/Hosting/RecordEditView.Avalonia.cs 36.53% 27 Missing and 6 partials ⚠️
...c/FdoUi/DetailRules/PronunciationWritingSystems.cs 0.00% 13 Missing and 4 partials ⚠️
...Works/Avalonia/Hosting/MultiStringMenuAuthority.cs 80.48% 1 Missing and 7 partials ⚠️
...Common/Controls/DetailControls/MultiStringSlice.cs 65.00% 5 Missing and 2 partials ⚠️
...nia/ViewDefinition/ViewDefinitionJsonSerializer.cs 44.44% 3 Missing and 2 partials ⚠️
Src/xWorks/Avalonia/Hosting/ObjectMenuAuthority.cs 0.00% 3 Missing ⚠️
...rks/Avalonia/Hosting/ReferenceItemMenuAuthority.cs 0.00% 3 Missing ⚠️
...rks/Avalonia/Hosting/ReorderVectorMenuAuthority.cs 0.00% 3 Missing ⚠️
Src/xWorks/Avalonia/Hosting/XCoreMenuBridge.cs 80.00% 0 Missing and 2 partials ⚠️
...trols/DetailControls/ConfigureWritingSystemsDlg.cs 0.00% 1 Missing ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1169      +/-   ##
==========================================
- Coverage   39.38%   39.36%   -0.03%     
==========================================
  Files        1526     1529       +3     
  Lines      353234   353428     +194     
  Branches    40784    40818      +34     
==========================================
- Hits       139132   139114      -18     
- Misses     184843   185013     +170     
- Partials    29259    29301      +42     
Files with missing lines Coverage Δ
Src/Common/FwAvalonia/Detail/DetailModel.cs 78.55% <ø> (ø)
...n/FwAvalonia/ViewDefinition/ViewDefinitionModel.cs 89.59% <100.00%> (+0.19%) ⬆️
...ia/ViewDefinition/ViewDefinitionOverrideApplier.cs 93.77% <100.00%> (+0.11%) ⬆️
...mon/FwAvalonia/ViewDefinition/XmlLayoutImporter.cs 86.29% <100.00%> (+0.17%) ⬆️
Src/FdoUi/DetailRules/FieldWritingSystemOptions.cs 100.00% <100.00%> (ø)
Src/xWorks/Avalonia/Composer/DetailComposer.cs 70.96% <100.00%> (+0.26%) ⬆️
...trols/DetailControls/ConfigureWritingSystemsDlg.cs 0.00% <0.00%> (ø)
.../xWorks/Avalonia/Hosting/CompositeMenuAuthority.cs 78.94% <75.00%> (+2.75%) ⬆️
Src/xWorks/Avalonia/Hosting/XCoreMenuBridge.cs 85.71% <80.00%> (-1.89%) ⬇️
Src/xWorks/Avalonia/Hosting/ObjectMenuAuthority.cs 66.66% <0.00%> (-4.17%) ⬇️
... and 7 more

... and 6 files with indirect coverage changes

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

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

NUnit Tests

    1 files  ± 0      1 suites  ±0   13m 36s ⏱️ +3s
6 406 tests +25  6 321 ✅ +25  85 💤 ±0  0 ❌ ±0 
6 415 runs  +25  6 330 ✅ +25  85 💤 ±0  0 ❌ ±0 

Results for commit 16784be. ± Comparison against base commit a6d34bd.

This pull request removes 2 and adds 27 tests. Note that renamed tests count towards both.
SIL.FieldWorks.XWorks.DetailObjectCommandExecutionTests ‑ LabelMenu_IsFullyOwned_UnlessItCarriesTheWritingSystemsList
SIL.FieldWorks.XWorks.DetailObjectCommandExecutionTests ‑ OwnedMenu_WithAListSubmenu_IsRefused
FwAvaloniaTests.XmlLayoutImporterTests ‑ Import_OptionalWsAndForceIncludeEnglish_RideTheNode
FwAvaloniaTests.XmlLayoutImporterTests ‑ Import_WithoutOptionalWs_LeavesBothPropertiesUnset
SIL.FieldWorks.Common.Framework.DetailControls.MultiStringSliceWritingSystemsTests ‑ SharedRule_OffersExactlyWhatTheViewOffered(-13,-4)
SIL.FieldWorks.Common.Framework.DetailControls.MultiStringSliceWritingSystemsTests ‑ SharedRule_OffersExactlyWhatTheViewOffered(-3,0)
SIL.FieldWorks.Common.Framework.DetailControls.MultiStringSliceWritingSystemsTests ‑ SharedRule_OffersExactlyWhatTheViewOffered(-4,0)
SIL.FieldWorks.Common.Framework.DetailControls.MultiStringSliceWritingSystemsTests ‑ SharedRule_OffersExactlyWhatTheViewOffered(-5,0)
SIL.FieldWorks.Common.Framework.DetailControls.MultiStringSliceWritingSystemsTests ‑ SharedRule_OffersMoreThanItShows_WhenTheProjectHasAnUncheckedWritingSystem
SIL.FieldWorks.FdoUi.FieldWritingSystemOptionsTests ‑ EveryEntryPoint_RejectsAMissingCacheOrSpec
SIL.FieldWorks.FdoUi.FieldWritingSystemOptionsTests ‑ Menu_CannotUncheckTheLastShownWritingSystem
SIL.FieldWorks.FdoUi.FieldWritingSystemOptionsTests ‑ Menu_ChecksWhatIsShown_AndOffersWhatIsNot
…

♻️ This comment has been updated with latest results.

The multi-string label menu was the last shared menu group still routed
through the hidden WinForms tree. Its Writing Systems submenu was populated
by asking the mediator, which only the hidden adapter's slice could answer,
and each toggle dispatched there and copied the result back into the view
override afterwards.

A new authority owns that menu on multi-string rows. It delegates the Field
Visibility, Move Field and Help leaves to the per-object authority and
answers the writing-system list from the shared rule the previous commit
introduced. Toggles, Show all right now and Configure now write the view
override directly, and a Pronunciation row also keeps the project's current
pronunciation writing systems in step, through a rule the WinForms slice
shares. The bridge learned to take a list-populated submenu's items from an
authority instead of populating it through the mediator.

Counted from the shipped configuration, 111 of the 118 multi-string parts
bind either no menu or the empty Help menu, so their label menus now build
without the hidden adapter at all. The other seven keep it only for their
own menu id.

Rules worth knowing. The authority claims the id only on a genuine
multi-string row; any other row that binds it keeps the mediator path. The
interceptor's writing-system items are gone, since no leaf reaches them any
more. Shared rules read the model; the one write, the pronunciation list,
joins a unit of work that is already open rather than failing inside it.
Configure treats the same set in any order as no change. A row renders its
selection in option order, whatever order it was stored in, so re-checking
a writing system never reorders the rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mark-sil mark-sil changed the title LT-22691: Share the writing-system option rule across detail views LT-22691: Answer the Writing Systems menu natively on multi-string rows Oct 1, 2026

@papeh papeh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. Comments so far (two files yet to review)

Comment thread Src/FdoUi/DetailRules/FieldWritingSystemOptions.cs
Comment thread Src/FdoUi/FdoUiTests/DetailRules/FieldWritingSystemOptionsTests.cs Outdated
Comment thread Src/FdoUi/FdoUiTests/DetailRules/FieldWritingSystemOptionsTests.cs Outdated
// The Writing Systems menu offers writing systems the project has not
// checked. Render from the SAME rule, or a chosen one cannot appear.
var spec = WritingSystemSpecOf(node, hvo);
systems = revealed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect revealed ? shown : options, but I may be misunderstanding something

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revealed means the row is under "Show all right now", so it renders every option; otherwise it renders the shown set. The name reads as if it were the revealed set rather than the row's state.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming revealed to showAllWss would help clarify.

Comment thread Src/Common/FwAvalonia/Detail/DetailModel.cs
Comment thread Src/Common/FwAvalonia/FwAvaloniaTests/ViewDefinitionTests.cs Outdated
Comment thread Src/Common/FwAvalonia/FwAvaloniaTests/ViewDefinitionTests.cs Outdated

@papeh papeh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. One question in DetailComposer; most other comments are about wording.

Comment thread Src/xWorks/xWorksTests/Avalonia/Composer/FieldTypeComposerTests.cs Outdated
Comment thread Src/xWorks/xWorksTests/Avalonia/Composer/FieldTypeComposerTests.cs Outdated
Comment thread Src/xWorks/Avalonia/Hosting/MultiStringMenuAuthority.cs
mark-sil and others added 3 commits October 5, 2026 08:43
Assert messages now state the expectation rather than the outcome, and
one that described the code's history now describes the behaviour. The
importer test for a part without optionalWs is renamed for what it
asserts, and the comments say "expand" rather than "widen" and drop a
mention of the WinForms slice.

The menu authority computes a toggle's resulting set when the item is
clicked rather than for every option on each menu open. The transition
test that compares the native menu with the mediator path says that it
is deleted with that path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brings in #1172. Its ReferenceItemMenuAuthority does not implement
IDetailMenuAuthority.BuildList yet; the next commit adds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReferenceItemMenuAuthority arrived from main without the BuildList
member this branch adds to IDetailMenuAuthority, so the merged tree did
not compile. mnuReferenceChoices has no list-populated submenu, so the
authority refuses every list, as ObjectMenuAuthority and
ReorderVectorMenuAuthority do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@papeh papeh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more comment, then LGTM.

// The Writing Systems menu offers writing systems the project has not
// checked. Render from the SAME rule, or a chosen one cannot appear.
var spec = WritingSystemSpecOf(node, hvo);
systems = revealed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming revealed to showAllWss would help clarify.

@mark-sil
mark-sil merged commit 44b1d04 into main Oct 7, 2026
8 of 9 checks passed
@mark-sil
mark-sil deleted the LT-22691j branch October 7, 2026 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants