Skip to content

LT-22691: Answer the reference-choices item menu natively - #1172

Open
mark-sil wants to merge 1 commit into
mainfrom
LT-22691k
Open

mark-sil wants to merge 1 commit into
mainfrom
LT-22691k

Conversation

@mark-sil

@mark-sil mark-sil commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Start here: Src/xWorks/Avalonia/Hosting/ReferenceItemMenuAuthority.cs, the one class that answers the menu. Everything else either wires it into the host or moves a rule into Src/FdoUi/DetailRules so WinForms and Avalonia share it.

What it does. The per-item menu of a reference-vector row in the Avalonia detail view (mnuReferenceChoices: the chips on Subentries, Components, Anthropology Categories and other reference fields) is now answered natively. Nothing on the mediator takes part: the hidden DataTree adapter is never pointed at the row, and the item's object UI is no longer registered as a temporary colleague. Ctrl+click runs the same default jump the same way.

The question to settle. Does the native menu render exactly what the mediator path rendered? For every item of every vector row of the test entry, yes, with one deliberate difference: two leaves that read a WinForms rootbox selection, which the hidden tree never has, were broken on the Avalonia path and now work. Show Subentry under this Component never appeared; the two anthropology-category filter jumps were enabled but did nothing.

Where to look.

  • The 34 Show-in-tool jumps are answered by calling the item's CmObjectUi display and execute methods directly, not re-implemented. Pinned by the leaf-coverage and equivalence tests.
  • ComplexFormVisibility replaces two hand-written ordered-insert loops in DTMenuHandler; the FdoUi tests pin component order and undo.
  • WithoutLeakedSubentryMark drops one item from the equivalence baseline: the adapter leaks Show Subentry as enabled on Publish Sense In. Pre-existing adapter defect; see Evidence.
  • CommandChoice.CommandObject becomes public, temporary until E2b.
  • The anthropology filter jump keeps a PostMessage("FollowLink") moved from DataTree; see the inline comment.

Deliberately not here. The environments chips (mnuEnvReferenceChoices) still use the colleague path; their authority is track A row 2, which deletes BuildItemMenuThroughTheColleague, AddMoveCommands and the Ctrl+click fallback. Converting the deferred FollowLink post belongs with LT-21401's remaining senders. CmdVisibleComplexForm is answered but unreachable: the Complex Forms row composes read-only.

Verification. Build with both hygiene gates clean. Locally green: 11 FdoUi DetailRules tests, 41 FwAvalonia parity and menu tests, the whole 54-test DetailObjectCommandExecutionTests fixture. CI green. Manual pass of 16 scenarios in Lexicon Edit, including the WinForms twins of the shared rules. Not run: the full suite.

Next: approve, or say if the row-2 deletions should land here instead of in their own PR.


Reading this a year from now -- start here

This PR is stage 1, track A, row 1 of the plan that retires the hidden WinForms DataTree the Avalonia detail view keeps alive to answer context-menu commands. The unit of work is one menu id, answered in full by one IDetailMenuAuthority. Earlier rows shipped mnuReorderVector (#1143), the help-topic engine (#1151), the owned-menu bridge (#1153) and the per-object and Help menus (#1161). The plan of record and the per-id estimates live in gitignored working notes under Docs/migration/working/, so the reasoning behind this id is recorded here rather than in the tree.

Decisions, and why

Jumps delegate to CmObjectUi rather than being re-implemented. The 34 jump leaves are answered today by the clicked item's object UI through its OnDisplayJumpToTool / OnJumpToTool pair, including the per-class rules in CmPossibilityUi, LexSenseUi, MoFormUi and PartOfSpeechUi. Calling those methods directly keeps every rule in one place and removes only the mediator hop. The architect agreed an authority may answer a leaf by calling a non-tree object. Re-implementing them natively is engine E7 of the plan, deferred.

Shared rules live in Src/FdoUi/DetailRules. ComplexFormVisibility and AnthroItemFilterLink are control-free, and the WinForms DTMenuHandler and DataTree now call them. The reason is that shared code must keep working once WinForms is removed, which DetailRulesBoundaryTests enforces by reflection over every signature and using in the folder.

CommandChoice.CommandObject is public. The authority needs the Command behind a leaf to pass to the object UI. The alternative, a host-supplied lookup through Mediator.CommandSet, was equally temporary: E2b, first in stage 2, has the bridge hand authorities a command id and label instead of a ChoiceBase, which deletes the dependency either way.

One authority for the item menu, parameterized by row context. It owns mnuReferenceChoices now and takes mnuEnvReferenceChoices in row 2. Authorities partition by the row context they close over, not one per id.

Row 2 is a separate PR. The repository squash-merges, so a second commit here would land as one change answering two ids. Row 2 also needs the caret seam for the five environment insert commands, which is new surface rather than composition.

The FollowLink post stays a mediator call. LT-21401 Part 1 (#921) converted the synchronous SendMessage("FollowLink") senders to Pub/Sub; no PostMessage sender has been converted. Switching this one to Publisher.Publish would move the tool switch inside the click handler, which is the per-site timing analysis that conversion still owes.

Paths not taken
  • Keeping the colleague path for everything and intercepting. Rejected before this PR: the bridge hides a leaf before any interceptor sees it, so an interceptor can never own display, and peeling commands one at a time duplicated rules into Avalonia without reducing the number of things that had to answer.
  • A separate authority per leaf group (jumps, marks, filters, moves). The 110 in-scope context-menu ids draw on 77 messages, so authorities are kept to the row context they close over.
  • Publishing FollowLink through Pub/Sub for the filter jumps. See Decisions.
  • An early exit for Ctrl+click. Finding the default jump materializes all 40 leaves, where the object UI's own Ctrl+click stopped at the first enabled one. The cost is one menu build per click; stopping early needs a bridge change better made with E2b.
Surprising findings
  • The adapter leaks Show Subentry under this Component as enabled. When the hidden tree lands on a slice with no field, which is the sense's summary slice it falls back to for rows it cannot target such as Publish Sense In, DTMenuHandler.OnDisplayAddComponentToPrimary returns before setting any display state, and the leaf keeps xCore's default of visible and enabled. The native path hides it. The equivalence test drops that one item from the colleague-path baseline and names the row in its output.
  • DataTree.DisplayJumpToToolAndFilterAnthroItem dereferenced a null current slice when the row's object was not in the record the hidden tree shows. Only reachable through the adapter; the one line this PR already changed now tolerates it.
  • A native Views access violation in every hidden-tree test on one machine turned out to be stale Obj\Debug\Views objects after a views header change in LT-22674: Cache text analysis only for text NFC leaves unchanged #1166, not a product defect: the native build does not rebuild objects whose included header changed. Remedy: delete that folder and rebuild native. LT-22816: Rebuild native objects when an included header changes #1168 makes header edits rebuild their includers.
Deferred, and what would unblock it
  • mnuEnvReferenceChoices (track A row 2): Describe Error in Environment can be answered from the clicked item now; the five insert commands need the caret position of the row's text editors exposed through the detail model, plus a control-free insert helper shared with the Environments tool's PhEnvStrRepresentationSlice. Owning it deletes BuildItemMenuThroughTheColleague, AddMoveCommands, MoveCommandItem and the Ctrl+click fallback in RunDefaultItemActivation, and retires or re-bases the equivalence test that uses the colleague path as its baseline.
  • E2b (stage 2, first): the bridge walks the menu XML for owned ids, so Build takes a command id and label; CommandChoice.CommandObject can go private again.
  • The deferred FollowLink senders: LT-21401's remaining part.
Evidence

The four contracts every authority carries, all in DetailObjectCommandExecutionTests:

  1. Leaf coverage. ReferenceItemAuthority_AnswersEveryLeafOfItsMenu populates the real mnuReferenceChoices group, asserts at least 40 leaves each with a configuration node, builds every leaf, then builds the whole id through XCoreMenuBridge so a refused submenu fails the test instead of reverting the menu.
  2. Rejection. ReferenceItemAuthority_RejectsALeafItDoesNotAnswer feeds it the Help leaf and expects InvalidOperationException.
  3. Equivalence against the colleague path. ReferenceItemMenu_NativeAuthority_RendersWhatTheColleaguePathRendered_ForEveryItem builds each item of each vector row of the test entry (two subentries, one anthropology category) both ways and compares the rendered trees as text. BuildItemMenuThroughTheColleague is the production method the unowned branch still uses, so the baseline is real code, not a test copy.
  4. No mediator. ReferenceItemMenu_OfASubentry_IsBuiltWithoutTheAdapterOrTheMediator asserts a display spy on the mediator was never asked and the hidden tree was never built.

Behaviour tests: ShowSubentryUnderComponent_OnAComplexFormsComponentsRow_TogglesThePrimaryLexeme toggles the mark both ways on a real complex form and shows the colleague path cannot offer it; AnthropologyCategoryItem_OffersTheFilterJumps_AndItsListJumpAsTheDefault; SubentriesCtrlClick_ResolvesTheClickedEntry_AndRunsTheDefaultJumpPath captures the posted link and checks its tool and target; ItemMenu_IsOwned_ForReferenceChoices_ButNotForEnvironments states in one place which item menu still needs the adapter.

Rules: ComplexFormVisibilityTests (component-order insert, unmark, unlisted component, undo task, variant refs excluded) and AnthroItemFilterLinkTests (field gate, link contents) in FdoUiTests; DetailRulesBoundaryTests unchanged and passing.

Manual (Lexicon Edit, Avalonia detail view unless noted): subentry chip menu contents and both jumps; Ctrl+click; edit saved before a jump; Shift+F10 anchoring; Move Left/Right with end disabling; Show Subentry on a complex form's Components row with checkmark, dictionary effect and undo, and the same in the WinForms view; both anthropology filters from Avalonia and from WinForms; unchanged menus on possibility, Referenced Complex Forms, Environments and label rows.

Preflight review details

Code Review Summary

Branch: LT-22691k
Base: main
Date: 2026-10-01
Review model: Claude Fable 5.1
Files changed: 10

Overview

Stage 1, track A, row 1 of the hidden-DataTree retirement plan (LT-22691): the per-item menu of a reference-vector row (mnuReferenceChoices) is answered natively by a new ReferenceItemMenuAuthority, so the item-menu path no longer points the hidden DataTree adapter at the row or registers the item's object UI as a mediator colleague. The 34 Show-in-tool jumps are answered by calling the item's CmObjectUi display and execute methods directly; the anthropology filter jumps and the two complex-form visibility marks are answered through control-free rules in Src/FdoUi/DetailRules, which the WinForms DTMenuHandler and DataTree now call too.

Analysis found no Critical or Important issues. Two leaves that were broken on the Avalonia path are fixed as a consequence: Show Subentry under this Component (never shown, it read a WinForms selection) and the two anthropology filter jumps (enabled but inert for the same reason).

Contract/API Changes

  • XCore.CommandChoice.CommandObject becomes public (was private). Temporary until E2b hands authorities a command id and label instead of a ChoiceBase.
  • New public static classes ComplexFormVisibility and AnthroItemFilterLink in SIL.FieldWorks.Common.DetailRules (FdoUi).
  • RecordEditView implements the new internal IReferenceItemMenuHost.
  • RecordEditView.BuildReferenceItemMenu's out parameter is renamed itemUi; the object UI is no longer registered as a colleague for an owned menu id, only for mnuEnvReferenceChoices.
  • DataTree.DisplayJumpToToolAndFilterAnthroItem tolerates a null current slice (hidden, as for a non-anthropology field).

Findings

Critical - Must address before merge

None.

Important - Should address before merge

None.

Minor - Consider

  • BuildItemMenuThroughTheColleague, AddMoveCommands and the Ctrl+click fallback duplicate the first-enabled-jump rule the authority implements (author: they serve only mnuEnvReferenceChoices and are deleted in track A row 2, a separate PR)
  • The anthropology filter jump posts FollowLink through the mediator (author: moved from DataTree; LT-21401 Part 1 converted only the synchronous senders, so the deferred posts are converted together later; inline PR comment records it)
  • A Ctrl+click materializes all 40 leaves to find the default jump (author: small per-click cost; an early exit needs a bridge change better made with E2b)

Required Validation / Evidence

  • .\build.ps1 -CommentHygiene -TokenHygiene: clean.
  • .\test.ps1 -TestProject Src/FdoUi/FdoUiTests (DetailRules fixtures): 11/11.
  • .\test.ps1 -TestProject Src/Common/FwAvalonia/FwAvaloniaTests (DetailEditorParityTests, DetailMenuRequestTests): 41/41.
  • .\test.ps1 -TestProject Src/xWorks/xWorksTests (DetailObjectCommandExecutionTests, whole fixture): 54/54.
  • Flex CI green on 4b9fcb6; the final amend 1f6f85e changed only the commit message.
  • Manual: the author's 16-step pass in Lexicon Edit. All passed, 2026-10-01.
  • Known test-environment hazard, not a product issue: hidden-tree tests crash locally with a native Views access violation when Obj\Debug\Views holds stale objects after a views header change (LT-22674: Cache text analysis only for text NFC leaves unchanged #1166). Remedy: delete that folder and rebuild native.

Positive Observations

  • Every leaf of the owned id is answered; the contract test builds the whole menu through the bridge, so an unanswered leaf fails a test instead of reverting the menu to WinForms.
  • The equivalence test compares against the colleague path for every item of every vector row of the test entry and names the one adapter leak it drops from the baseline.
  • Shared rules live in Src/FdoUi/DetailRules with the reflection boundary test unchanged and passing; the WinForms handlers call the same code.
  • The Ctrl+click test proves the posted link's tool and target.

Interview Notes

  • Author reviewed the full diff and ran the manual pass; nothing further to flag.
  • Decision: row 2 (mnuEnvReferenceChoices) is a separate PR, not a second commit here.
  • Decision: keep the deferred FollowLink post as a mediator call pending LT-21401's per-site timing analysis.
  • Decision: CommandChoice.CommandObject public rather than a host-side CommandSet lookup; either is temporary until E2b.

Suggested Review Focus

  • ReferenceItemMenuAuthority.JumpItem: the jumps are answered by calling the item's object UI directly instead of registering it on the mediator.
  • ComplexFormVisibility.Toggle: the ordered insert must match the DTMenuHandler loops it replaces.
  • The equivalence test's WithoutLeakedSubentryMark: the one divergence dropped from the colleague-path baseline.

🤖 Generated with Claude Code


This change is Reviewable

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

NUnit Tests

    1 files  ± 0      1 suites  ±0   13m 39s ⏱️ -11s
6 380 tests +16  6 295 ✅ +16  85 💤 ±0  0 ❌ ±0 
6 389 runs  +16  6 304 ✅ +16  85 💤 ±0  0 ❌ ±0 

Results for commit 1f6f85e. ± Comparison against base commit 37b7024.

♻️ This comment has been updated with latest results.

@codecov-commenter

codecov-commenter commented Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.19431% with 65 lines in your changes missing coverage. Please review.
✅ Project coverage is 39.38%. Comparing base (37b7024) to head (1f6f85e).

Files with missing lines Patch % Lines
...nia/Hosting/RecordEditView.ReferenceVectorMenus.cs 61.64% 23 Missing and 5 partials ⚠️
...rks/Avalonia/Hosting/ReferenceItemMenuAuthority.cs 74.39% 9 Missing and 12 partials ⚠️
Src/xWorks/DTMenuHandler.cs 0.00% 9 Missing and 2 partials ⚠️
Src/Common/Controls/DetailControls/DataTree.cs 16.66% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1172      +/-   ##
==========================================
+ Coverage   39.30%   39.38%   +0.07%     
==========================================
  Files        1523     1526       +3     
  Lines      353047   353161     +114     
  Branches    40750    40771      +21     
==========================================
+ Hits       138782   139102     +320     
+ Misses     185001   184811     -190     
+ Partials    29264    29248      -16     
Files with missing lines Coverage Δ
Src/FdoUi/DetailRules/AnthroItemFilterLink.cs 100.00% <100.00%> (ø)
Src/FdoUi/DetailRules/ComplexFormVisibility.cs 100.00% <100.00%> (ø)
Src/XCore/xCoreInterfaces/Choice.cs 43.34% <ø> (ø)
Src/Common/Controls/DetailControls/DataTree.cs 47.48% <16.66%> (-0.24%) ⬇️
Src/xWorks/DTMenuHandler.cs 27.66% <0.00%> (+3.65%) ⬆️
...rks/Avalonia/Hosting/ReferenceItemMenuAuthority.cs 74.39% <74.39%> (ø)
...nia/Hosting/RecordEditView.ReferenceVectorMenus.cs 60.20% <61.64%> (+4.49%) ⬆️

... and 12 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.

@mark-sil
mark-sil force-pushed the LT-22691k branch 2 times, most recently from ef764a1 to 4b9fcb6 Compare October 1, 2026 18:34
{
SettleDetailEdits();
#pragma warning disable 618 // suppress obsolete warning
m_mediator.PostMessage("FollowLink",

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.

This PostMessage("FollowLink") call was taken from DataTree.OnJumpToToolAndFilterAnthroItem along with the link it posts. It stays a deferred mediator post on purpose: LT-21401 Part 1 (#921) converted only the synchronous SendMessage senders of FollowLink, so this one should be cleaned up together with the other FollowLink PostMessage calls, with the timing analysis that conversion needs.

The per-item menu of a reference-vector row is now answered in full
from the row and the clicked item: the jumps by asking the item's
object UI directly, the anthropology-category filter jumps and the
complex-form visibility marks through rules in FdoUi/DetailRules that
the WinForms handlers call too. Nothing on the mediator takes part, so
the hidden DataTree adapter is never built for it, and a Ctrl+click
runs the same default jump without it.

This fixes two leaves that were broken on the Avalonia path because
they read a WinForms selection the hidden tree never has: Show Subentry
under this Component never appeared, and the two filter jumps were
enabled but did nothing. Show Subentry is now offered on a complex
form's Components row, and the filter jumps carry the clicked category.

The environments item menu keeps the colleague path until its own
authority lands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mark-sil
mark-sil marked this pull request as ready for review October 1, 2026 18:51
@thejambi

thejambi commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Looks good, just a few minor things that could potentially be addressed:

  1. ReferenceItemMenuAuthority.JumpItem: OnDisplayJumpToTool sets TargetId on the
    shared Command, and on the old path the DataTree/MorphologyListener click
    handlers cleared it. Could we reset command.TargetId here, so a chip menu can't
    leave a stale target for the mnuDataTree-* menus that share
    CmdEntryJumpToConcordance and friends?
  2. Equivalence test: this pins rendering, but the old click went through the hidden
    DataTree first (Medium beats Low), and GetGuidForJumpToTool can pick the row's
    owner instead of the chip (the lexiconEdit/notebookEdit owner branches). Is the
    native "always the clicked item" behavior intended? If so, could the PR text say so?
    Or could one FollowLinkSpy check cover a non-Lexicon row?
  3. WithoutLeakedSubentryMark: could we assert the leaks set, so the baseline
    exception stays limited to Publish Sense In?
  4. Host toggles: should these follow CompleteReferenceEdit and stop when Settle()
    rolls back?
  5. Anthro filter test: could it run Execute() and check HvoOfAnthroItem with
    FollowLinkSpy? That is the user-visible fix.

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