Skip to content

LT-22691: Answer the sense menu natively - #1214

Merged
mark-sil merged 1 commit into
mainfrom
claude/lt-22691-pr-e-45419f
Oct 29, 2026
Merged

mark-sil merged 1 commit into
mainfrom
claude/lt-22691-pr-e-45419f

Conversation

@mark-sil

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

Copy link
Copy Markdown
Contributor

Start here: Src/xWorks/Avalonia/Hosting/RecordEditView.LexiconMenus.cs -- the five new Answer registrations in CreateLexiconMenuAuthority, then their answerers: InsertPictureItem/InsertPicture after ChoosePictureFileWithDialog, and SenseNestingItem, NestSense, SplitItem, SplitDetailObject at the end of the file.

In Lexicon Edit's Avalonia detail view, a sense's menu now displays and runs without the hidden WinForms DataTree. Demote, Promote, Move Sense to a New Entry, Delete this Sense and any Subsenses and Insert Picture join the Insert, Move and Merge leaves answered earlier, which completes mnuDataTree-Sense (PinnedOwnedIds 53 -> 54), the last stage 2 id. Three shared rules carry the logic, and the legacy handlers now call them: SenseNesting, DetailObjectSplit and PictureInsertion.

Does legacy behave differently now? Promote's Undo reads "Undo Promote"; it read "Undo Demote". Insert Picture and Insert Sound or Movie now disable themselves on an unknown class or field instead of throwing ConfigurationException. Everything else runs the same code: Move Sense to a New Entry still goes through LexSenseUi.MoveUnderlyingObjectToCopyOfOwner, and the picture dialog is still PicturePropertiesDialog.

Where to look

  • Move Sense to a New Entry keeps the shown record and focuses the nearest surviving row -- MoveSenseToANewEntry_TakesTheSenseOutOfTheShownEntry, where one Undo restores the sense and removes the new entry.
  • Demote's new owner (the sense before, or after for the first) and Promote's position -- SenseNestingTests.
  • A native sense merge does not publish MasterRefresh (LT-22155) -- MergeSense_IntoThePickedSense_InOneUndoableStep; the entries list stayed correct by hand.
  • Four strings in FdoUiStrings: Undo/Redo Demote moved from LexEdStrings, Undo/Redo Promote added (LexEdStrings keeps its pair for reversal entries).

Deliberately not here

  • An inserted picture has no New UI row until pictures compose (LT-22772); the insert is proved headlessly and seen in the legacy view.
  • Stage 4: the Notebook and posEdit ids, and DataTreeCopy.

Verification. Merges into main, no dependency. build.ps1 -CommentHygiene -TokenHygiene is clean. A targeted run of 227 tests across the rule, menu, integrity and command fixtures passed; the full suite was not run locally. A manual pass in Sena 3 (bulukir, adidi, phat) passed in both UIs.

Next: approve, or flag a divergence below to revisit.


Reading this a year from now -- start here

Stage 2 PR E of LT-22691, which retires the hidden WinForms DataTree command adapter from the Avalonia detail view one context-menu id at a time. The predicate-owned LexiconMenuAuthority owns an id once every leaf under it has a native answerer, and LexiconMenuAuthorityTests.PinnedOwnedIds pins the exact owned list. This PR adds answerers for DataTreeDeleteSense, DemoteSense, PromoteSense, DataTreeSplit and InsertPicture, which completes mnuDataTree-Sense and ends stage 2. No working documents were kept on the branch; the reasoning is below.

Deliberate rules
  • Demote makes the sense the last subsense of the sense before it, or of the sense after it when it comes first; it needs a sibling. Promote puts a subsense right after the sense that owns it, at that sense's level. Both are SenseNesting, which LexEntryMenuHandler now calls too.
  • Move Sense to a New Entry is DetailObjectSplit.CanSplitNow: a subsense can always move; a top-level sense only when the entry keeps another. Execution stays with CmObjectUi.MoveUnderlyingObjectToCopyOfOwner (liblcm's MoveSenseToCopy, one undoable step). The native path records the rows to focus before the move, as for a delete, and re-shows only when the sense's owner changed. The shown record stays on the original entry.
  • Insert Picture is offered only when PictureInsertion.TargetField finds the command's field on an object of exactly the command's class, as the legacy check did; an unknown class or field now means "absent" (New UI) or "disabled" (legacy) rather than an exception. The picture goes last in the field, in one undoable step, and the re-show focuses it.
  • Demote, Promote and Move Sense to a New Entry are absent on a row with no object or a ghost row, and on a row whose object the command does not act on (CommandActsOn); they are disabled when the rule refuses.
  • Strings. SenseNesting lives in FdoUi, which cannot reference LexEdDll, so the Demote pair moves to FdoUiStrings and the Promote pair is added there. ReversalEntryPOS keeps using LexEdStrings' Promote pair, since FdoUiStrings is internal.
Avalonia vs legacy divergences
Where Legacy New UI
Promote's Undo text "Undo Promote"; until this PR "Undo Demote" "Undo Promote"
Insert Picture or Insert Sound or Movie with an unknown class or field Disabled; until this PR threw ConfigurationException Absent
Demote, Promote, Move Sense to a New Entry on a row with no object Disabled Absent
After Merge Sense MasterRefresh Re-show, focus on the survivor, no MasterRefresh (LT-22155)
After Insert Picture A picture slice No row until pictures compose (LT-22772)
Decisions, and why
  • Answer Insert Picture natively although no New UI row shows a picture. Leaving one leaf to the adapter would keep the whole id there. The picture is real data: the legacy view shows it, and the New UI will once LT-22772 composes pictures.
  • No MasterRefresh after a native sense merge (LT-22155). The re-show focuses the survivor, and a manual check found the entries list correct. If a stale view turns up, the native merge can publish it then.
  • Split through CmObjectUi, not a new rule. liblcm's MoveSenseToCopy does the work; an authority may call a non-tree object directly, as the reference jumps do.
  • Fix Promote's Undo text in legacy too. The shared rule takes the matching pair; keeping "Undo Demote" for Promote would have meant copying the bug into the shared code.
Evidence
Claim Test
The owned list grows by exactly mnuDataTree-Sense OwnsExactlyThePinnedIds_AndAnswersEveryLeafOfEachOne
A sense's menu renders what the adapter path rendered, with Demote, Split and Promote enabled where they apply SenseMenu_RendersWhatTheAdapterPathRendered_WhereDemotePromoteAndSplitApply
No owned menu asks the mediator or builds the hidden tree (42 rows) OwnedMenus_AreBuiltWithoutTheAdapterOrTheMediator
Demote and Promote: rule, enablement, one Undo each SenseNestingTests (7), DemoteSense_ThenPromote_MovesTheSenseUnderTheOneBefore_AndBack_EachInOneUndoableStep, SenseMenu_DisablesPromoteOnATopLevelSense_AndDemoteOnAnOnlySense
Move Sense to a New Entry: rule and execution DetailObjectSplitTests (3), MoveSenseToANewEntry_TakesTheSenseOutOfTheShownEntry, MoveSenseToANewEntry_IsDisabled_OnTheOnlySense
Delete and merge on a sense DeleteSense_RemovesItAndItsSubsenses_WhenTheUserConfirms, MergeSense_IntoThePickedSense_InOneUndoableStep
Insert Picture: target field, insert, cancel PictureInsertionTests (3), InsertPicture_FromTheSenseMenu_AddsAPictureOfTheFileTheUserPicks_InOneUndoableStep, InsertPicture_LeavesTheModelAlone_WhenTheUserCancels_AndIsAbsentOffASense
Shared rules stay UI-free DetailRulesBoundaryTests

Thirteen new tests in FdoUiTests, nine in xWorksTests. Manual, in Sena 3: Demote, Promote and Delete on bulukir; Demote enablement and Merge Sense on adidi; Move Sense to a New Entry and Insert Picture on phat, then in the legacy view the picture, the menu's enabled states and "Undo Promote". All passed.

Preflight review details

Code Review Summary

Branch: claude/lt-22691-pr-e-45419f
Base: main (merge base 10f08c9)
Date: 2026-10-29
Review model: Claude Opus 5.5
Files changed: 16 (one commit, d1a610d)

Overview

Stage 2 PR E of LT-22691, the last of stage 2: the Lexicon detail view's predicate-owned
LexiconMenuAuthority gains native answerers for Delete this Sense and any Subsenses, Demote,
Promote, Move Sense to a New Entry and Insert Picture, which completes mnuDataTree-Sense; a
sense's menu no longer builds the hidden WinForms DataTree. The rules both views need are
control-free in Src/FdoUi/DetailRules (SenseNesting, DetailObjectSplit,
PictureInsertion) and LexEntryMenuHandler, Slice.GetCanSplitNow and DTMenuHandler call
them. Move Sense to a New Entry still runs through LexSenseUi, and Insert Picture through
PicturePropertiesDialog.

The analysis found no Critical issue. Promote's Undo text was "Undo Demote" in WinForms; the
shared rule uses "Undo Promote" in both views.

Contract/API Changes

  • New public static classes in FdoUi: SenseNesting, DetailObjectSplit, PictureInsertion.
  • Resources: ksUndoDemote, ksRedoDemote move from LexEdStrings to FdoUiStrings;
    ksUndoPromote, ksRedoPromote added to FdoUiStrings (LexEdStrings keeps its pair for
    ReversalEntryPOS). English text unchanged.
  • DTMenuHandler: the private CanInsertPictureOrMediaFile is replaced by
    CurrentSliceObject and InsertTargetField; an unknown class or field on Insert Picture or
    Insert Sound or Movie now disables the command instead of throwing ConfigurationException.
  • Owned ids: mnuDataTree-Sense added (PinnedOwnedIds 53 -> 54).

Findings

Critical - Must address before merge

None.

Important - Should address before merge

  • Moved strings lose their existing translations: ksUndoDemote/ksRedoDemote leave
    LexEdStrings, so Crowdin translations do not follow, and the Promote pair starts
    untranslated in FdoUiStrings.
    (author: as for D2; once the PR merges, the author tells
    Jason to approve them in Crowdin)

Minor - Consider

  • "Undo Promote" lives in both LexEdStrings and FdoUiStrings: one English text,
    two translation units; FdoUiStrings is internal, so ReversalEntryPOS cannot share it.

    (author: fine as is)
  • A misconfigured picture or media command fails silently in WinForms: an unknown
    class or field disables it instead of throwing ConfigurationException.
    (author: fine as
    is)
  • A native sense merge does not publish MasterRefresh (LT-22155). (author's
    decision: try without it, and adjust if a stale view shows up)

Required Validation / Evidence

  • ./build.ps1 -CommentHygiene -TokenHygiene: clean.
  • Targeted ./test.ps1 -TestFilter run, 227 tests, all green: SenseNestingTests,
    DetailObjectSplitTests, PictureInsertionTests, DetailRulesBoundaryTests,
    LexiconMenuAuthorityTests, LexiconMenuExecutionTests,
    DetailObjectCommandExecutionTests, DetailEditorParityTests, DetailMenuRequestTests,
    WindowConfigurationIntegrityTests, DetailObjectMergeTests, DetailObjectDeletionTests,
    MediaFileInsertionTests.
  • /code-review high: 3 findings, all fixed before commit (a duplicated class rule in
    DTMenuHandler, the split test's missing Undo proof, a possible leak of the split's new
    entry).
  • Manual testing by the author in Sena 3, New UI and Legacy, all passed: Demote, Promote and
    Delete on bulukir; Demote enablement and Merge Sense on adidi; Move Sense to a New Entry and
    Insert Picture on phat, then the picture, the menu's enabled states and "Undo Promote" in the
    Legacy UI.
  • Not run: the full test suite locally (CI runs it). The inserted picture cannot be seen in
    the New UI (LT-22772).

Positive Observations

  • PinnedOwnedIds shows exactly one id switching over; the adapter-equivalence test covers
    each sense header with Demote, Split and Promote enabled, and the no-mediator spy covers the
    new messages (42 rows).
  • The WinForms handlers now call the same rules, so the two views cannot drift; each rule has
    its own test file, guarded by DetailRulesBoundaryTests.
  • Every new mutation is one undoable step, and the dialogs sit behind seams the tests replace.

Interview Notes

  • Localization: once the PR merges, the author tells Jason to approve the four strings.
  • Minor findings: all accepted as is.
  • Least obvious part (Move Sense to a New Entry): the explanation was drafted by the review
    model at the author's request, and the author confirmed it matches their understanding.
    Native Move Sense to a New Entry runs the WinForms code: CmObjectUi.MakeUi gives a
    LexSenseUi, whose MoveUnderlyingObjectToCopyOfOwner climbs to the owning entry and calls
    liblcm's MoveSenseToCopy, which makes a new homograph entry holding the sense, as one
    undoable step. The native path adds only what the row needs: before the move it records the
    rows to focus afterwards (RowsToFocusAfterDeleting, as for a delete, since the sense leaves
    the shown entry); afterwards it checks that the sense's owner changed, and when it did not,
    it neither focuses nor re-shows. The shown record stays on the original entry; the new entry
    appears in the entries list. DetailObjectSplit.CanSplitNow is the shared enable rule: a
    subsense can always move, a top-level sense only when the entry keeps another.
  • Nothing else to flag.

Suggested Review Focus

  • RecordEditView.SplitDetailObject: focus rows taken before the move, the owner check
    after it.
  • SenseNesting.Demote/Promote against the LexEntryMenuHandler code they replace.
  • PictureInsertion.TargetField: exact class match, 0 for an unknown class or field.
  • RecordEditView.InsertPicture: a picture with no New UI row yet (LT-22772).

🤖 Generated with Claude Code


This change is Reviewable

The Lexicon detail view now answers a sense's menu without the hidden
DataTree: Demote, Promote, Move Sense to a New Entry, Delete this Sense
and any Subsenses and Insert Picture display and run from the row,
alongside the Insert, Move and Merge leaves already answered. Until now
right-clicking any sense built the WinForms adapter to answer them.

Promote's Undo now reads "Undo Promote" in both views. Until now it
read "Undo Demote".

The rules both views need live in Src/FdoUi/DetailRules and the
WinForms menu handlers call them; the Demote and Promote undo strings
move to FdoUiStrings.

Owned by the native authority from this change:
* mnuDataTree-Sense

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

Copy link
Copy Markdown

NUnit Tests

    1 files  ± 0      1 suites  ±0   15m 10s ⏱️ -9s
6 943 tests +22  6 858 ✅ +22  85 💤 ±0  0 ❌ ±0 
6 952 runs  +22  6 867 ✅ +22  85 💤 ±0  0 ❌ ±0 

Results for commit d1a610d. ± Comparison against base commit 002280d.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.10651% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.35%. Comparing base (10f08c9) to head (d1a610d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ks/Avalonia/Hosting/RecordEditView.LexiconMenus.cs 72.28% 8 Missing and 15 partials ⚠️
Src/xWorks/DTMenuHandler.cs 41.66% 4 Missing and 3 partials ⚠️
Src/LexText/Lexicon/LexEntryMenuHandler.cs 0.00% 2 Missing and 4 partials ⚠️
Src/FdoUi/DetailRules/SenseNesting.cs 96.96% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1214      +/-   ##
==========================================
+ Coverage   40.29%   40.35%   +0.05%     
==========================================
  Files        1547     1550       +3     
  Lines      355556   355635      +79     
  Branches    41446    41479      +33     
==========================================
+ Hits       143258   143502     +244     
+ Misses     182980   182819     -161     
+ Partials    29318    29314       -4     
Files with missing lines Coverage Δ
Src/Common/Controls/DetailControls/Slice.cs 60.24% <100.00%> (-0.46%) ⬇️
Src/FdoUi/DetailRules/DetailObjectSplit.cs 100.00% <100.00%> (ø)
Src/FdoUi/DetailRules/PictureInsertion.cs 100.00% <100.00%> (ø)
Src/FdoUi/DetailRules/SenseNesting.cs 96.96% <96.96%> (ø)
Src/LexText/Lexicon/LexEntryMenuHandler.cs 27.30% <0.00%> (+0.97%) ⬆️
Src/xWorks/DTMenuHandler.cs 26.77% <41.66%> (-0.08%) ⬇️
...ks/Avalonia/Hosting/RecordEditView.LexiconMenus.cs 70.89% <72.28%> (+0.49%) ⬆️

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

@thejambi thejambi 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.

:lgtm:

@thejambi reviewed 16 files and all commit messages, and made 1 comment.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on mark-sil).

@mark-sil
mark-sil merged commit 2eeca0d into main Oct 29, 2026
9 checks passed
@mark-sil
mark-sil deleted the claude/lt-22691-pr-e-45419f branch October 29, 2026 16:15
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