Repository navigation
Conversation
In the Avalonia detail view, Delete Translation and Delete Complex Form Info now run from the row itself: the Lexicon menu authority answers every DataTreeDelete leaf, so mnuDataTree-Translation and mnuDataTree-ComplexFormSpec no longer build the hidden DataTree. After the delete, the re-show gives keyboard focus to the nearest surviving row, the one before the deleted object's rows when it survives. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1182 +/- ##
==========================================
+ Coverage 39.52% 39.59% +0.06%
==========================================
Files 1538 1540 +2
Lines 354569 354689 +120
Branches 41090 41123 +33
==========================================
+ Hits 140159 140454 +295
+ Misses 185034 184864 -170
+ Partials 29376 29371 -5
🚀 New features to boost your workflow:
|
thejambi
left a comment
There was a problem hiding this comment.
@thejambi reviewed 14 files and all commit messages, and made 3 comments.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on mark-sil).
Src/Common/FwAvalonia/Detail/DataTree.cs line 565 at r1 (raw file):
editor.Dispose(); _vectors.Clear(); _labelBlocks.Clear();
RebuildItems resets the other per-build collections here but not _rowFocusTargets. After a header toggle, the map keeps entries for rows that are no longer built, and those entries point at disposed editors. Today's restore can't reach that because it runs on a fresh tree, but clearing it with its siblings keeps FocusRow safe if something calls it later.
_labelBlocks.Clear();
_rowFocusTargets.Clear();
Src/FdoUi/FdoUiCore.cs line 1071 at r1 (raw file):
public bool ConfirmDelete() { var mainWindow = m_propertyTable.GetValue<Form>("window");
The other public members begin with CheckDisposed(). Should this one?
Src/xWorks/Avalonia/Hosting/RecordEditView.LexiconMenus.cs line 78 at r1 (raw file):
return false; } var focusRows = RowsToFocusAfterDeleting(m_avaloniaEntryForm?.ShownDetail, target);
Codecov shows DetailHostControl.ShownDetail and FocusRowOnNextShow only ever taking the null branch. So in LexiconMenuExecutionTests, focusRows is always empty and lines 78–89 never run under test. The manual pass caught the vanishing-header bug on this exact path. Would a test asserting that the focus request reaches the host be cheap? I know you accepted this in preflight, so fine to leave it if it isn't.
thejambi
left a comment
There was a problem hiding this comment.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on mark-sil).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review, @thejambi. Replies to the three discussions:
I tried adding the assertion and it isn't cheap. The hand-off relies on the re-show being posted with |
thejambi
left a comment
There was a problem hiding this comment.
@thejambi reviewed 2 files and all commit messages, and resolved 3 discussions.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on mark-sil).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thejambi
left a comment
There was a problem hiding this comment.
@thejambi reviewed 4 files and all commit messages.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on mark-sil).
thejambi
left a comment
There was a problem hiding this comment.
@thejambi made 1 comment.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on mark-sil).
Start here:
Src/xWorks/Avalonia/Hosting/RecordEditView.LexiconMenus.cs, the Delete answerer and the host that runs it. ThenSrc/FdoUi/DetailRules/DetailObjectDeletion.cs, the rule both detail views now call.What it does. Stage 2, PR A of the hidden-DataTree retirement: the first answerer of
LexiconMenuAuthority. Delete Translation and Delete Complex Form Info run from the Avalonia row: hidden on a ghost or object-less row, disabled with the WinForms wording when the object is gone or is the last item of a required field, otherwise deleted through the object'sCmObjectUiwith its confirmation dialog, in one undo step. The re-show then focuses the nearest surviving neighbour row.mnuDataTree-TranslationandmnuDataTree-ComplexFormSpecno longer build the hidden tree.The question to settle. Message-level ownership reaches further than two ids. Every id whose leaves all send
DataTreeDeleteis now owned, 17 in all, pinned by area inLexiconMenuAuthorityTests. Two are live posEdit rows (Inflection Class Name, Stem Allomorph Label) and go native with the same engine; the rest are ids no part binds or ids of tools without an Avalonia detail view.Where to look.
DetailObjectDeletion: the enablement and wording lifted fromSlice.GetCanDeleteNowandDTMenuHandler.OnDisplayDataTreeDelete; both call it. Pinned byDetailObjectDeletionTestsand the DetailRules boundary test.CmObjectUi.DeleteUnderlyingObject(Func<bool>): the confirm dialog split out behind a delegate; the parameterless method behaves as before.LexiconMenuExecutionTestsdeletes headlessly through it, confirm and decline.RowsToFocusAfterDeletingandDataTree.FocusFirstRow: neighbours nearest first, previous before next, because a section header dies with its last item.DetailFocusMemoryTestspins the fallback.Deliberately not here. E3 Move Up/Down and E6 Merge:
mnuDataTree-AlternateFormis bound to no WinForms slice andmnuDataTree-CmMediais unreachable, so both engines move to the PR that ownsmnuDataTree-Allomorph. The empty-owned-menu fallback and the record-level delete path, noted in the preflight details.Verification. Build and both hygiene gates clean. 8 FdoUi, 26 FwAvalonia and 80 xWorks targeted tests green. Manual pass in Lexicon Edit and Category Edit against the WinForms menus, 13 steps, all passed; it found the focus defect this PR then fixed. Not run: the full suite.
Next: approve, or tell me what to change.
Reading this a year from now -- start here
This PR is stage 2, PR A of the plan that retires the hidden WinForms
DataTreethe Avalonia detail view keeps alive to answer context-menu commands. PR 1 (#1177) built owned menus from the menu XML and added an emptyLexiconMenuAuthoritythat owns an id by predicate: when every leaf's message has an answerer. This PR adds the first answerer, Delete (engine E4 of the plan). Later PRs add Insert, Jump, Swap, Move, Merge and the lexical-relation commands, and the last one ownsmnuDataTree-Sense. The plan of record lives in gitignored working notes underDocs/migration/working/, so the reasoning is recorded here rather than in the tree.Decisions, and why
The rule lives in
Src/FdoUi/DetailRules, and WinForms calls it too.Slice.GetCanDeleteNowand the label code inDTMenuHandler.OnDisplayDataTreeDeletewere replaced by calls toDetailObjectDeletion, so there is one copy of the rule and it keeps working when WinForms is removed. The folder's boundary test forbids any UI dependency.Confirmation is a delegate on
CmObjectUi, not a host-side switch.DeleteUnderlyingObject(Func<bool> confirm)keeps the dialog, the undo task and the related-file prompt where they were; the parameterless method passesConfirmDelete. The host owns aDeleteConfirmationseam the tests set to answer for the user. The repo already does this onFwWritingSystemSetupModel.ConfirmDeleteWritingSystem, andDummyCmObjectUialready reaches past the dialog inFdoUiTests.Focus after a delete is a candidate list, not a row. The rows before the deleted object's rows, nearest first, then the rows after; the rebuilt tree focuses the first that exists and can take focus, falling back to the row's field-menu button when its editor is hidden. A single row failed in the manual pass: deleting the last translation removes the "Translations" section header too.
The Delete answerer ignores the command's
<parameters>. The WinForms handler reads only the current slice's object, so the row's object is the target. The atomic-wrapping and variant-back-ref target rules are needed by no id in this PR and will be shared when the PRs that need them arrive.Ownership stays message-level across the whole configuration. Narrowing it to the Lexicon area would reopen the predicate PR 1 settled. The pin lists all 17 ids by area so the reach is visible.
Surprising findings
mnuDataTree-CmMediais unreachable in both UIs. The LexPronunciation detail layout's<part ref="MediaFiles" param="FileAndLabel"/>namesLexPronunciation-Detail-MediaFiles, which exists in no parts file;DataTree.ProcessPartRefNodeandXmlLayoutImporterboth omit a missing part. Media rows never render under a pronunciation.mnuDataTree-AlternateFormis bound to no WinForms slice. The id sits only on the<seq field="AlternateForms">node; every allomorph row takes its caller menumnuDataTree-Allomorphor-AffixProcessfrom the MoForm layouts. In Avalonia it reaches only the empty-sequence prompt row, which WinForms does not have.mnuDataTree-Translationlands on two Avalonia rows, the item header "Translations N" and the multistring row, both targeting the CmTranslation; WinForms has only the latter.Delete X*(cannot delete this)*. The shared rule reproduces that through the sameStringTablecall; no reachable row in this PR hits the disabled case.Paths not taken
CmObjectUioverload. Rejected: it would skip theCmObjectUiconstruction the product uses, proving less.-AlternateFormproved unreachable: an engine ships with its first consumer, which ismnuDataTree-Allomorph.ConfirmDeleteObjectDlgis unchanged WinForms code; the seam tests everything around it.Deferred, and what would unblock it
OnDetailMenuRequested; reachable only by an owned id whose every leaf hides on a row with no other menu group. Fix when such an id exists.DeleteDetailObjectreturns false for both a cancel and the DeleteRecord delegation; becomes relevant when a record-level row is owned.CanDeleteNow's required-atomic branch has no real-data test; needs a stubICmObject.XWorksAppTestBase.Preflight review details
Code Review Summary
Branch: LT-22691n
Base: main (merge base a90754c, 1 commit ahead)
Date: 2026-10-13
Review model: Claude Fable 5.1 (Claude Code)
Files changed: 14
Overview
Stage 2, PR A of the hidden-DataTree menu-id retirement (LT-22691): the first answerer
of
LexiconMenuAuthority. EveryDataTreeDeleteleaf is answered from the Avalonia row:hidden on a ghost or object-less row, disabled with the WinForms wording when the object
is gone or is the last item of a required field, otherwise deleted through the object's
CmObjectUiwith its confirmation dialog in one undo step. The re-show then focuses thenearest surviving neighbour row.
mnuDataTree-TranslationandmnuDataTree-ComplexFormSpecno longer build the hidden tree. The enablement and wording rule moved to
Src/FdoUi/DetailRules/DetailObjectDeletion.cs, which the WinForms slice and menuhandler now call.
The analysis found no Critical or Important issue. The inventory behind the branch
found two of the four planned ids unreachable (
-CmMedia: its pronunciation detail partdoes not exist;
-AlternateForm: bound to no WinForms slice), so E3 and E6's merge halfmoved out of this PR. Message-level ownership makes the authority own 17 ids across the
configuration, two of them live posEdit rows; the author accepted that reach and tested
it manually.
Contract/API Changes
CmObjectUi.DeleteUnderlyingObject(Func<bool> confirm)andCmObjectUi.ConfirmDelete()added (public). The parameterless
DeleteUnderlyingObject()delegates to them andbehaves as before.
SIL.FieldWorks.Common.DetailRules.DetailObjectDeletion(FdoUi).DetailField.RowAutomationId;DataTree.RequestRowFocusOnRebuild(IReadOnlyList<string>);DetailHostControl.FocusRowOnNextShow(IReadOnlyList<string>)andShownDetail;DetailFocusMemory.Mementogains an optional trailing constructor parameter andRowAutomationIds.RecordEditView.CreateLexiconMenuAuthority()becomes(DetailField)(internal).Findings
Critical - Must address before merge
None.
Important - Should address before merge
None.
Minor - Consider
FocusRowfocused a target without checking it was effectively visible, so acollapsed row dropped focus silently (fixed during review: falls back to the
field-menu button and returns whether focus was taken)
(the "Translations" header goes when its last item does), found in the manual pass
(fixed during review: an ordered candidate list, previous rows nearest first then
following rows; the rebuilt tree focuses the first that exists)
An owned menu whose every leaf hides builds zero items and the host falls back(author: pre-existing bridge behaviour; no id in this PR canto the adapter menu
reach it)
DeleteDetailObjectcannot tell a cancel from the DeleteRecord delegation(author: only matters once a record-level row is owned, in a later PR)
(author: no LCM classCanDeleteNow's required-atomic branch has no testrequires an owning atomic field, so the branch is reachable only through a stub)
No end-to-end assertion that the focus request was made(author: the staticrow choice and the FwAvalonia seam are each tested; accepted)
Three xWorks fixtures duplicate the spy, Describe and setup helpers(author:known debt, tracked at low priority)
Focus candidates are computed from the shown model, which could lag under a held(author: edge case; accepted)refresh
it", "as
Slice.HandleDeleteCommanddoes") (fixed during review: removed; therule is now recorded for future work)
Required Validation / Evidence
.\build.ps1 -CommentHygiene -TokenHygiene: clean..\test.ps1 -TestProject Src/FdoUi/FdoUiTests -TestFilter "FullyQualifiedName~DetailObjectDeletionTests|FullyQualifiedName~DetailRulesBoundaryTests": 8 passed..\test.ps1 -TestProject Src/Common/FwAvalonia/FwAvaloniaTests -TestFilter "FullyQualifiedName~DetailFocusMemoryTests|FullyQualifiedName~DetailMenuTests|FullyQualifiedName~DataTreeTabNavigationIntegrationTests|FullyQualifiedName~DetailEditorParityTests": 26 passed..\test.ps1 -TestProject Src/xWorks/xWorksTests -TestFilter "FullyQualifiedName~LexiconMenuAuthorityTests|FullyQualifiedName~LexiconMenuExecutionTests|FullyQualifiedName~DetailObjectCommandExecutionTests|FullyQualifiedName~DetailMenuRequestTests": 80 passed.against the WinForms menus: all passed after the focus-rule fix.
Positive Observations
and the DetailRules boundary test keeps it free of UI dependencies.
switches over.
delete run headlessly end to end for the first time.
Interview Notes
vanishing header) was fixed and re-verified.
the predicate, which PR 1 settled.
CmObjectUioverload over a host-side delete delegate, after therepo's existing seams (
FwWritingSystemSetupModel.ConfirmDeleteWritingSystem,DummyCmObjectUi,IEnvironmentMenuHost) were shown as precedent.was rebased from LT-22691m onto main (no conflicts) and the stacked-PR plan dropped.
In-Review Quality Check
Gated build and the three targeted test runs above were rerun after the last edit.
Suggested Review Focus
DetailObjectDeletion.CanDeleteNowagainstSlice.GetCanDeleteNowat the merge baseRowsToFocusAfterDeletingordering andDataTree.FocusFirstRowLexiconMenuAuthorityTests: is the posEdit reach wanted now🤖 Generated with Claude Code
This change is