Skip to content

LT-22691: Answer the Notebook menus natively - #1240

Merged
mark-sil merged 1 commit into
mainfrom
claude/lt-22691-notebook-6bbe5a
Nov 11, 2026
Merged

mark-sil merged 1 commit into
mainfrom
claude/lt-22691-notebook-6bbe5a

Conversation

@mark-sil

@mark-sil mark-sil commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

The Avalonia detail view answers every Notebook Edit menu itself. Until
now a right-click on a subrecord or the Subrecords section, and the main
menu's text and Demote Record commands, needed the hidden DataTree.

Structured-text rows get the text menu WinForms shows: Cut, Copy, Paste,
Find in Dictionary and Add to Dictionary (LT-22861). Promote, Demote and
the word rules are shared with WinForms in DetailRules.

Promote publishes JumpToRecord through Pub/Sub in both views, at once and
window-scoped, in place of SummarySlice's queued mediator broadcast.


Reviewer notes

Start here: Src/xWorks/Avalonia/Hosting/RecordEditView.NotebookMenus.cs, every Notebook answer: the subrecord commands, the text menu, and Demote Record. Then Src/FdoUi/DetailRules/RecordNesting.cs, the Promote/Demote rule both views now call.

The question to bring: does a text command act on the text the user right-clicked? The menu is built from what the editor held at the right-click; the command runs on the saved paragraph after pending edits settle. It runs only if that paragraph still holds the same text, compared in NFD because LCM stores text decomposed, with the selection's offsets mapped into the saved text. Otherwise it does nothing and logs.

Where to look

  • Every Notebook row menu is answered natively: NotebookMenuTests.EveryRowMenu_OfARecordWithSubrecordsAndText_IsAnsweredNatively; the owned-id pin in LexiconMenuAuthorityTests shows the four new menus.
  • WinForms behaviour is unchanged: SummarySlice, DataTree and StTextSlice call RecordNesting and TextWordCommands, pinned by RecordNestingTests and TextWordCommandsTests.
  • Word edges follow the native Views rule: TextWordBoundariesTests.
  • Promote's jump is published last, because its filter prompt can replace the view: SummarySlice.OnPromoteSubitemInVector, RecordEditView.NestRecord.
  • A section button whose menu no authority owns now shows its commands disabled instead of running them through the hidden tree: HotlinkItemsFor_AMenuNoAuthorityOwns_ShowsItsCommandsDisabled. Only Words > Analyses has such menus, and that tool is not on in the New UI.

Deliberately not here

  • Participants, See Also and the evidence fields are still Unsupported rows, so their menus do not exist in the New UI (LT-22893, LT-22894).
  • Spelling suggestions (LT-22895), and the Edit menu's Cut, Copy and Paste on New UI text (LT-22896).
  • Insert Record and Subrecord on the main menu, toolbar and pane bar still reach the record list through the mediator, as in Legacy.
  • The Lexicon Edit Popup's pane-bar inserts: since General fix to Lexicon Edit Popup problems #619, pane-bar menus in a pop-up are refused in both UIs.

Verification: build, comment and token hygiene clean; 131 targeted tests passed after #1232 was pulled in, and 34 after the review fixes; 17 manual steps on Sena 3 in both UIs passed. Not covered by tests: running Insert Subrecord, which opens the modal Insert Record dialog. Full suite not run. Main's two newer commits merge cleanly; the merged tree was not built.

Next: approve, or name a Notebook menu that still behaves differently from Legacy.

Decisions, and why
  • Insert Subrecord calls the record list directly (RecordClerk.OnInsertItemInVector), as the reference jumps call CmObjectUi. Rejected: passing the command to the mediator without the hidden tree, which keeps mediator routing in a path the plan is moving off; and copying the record list's rules into a shared rule.
  • JumpToRecord moves to Pub/Sub here, in both views, because the jump needs no delay. SummarySlice's queued BroadcastMessageUntilHandled became an immediate, window-scoped publish; the record list is the only subscriber. The change was checked against the per-site concerns of the JumpToRecord Phase 2 plan.
  • The main menu and toolbar use the selection the editor last reported while it had focus. An Avalonia text box collapses its selection to the caret when it loses focus, so reading it when the menu is clicked would see a caret. The right-click menu reads the selection at request time. Rejected: using only the caret (after a left-to-right drag it sits at the end, so Find in Dictionary would take the last word, not the first), and stopping every editor from collapsing its selection (more invasive).
  • Word boundaries are a C# copy of the native rule (VwTextSelection::FindWordBoundaries), used only by the New UI, because the Avalonia editor is not a Views control. Legacy keeps calling GrowToWord. Rejected: moving StTextSlice onto the C# rule, which changes working code with no visible gain.
  • The text menu is an Avalonia menu built from the configured mnuStTextChoices, replacing the theme's own Cut/Copy/Paste menu, so it gains Find in Dictionary and Add to Dictionary.
  • Cut and Paste change the saved paragraph in one undoable step, not the editor, because opening a menu saves and re-shows the view. The cost is the caret position. Holding the save while a menu is open would change save behaviour for every row, so it is left for later.
  • Paste keeps only the clipboard's first line, like the New UI paragraph editor's own paste, which drops every line after the first. Legacy makes each pasted line a new paragraph; that is a gap in every New UI structured-text field, not only this menu, and is tracked on LT-22896.
  • An unowned section button shows disabled and logs, never runs through the hidden tree. This replaces LT-22693: Show a button per hotlink command on the active section #1232's adapter fallback and its two strings.
  • Strings: none added. The four Undo/Redo Promote and Demote strings leave DetailControlsStrings; both views use the identical FdoUiStrings ones.
Found in passing

Both also happen on main and are kept out of this PR.

  • Undo of a Text-field edit in the New UI reverts the data but needs F5 to redraw: the refresh rule walks owners only, and a record's Text is referenced, not owned. Handed to LT-22692.
  • Ctrl+V while typing in a New UI field crashes with "Nested tasks are not supported": the field's open undo task meets FwXWindow.OnEditPaste's own. Commented on LT-22896.
Preflight review details

Code Review Summary

Branch: claude/lt-22691-notebook-6bbe5a
Base: main (merge base fbf02e6; main has 2 newer commits, #1236 and #1237, which touch
6 of these files and merge cleanly; the merged tree was not built)
Date: 2026-11-10
Review model: Claude Opus 5.5
Files changed: 30 (1 commit, 51570de)

Overview

LT-22691 stage 4, Notebook (with main-menu item 3d). In the New UI, every Notebook Edit
menu is answered by the detail view: the Subrecords section and subrecord menus, their
section buttons, a structured-text row's text menu, and the main menu's and toolbar's Find
in Dictionary and Add to Dictionary and the pane bar's Demote Record. Until now these needed
the hidden DataTree. The text menu is the one WinForms shows (Cut, Copy, Paste, Find in
Dictionary, Add to Dictionary), which also fixes LT-22861. Promote, Demote and the
word-command rules move to Src/FdoUi/DetailRules and both views call them. Promote now
publishes JumpToRecord through Pub/Sub, window-scoped, at once, in both views.

The four analysis passes found no Critical or Important issue. The earlier
/code-review high findings were resolved or decided with the author (below).

Contract/API Changes

  • DetailRules (FdoUi): new RecordNesting, TextWordBoundaries, TextWordCommands.
    WinForms SummarySlice, DataTree (Demote Record) and StTextSlice call them; their
    behaviour is unchanged.
  • DetailControls: new DemotedRecordOwnerChooser (the WinForms chooser both views show);
    DataTree.ChooseNewOwner removed.
  • FwAvalonia: DetailTextSelectionSnapshot, IDetailTextSnapshotSource,
    DetailMenuRequest.TextSnapshot, DataTree.TextSelectionChanged,
    DetailHostControl.CurrentTextSelection; FwStructuredTextField and
    SliceFactoryContext take optional menu and selection callbacks;
    FwReferenceVectorField.PlaceCaretAtPointer is now internal.
  • Composer: a structured-text row whose part names no context menu gets mnuStTextChoices.
  • xWorks: StructuredTextMenuAuthority; Notebook answers added to the Lexicon cluster
    authority and to the Notebook Edit command authority. HotlinkItemsFor no longer runs an
    unowned section button through the hidden tree: it shows its commands disabled and logs.
  • Mediator: SummarySlice's BroadcastMessageUntilHandled("JumpToRecord") is replaced by a
    window-scoped Publisher.Publish(EventConstants.JumpToRecord).
  • Resources: removed ksUndoPromote, ksRedoPromote, ksUndoDemote, ksRedoDemote from
    DetailControlsStrings (identical FdoUiStrings ones are used now) and
    ksHotlinkCommandUnavailable, ksHotlinkCommandDisabled from xWorksStrings. No new strings.
  • No configuration, native or installer change.

Findings

Critical - Must address before merge

None.

Important - Should address before merge

None.

Minor - Consider

  • Text commands silently did nothing after typing accented letters (LCM stores
    decomposed text, the editor keeps what was typed) (fixed: the saved-text check and the
    selection offsets compare in NFD; test added)
  • The toolbar recomputed the word on every idle poll (fixed: worked out once per
    selection; test that it still follows a new selection)
  • A paragraph using a deleted style could throw in the style lookup (checked:
    LcmStyleSheet.GetStyleRgch returns null for an unknown style, and the word rule treats
    null as no properties)
  • Cut and Paste act on the saved paragraph rather than the editor, so the caret
    position is lost
    (author: holding the save while a menu is open changes save behaviour
    for every row; not in this PR)
  • New UI Undo for a subrecord's Move Up/Down reads "Undo Move Item", not "Undo Move
    Up/Down"
    (author: not taken; matching it needs two new xWorks strings)
  • A read-only, mixed-font paragraph looks up its first word wherever you click
    (author: left; these paragraphs hold an embedded object and are rare)
  • Selection clamping is written three times; the text menu has no test through the
    real right-click path
    (author: not taken)

Required Validation / Evidence

  • .\build.ps1 -CommentHygiene -TokenHygiene: clean.
  • .\test.ps1 -CommentHygiene -TokenHygiene -TestFilter ...: 131/131 over NotebookMenuTests,
    LexiconMenuExecutionTests, LexiconMenuAuthorityTests, DetailHotlinkTests and
    DetailFocusMemoryTests after main's LT-22693: Show a button per hotlink command on the active section #1232 was pulled in; 34/34 over the new rule and
    request tests (RecordNestingTests, TextWordBoundariesTests, TextWordCommandsTests,
    StructuredTextMenuRequestTests, NotebookMenuTests) after the review fixes.
  • Manual: the author ran 17 steps on Sena 3 in both UIs; all passed.
  • Not covered by automated tests: Insert Subrecord's execution (it opens the modal Insert
    Record dialog) and the Demote choosers (the tests supply the pick).
  • Full suite not run (targeted runs only). The tree merged with main's 2 newer commits was
    not built.

Positive Observations

  • No new mediator receivers and no new reliance on the hidden tree; one mediator broadcast
    removed.
  • NotebookMenuTests composes a record with subrecords and text and checks that every row
    menu is answered natively; the owned-id pin in LexiconMenuAuthorityTests shows the four
    newly owned menus.
  • The Promote/Demote and word rules are shared, not copied; WinForms calls them.

Interview Notes

Decisions settled with the author during the session (not re-asked here):

  • Insert Subrecord / Subrecord of Subrecord call the record list (RecordClerk) directly,
    as the reference jumps call CmObjectUi; no mediator routing, no copy of its rules.
  • JumpToRecord on Promote moves to Pub/Sub here, in both views, because it needs no delay;
    checked against the JumpToRecord Phase 2 concerns.
  • Main menu and toolbar read the selection the editor last reported while focused, because
    an Avalonia text box collapses its selection when it loses focus; the right-click menu
    reads it at request time.
  • The word boundary is a C# copy of the native Views rule, used only by the New UI; WinForms
    keeps calling GrowToWord.
  • The Lexicon Edit Popup's pane-bar inserts were dropped: since General fix to Lexicon Edit Popup problems #619 PanelMenu_Click
    refuses every pane-bar menu in a pop-up window, in both UIs.
  • Found during manual testing, both also on main, kept out of scope: Undo of a Text-field
    edit needs F5 to redraw (handed to LT-22692); Ctrl+V while typing in a New UI field
    crashes with "Nested tasks are not supported" (commented on LT-22896).
  • Filed for gaps this PR does not close: LT-22893 (Participants), LT-22894 (See Also and
    the evidence fields), LT-22895 (no spell checking), LT-22896 (Edit menu Cut/Copy/Paste).

Suggested Review Focus

  • RecordEditView.NotebookMenus.cs: the saved-paragraph check and NFD offset mapping
    before Cut, Paste and the dictionary commands.
  • FwStructuredTextField.WireParagraphMenu / WireSelectionReports: the theme's text
    menu replaced, and the selection reported only while the paragraph has focus.
  • RecordNesting against the removed SummarySlice / DataTree code: same enable
    rules, same choosers, same moves.

🤖 Generated with Claude Code


This change is Reviewable

The Avalonia detail view answers every Notebook Edit menu itself. Until
now a right-click on a subrecord or the Subrecords section, and the main
menu's text and Demote Record commands, needed the hidden DataTree.

Structured-text rows get the text menu WinForms shows: Cut, Copy, Paste,
Find in Dictionary and Add to Dictionary (LT-22861). Promote, Demote and
the word rules are shared with WinForms in DetailRules.

Promote publishes JumpToRecord through Pub/Sub in both views, at once and
window-scoped, in place of SummarySlice's queued mediator broadcast.

Menus and commands now answered natively:
* mnuDataTree-SubRecords, mnuDataTree-SubRecords-Hotlinks,
  mnuDataTree-Subrecord-Hotlinks, mnuDataTree-SubRecordSummary
* mnuStTextChoices
* CmdLexiconLookup, CmdAddToLexicon, CmdDemoteRecord (Notebook Edit)

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

codecov-commenter commented Nov 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.62706% with 178 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.70%. Comparing base (fbf02e6) to head (51570de).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...s/Avalonia/Hosting/RecordEditView.NotebookMenus.cs 74.03% 35 Missing and 32 partials ⚠️
.../Common/FwAvalonia/Detail/FwStructuredTextField.cs 74.50% 18 Missing and 8 partials ⚠️
...ntrols/DetailControls/DemotedRecordOwnerChooser.cs 0.00% 12 Missing and 1 partial ⚠️
...ks/Avalonia/Hosting/StructuredTextMenuAuthority.cs 43.47% 9 Missing and 4 partials ⚠️
Src/Common/Controls/DetailControls/SummarySlice.cs 0.00% 5 Missing and 6 partials ⚠️
Src/FdoUi/DetailRules/TextWordBoundaries.cs 83.33% 2 Missing and 8 partials ⚠️
Src/FdoUi/DetailRules/RecordNesting.cs 78.57% 3 Missing and 6 partials ⚠️
Src/Common/FwAvalonia/DetailHostControl.cs 33.33% 7 Missing and 1 partial ⚠️
Src/Common/Controls/DetailControls/DataTree.cs 0.00% 3 Missing and 3 partials ⚠️
Src/Common/Controls/DetailControls/StTextSlice.cs 0.00% 3 Missing and 2 partials ⚠️
... and 4 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1240      +/-   ##
==========================================
+ Coverage   40.58%   40.70%   +0.12%     
==========================================
  Files        1560     1567       +7     
  Lines      356379   356898     +519     
  Branches    41646    41755     +109     
==========================================
+ Hits       144620   145264     +644     
+ Misses     182447   182300     -147     
- Partials    29312    29334      +22     
Files with missing lines Coverage Δ
Src/Common/FwAvalonia/Detail/DataTree.cs 92.51% <100.00%> (+0.03%) ⬆️
Src/Common/FwAvalonia/Detail/FwFieldControls.cs 88.04% <100.00%> (+0.07%) ⬆️
Src/Common/FwAvalonia/Detail/SliceFactory.cs 95.69% <100.00%> (+0.14%) ⬆️
...ks/Avalonia/Hosting/RecordEditView.LexiconMenus.cs 70.93% <100.00%> (+0.20%) ⬆️
...Works/Avalonia/Hosting/RecordEditView.MainMenus.cs 78.44% <100.00%> (+1.17%) ⬆️
Src/xWorks/Avalonia/Composer/DetailComposer.cs 74.25% <83.33%> (+1.97%) ⬆️
Src/Common/FwAvalonia/Detail/DetailModel.cs 79.32% <88.88%> (+0.24%) ⬆️
Src/FdoUi/DetailRules/TextWordCommands.cs 88.88% <88.88%> (ø)
...xWorks/Avalonia/Hosting/RecordEditView.Avalonia.cs 63.71% <0.00%> (+1.66%) ⬆️
Src/Common/Controls/DetailControls/StTextSlice.cs 0.00% <0.00%> (ø)
... and 9 more

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

Copy link
Copy Markdown

NUnit Tests

    1 files  ± 0      1 suites  ±0   14m 28s ⏱️ + 3m 15s
7 163 tests +43  7 078 ✅ +43  85 💤 ±0  0 ❌ ±0 
7 172 runs  +43  7 087 ✅ +43  85 💤 ±0  0 ❌ ±0 

Results for commit 51570de. ± Comparison against base commit 36f0082.

This pull request removes 1 and adds 44 tests. Note that renamed tests count towards both.
SIL.FieldWorks.XWorks.LexiconMenuExecutionTests ‑ HotlinkItemsFor_AMenuNoAuthorityOwns_OffersItsCommandsByLabel
FwAvaloniaTests.StructuredTextMenuRequestTests ‑ ARightClick_RaisesTheRowsTextMenu_WithTheParagraphAndItsSelection
FwAvaloniaTests.StructuredTextMenuRequestTests ‑ TheFocusedParagraph_ReportsItsSelection_AsItChanges
SIL.FieldWorks.FdoUi.RecordNestingTests ‑ CanDemoteRecord_OnlyATopLevelRecordWithAnother
SIL.FieldWorks.FdoUi.RecordNestingTests ‑ CanDemoteSubrecord_OnlyASubrecordWithASibling
SIL.FieldWorks.FdoUi.RecordNestingTests ‑ CanPromote_OnlyASubrecord
SIL.FieldWorks.FdoUi.RecordNestingTests ‑ ChooseOwner_TakesTheOnlyCandidate_AndAsksAmongMore
SIL.FieldWorks.FdoUi.RecordNestingTests ‑ Demote_ATopLevelRecord_UnderAnother
SIL.FieldWorks.FdoUi.RecordNestingTests ‑ Demote_MakesTheRecordTheFirstSubrecordOfItsNewOwner_InOneUndoableStep
SIL.FieldWorks.FdoUi.RecordNestingTests ‑ Promote_ASubrecordOfASubrecord_PutsItRightAfterItsOwner_InOneUndoableStep
SIL.FieldWorks.FdoUi.RecordNestingTests ‑ Promote_ASubrecordOfATopLevelRecord_MakesItTopLevel
…

@thejambi

Copy link
Copy Markdown
Contributor

A question about copy/paste behavior and a few small things to consider.

Question: Paste drops everything after the first line break, without telling the user. See PastedText at RecordEditView.NotebookMenus.cs:393.

  • Scenario: copy three lines from Word, right-click a Notebook paragraph, choose Paste. Only the first line lands, and the rest is gone.
  • Does WinForms split such a paste into new paragraphs or act the same?
  • The doc comment and the test name (…UpToALineBreak) show this was deliberate. It isn’t among the PR body’s decisions, though. Is this the intended behaviour until LT-22896? If it is, it’s worth a line in the decisions, plus a log entry when text is dropped.

Nits

  • "mnuStTextChoices" is defined twice: DetailComposer.StructuredTextMenuId and StructuredTextMenuAuthority.MenuId. One could refer to the other.
  • FwReferenceVectorField.PlaceCaretAtPointer became internal so that the structured-text field can call it. A shared text-box helper would be a better home than the reference-vector class.

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

@thejambi reviewed 30 files and all commit messages.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on mark-sil).

@mark-sil

Copy link
Copy Markdown
Contributor Author

Thanks, Zach.

Paste: yes, it's deliberate for now. Legacy doesn't drop the text: each pasted line becomes its own paragraph, and where a field can't take new paragraphs the line breaks become spaces. The New UI paragraph editor's own paste also keeps only the first line (Avalonia's text box does that when it doesn't accept line breaks), so this is a gap in every New UI structured-text field rather than in this menu. I've added it to the PR's decisions and to LT-22896, where the fix belongs: split pasted lines into paragraphs. I left out the log entry, since users never see the log, so it wouldn't tell them anything.

Nits: agreed on both. I'm leaving them out of this PR so it stays as tested.

@mark-sil
mark-sil merged commit c6cd25f into main Nov 11, 2026
8 of 9 checks passed
@mark-sil
mark-sil deleted the claude/lt-22691-notebook-6bbe5a branch November 11, 2026 13:31
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