Conversation
The reversal field now behaves like the WinForms slice it replaces: - The whole field saves once, when focus leaves it, as one undo step; moving between slots saves nothing. - Typing into a group's empty slot opens a fresh one after it, so several entries can be added in one visit. Each slot gets its own row key, so two new slots never overwrite each other. A new slot that is emptied again is removed when the user moves on. - Left and Right (alone or with Ctrl) at a slot's edge move into the neighboring slot, mirrored in right-to-left groups; Home and End go to the edges of the visual line; Enter does nothing. - Unlinking an entry also deletes each parent the deletion leaves with no senses and no subentries, so shortening "arm: hand: finger" to "arm" removes "hand" as well as "finger". - Slots keep a caret's width (the new DataTree.CaretAllowance token) past their text, so the caret shows at the end of a slot and in the empty add slot. Also adds exemplar-map rows for FwReversalEntriesField and the two plugin patterns it introduced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tab and Shift+Tab now visit each slot in reading order, including add slots opened while typing, and leave the field only from its last or first slot, where the detail view's tab order moves on to the next or previous slice. A slot opened after the view built the row takes the row's tab index from the slot before it, so Tab can leave from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NUnit Tests 1 files ± 0 1 suites ±0 13m 25s ⏱️ +25s Results for commit a09aae9. ± Comparison against base commit 30564cc. This pull request removes 2 and adds 132 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1163 +/- ##
==========================================
+ Coverage 39.01% 39.15% +0.14%
==========================================
Files 1520 1524 +4
Lines 352805 353834 +1029
Branches 40692 40911 +219
==========================================
+ Hits 137632 138550 +918
- Misses 185879 185921 +42
- Partials 29294 29363 +69
🚀 New features to boost your workflow:
|
A batch that throws now closes the host's edit session, so whatever it wrote before the failure cannot reach a later save. The row scan moved inside the same guard: it reads a reversal index that may be deleted, and that throw used to escape to the caller. Arrow keys no longer navigate from a stale position. A modified arrow ends a run of Up and Down, and an arrow pressed with a selection collapses it, at the end the arrow points at, and goes no further. The next press moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fill out the placeholder
ReversalIndexEntryPluginto work similarly to the WinForms slice. Jira: LT-22673.Notes:
Future work:
AI Summary
What changed
FwReversalEntriesFieldcontrol (FwAvalonia, LCModel-free). It shows one group per visible analysis-writing-system reversal index, labelled with the writing system's abbreviation.ReversalDetailEditContext, plugin side)body: arm: hand, LT-4665) and links the deepest entry; an existing entry is never renamed.DetailField.ControlFactorynow receives the render-timeSliceFactoryContext; plugins reach it asSlicePluginBuildContext.Render(jump callback, abbreviation-column width).visibility="ifdata"; the row is hidden when the sense has no entries.DetailEditContextBase.AddPendingEditFlush, withDetailEditContextHolder.Settle()flushing first. An editor that saves only on focus loss still gets its edits in when the host saves with focus inside it (navigation, refresh, tool switch).DataTree.CaretAllowance.Reviewer notes
ReversalDetailEditContext.TryCommitRows, row-key rebinding after a commit, and add-slot growth and removal inFwReversalEntriesField.Validation
build.ps1 -CommentHygiene -TokenHygiene: clean.DataTree), selection handling, a failed batch closing the session, and the host settle flush.Preflight review details
Code Review Summary
Branch:
feature/LT-22673-Avalonia-convert-reversal-sliceBase:
origin/main(b6cbf8e, after the in-review rebase)Date: 2026-09-25
Review model: Claude Opus 5.5
Files changed: 22 (5 commits; the Tab change is b2d6b92)
Overview
The author's purpose: port the sense-side Reversal Entries slice
(
ReversalIndexEntrySlice, fieldReferringReversalIndexEntries) to theAvalonia detail view with WinForms parity, replacing the reduced-scope
ReversalIndexEntryPlugin(Jira LT-22673). The new LCModel-freeFwReversalEntriesFieldcontrol shows one group per visible analysisreversal index, with separator-barred slots on one wrapping line, an add slot
that grows as you type, arrow/Home/End navigation across slots, and a
Ctrl+click / context-menu jump to the Reversal Index tool. The plugin's
ReversalDetailEditContextresolves colon chains find-or-create (LT-4665),never renames an existing entry, unlinks emptied rows, and cascades deletion
of emptied ancestors. The row hides when the sense has no entries (ifdata).
The analysis found that the first design committed row by row and on the
field's own focus loss. That lost data when a user swapped or shifted text
between slots, and when the host saved or re-showed while focus was still in
the field. It also missed Unicode normalization and a deleted-sense guard.
All of these were fixed during the review.
Contract/API Changes
IReversalEntryEditing(FwAvalonia.Detail):TryCommitRows(batch),TryCommitRow,IssueAddRowKey,TryResolveMainEntryGuid. It isobtained with
ctx as IReversalEntryEditing; the coreIDetailEditContextis unchanged.
FwReversalEntriesField,DetailReversalGroup,DetailReversalRow,DetailReversalAlternative.DetailField.ControlFactoryis nowFunc<SliceFactoryContext, Control>(was a parameterless factory);
SliceFactory.CreateCustompasses therender context through.
SlicePluginBuildContextgainsRender(theSliceFactoryContext) andVisibleWritingSystems.DetailEditContextBasegainsAddPendingEditFlush/FlushPendingEdits;DetailEditContextHolder.Settle()now flushes before checkingIsOpen.DataTree.CaretAllowance, exposed asFwAvaloniaDensity.CaretAllowance.ReversalShowInReversalIndex,ReversalAddEntryName.Findings
Critical - Must address before merge
None.
Important - Should address before merge
(
ReversalIndexEntryPlugin.cs,FwReversalEntriesField.cs). Swapping twoslots' text, or shifting text up a slot, deleted an entry that another row
was about to take over, because each row released its old entry before the
next row linked. (fixed during review: the control sends every changed
slot as one
TryCommitRowsbatch; the plugin links every target first,then unlinks only released entries no row still shows. Tests:
SwappingTwoRows_KeepsBothEntriesLinked,ShiftingTextUpARow_DeletesOnlyTheEntryNoRowKeeps.)commit (
FwReversalEntriesField.cs,DetailEditContextHolder.cs).Escape (the view's cancel) still saved the typed text on the focus loss that
followed the re-show, and a navigation, refresh or tool switch settled the
session before the field had staged its edits, so they were lost or landed
after the recompose. (fixed during review: Escape restores every slot to
its saved text; the plugin registers
CommitPendingEditswith the hostcontext, and
Settle()flushes it before deciding whether a session isopen. Tests:
Escape_RestoresEverySlotsSavedText_AndSavesNothing,CommitPendingEdits_SavesWhileFocusIsStillInside,Settling_SavesWhatTheFieldHolds_WhileFocusIsStillInIt.)(
ReversalIndexEntryPlugin.cs). Precomposed typed text never matcheddecomposed stored forms, so an unchanged row looked changed and a
find-or-create made a duplicate entry. (fixed during review:
SplitForms,ChainMatchesandFindDeepestcompare NFD forms. Test:PrecomposedTyping_MatchesADecomposedStoredForm.)(
ReversalIndexEntryPlugin.cs). (fixed during review:TryCommitRowsandTryResolveMainEntryGuidreturn false/null for an invalid sense; a failedwrite logs, restores the row bindings and returns false. Test:
ACommitForADeletedSense_ChangesNothing.)IDetailEditContext.TryResetReferenceOrderand conflicts inReversalIndexEntryPlugin.cs(e278320). (fixed during review:rebased onto origin/main, resolved the conflict, and added
TryResetReferenceOrder => falsetoReversalDetailEditContextand thetest fake. The branch was already on origin at 87d1b66, so it needs a
force-push with lease.)
Minor - Consider
SlicePluginBuildContextwas growing one constructor parameter perrender-time service (
SlicePlugins.cs). (fixed during review: it takesthe
SliceFactoryContextasRenderinstead of separate link-callback andcolumn-width parameters.)
writes in that session.
Stagerolls back only a session it openeditself. The row bindings are restored, but the next settle still validates
and commits whatever the failed batch wrote. This needs an unexpected LCModel
exception mid-batch to happen.
the row registers another
CommitPendingEditson the same context. Adisposed or unchanged control's flush does nothing, and the list lives only
as long as one compose, so this is bounded but not tidy.
Devin review (2026-09-30, head db4e96f)
Five bugs and one flag, all verified against the code before acting.
undo guard checked for an open session, which this field opens only on
focus loss. (fixed in the working tree, then stashed at the author's
request:
LT-22673-undo-guard-flush-20260930,59870327c59915d3668bee6cf06998f108bd4681. The branch is again without this
fix.)
Devin's one-line fix: any arrow with a modifier ends the run. Test:
AModifiedArrow_RestartsThePositionUpAndDownNavigateBy.now closes the host session outright, discarding whatever else it held --
the policy the holder already applies to an edit that fails validation. The
row scan moved inside the same guard, since it reads an index that may be
deleted. Test:
AFailedBatch_ClosesTheSession_LeavingNothingToSave.Escape leaves two empty add slots until the re-show(authorconfirmed this is not a problem)
discarded control. Traced: a discarded control's text already matches
what was saved, so nothing is written; it is a leak with a latent hazard.
The full fix also wants the detail view to dispose replaced editors, which
is beyond this branch.
The key-map comment exceeds the 200-character budget(the repo'sown checker raises the budget to 600 in dense branching code and passes it
clean)
Author's own change in the same round: an arrow pressed with a selection now
collapses it and goes no further, at the end the arrow points at, mirrored in
a right-to-left group; the next press moves. Shift+arrow still extends.
Required Validation / Evidence
Run on the rebased branch, with the in-review fixes staged:
.\build.ps1 -CommentHygiene -TokenHygiene: succeeded, 0 warnings,0 errors; comment hygiene and token hygiene clean. (Two auto-rewrapped
comment fragments from the first build were reflowed by hand.)
.\test.ps1 -CommentHygiene -TokenHygiene -SkipNative -TestProject Src/Common/FwAvalonia/FwAvaloniaTests: 823 total, 822 passed, 0 failed,1 skipped.
.\test.ps1 -CommentHygiene -TokenHygiene -SkipNative -TestProject Src/xWorks/xWorksTests: 1725 total, 1717 passed, 0 failed (the rest areexplicit or ignored tests that don't run by default).
git diff --cached --check: clean.0 failed, 1 skipped; xWorksTests 1726 total, 1718 passed, 0 failed; both
hygiene checks clean; gitlint clean.
manual pass, but it came before the in-review fixes and the rebase (which
brought in LT-22688 tab navigation). Worth re-checking: swapping two slots,
Escape after typing, switching entries with focus in a slot, and an
accented form typed into an add slot where the entry already exists.
origin/main..HEAD(the 3 rebased commits): clean. fb15242and the commit for the staged Tab change still need the same check;
"fix misstaging" and the "in progress" title may be worth rewording or
squashing before the PR.
Ticket: LT-22673 exists and covers the user-visible change.
Positive Observations
fake; the LCModel side has its own fixture against real data, including
undo/redo, cascade deletion, homographs, RTL and bidi forms.
step labelled with the field ("Undo change to Reversal Entries").
in hidden writing systems are left alone.
mechanisms, recorded in the control exemplar map for later slices.
Interview Notes
SlicePluginBuildContext): the author asked for the change now.in-review fixes; see Required Validation.
Author does not understand: the most complex parts of the change
(field-level save on focus loss, row-key rebinding after commit, add-slot
growth).
line. If one entry is wider than the line (unlikely), the user can scroll
through it with the arrow keys or by click-and-drag selecting.
After the rebase brought in LT-22688, the author asked for Tab/Shift+Tab
to visit every slot (add slots opened while typing included) and to
leave the field only from its last or first slot. Implemented and
tested; see In-Review Quality Check.
In-Review Quality Check
INTERVIEW_CHANGES (the author committed these as fb15242, "Claude cleaning
up & fixing tests - in progress"; the Tab change at the end is b2d6b92):
IReversalEntryEditing.cs:TryCommitRowsbatch contract.ReversalIndexEntryPlugin.cs: two-phase batch commit, NFD matching,sense guard, failure logging with binding restore, flush registration,
TryResetReferenceOrder, render context throughRender.FwReversalEntriesField.cs: per-slot saved-text state, one batch perfield visit, public
CommitPendingEdits, Escape revert.DetailEditContextBase.cs,DetailEditContextHolder.cs: pending-editflush hook and the flush in
Settle().SlicePlugins.cs,DetailComposer.cs:SlicePluginBuildContext.Render.Tests:
FwReversalEntriesFieldTests.cs(batch count, Escape, explicitflush, fake updates),
ReversalEntriesComposeTests.cs(swap, shift,other-writing-system forms, NFD, deleted sense, settle flush; idempotent
AddAnalysisWs),LexemeEditorInventoryTests.cs(render context passedthrough).
FwReversalEntriesField.cs(requested after the rebase): Tab andShift+Tab step slot by slot in reading order and fall through to the
view's tab walk only at the field's ends; a slot opened while typing takes
its row's tab index so the walk can leave from it. Six new tests, one of
them inside a real
DataTree. FwAvaloniaTests afterwards: 829 total,828 passed, 0 failed, 1 skipped; hygiene clean. xWorksTests not re-run
(no xWorks change).
The build, both hygiene checks, and both test projects pass on the rebased
branch (see above).
Suggested Review Focus
ReversalDetailEditContext.TryCommitRows: two-phase link/unlink andthe
stillWantedset, especially with cascade deletion.DetailEditContextHolder.Settle()flushing before theIsOpencheck:every host save path now stages the field's held edits first.
FwReversalEntriesFieldfocus-loss logic (FocusIsInside, add-slotremoval) and the Escape revert that leaves the key unhandled for the view.
WireSlotNavigationalongside LT-22688's row tabindexes, and the tab index copied onto slots opened while typing.
🤖 Generated with Claude Code
This change is
Devin