Skip to content

LT-22691: Apply a layout's part overrides through a conditional - #1246

Merged
mark-sil merged 3 commits into
mainfrom
claude/lt-22691-template-name-row-7398db
Nov 13, 2026
Merged

mark-sil merged 3 commits into
mainfrom
claude/lt-22691-template-name-row-7398db

Conversation

@mark-sil

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

Copy link
Copy Markdown
Contributor

A row whose part wraps its slice in a conditional now shows the label
and binds the menu its layout asks for. Until now the Avalonia detail
view dropped a part ref's overrides when the part's content sat inside
an <if>, <ifnot> or <choice>, so the row kept the part's own values.

In Grammar > Category Edit, an affix template's name row now reads
"Template Name" and offers the template's menu. It read "Name" and
offered only Help.

The part ref's abbreviation, hotlinks, visibility, expansion, target
layout, shown writing systems and child rows reach such a row the same
way.


Reviewer notes

Start here: Src/Common/FwAvalonia/ViewDefinition/XmlLayoutImporter.cs, CreateNode and AddConditionalChildren. Content inside a part's conditional now takes the part ref as its caller, as content written directly in the part already did. Then ViewDefinitionOverrideEditor.LocateTarget: a Field Visibility change now writes every state's row that came from the same part ref.

The question to bring: which rows change? Every row whose part wraps its content in a conditional and whose layout overrides something on the part ref: 44 shipped parts have that shape. Nothing changes in the WinForms detail view. Node StableIds are unchanged, so saved view overrides still match.

Where to look

  • The affix template's name row, from the shipped layout: ShippedMoInflAffixTemplateDefault_NameRow_TakesTheLayoutLabelAndMenu, and composed for both states of Active in DetailConditionalPartRefTests.
  • Each conditional form (<if>, <ifnot>, <where>, <otherwise>, nested): the PartRefOverrides_Reach... tests in ConditionalImportTests.
  • Field Visibility on such a row now reads and sets the row's own value: an entry's "Variant of" row shows "Normally hidden", and "Always visible" sticks. Pinned by PartRefVisibility_IsHeldByTheContent_NotTheConditional.
  • The change reaches every state's row: an affix template's Description stays hidden with Active unticked (FieldVisibility_OnATemplatesDescription_HoldsWhenActiveIsUnticked, PartRefBranches_ShareOneVisibilityTarget). Hiding "Variant of" also hides "Components", as in the WinForms detail view.
  • The Subcategories section keeps one heading: Compose_Subcategories_KeepOneHeading_AndOneNameRowPerSubcategory.
  • The template menu is now on two rows of a template, the heading and the name row; both act on the same template (CategoryEditMenuTests).

Deliberately not here

  • A part-level conditional that holds only <part> or <indent> children does not pass the part ref on. No shipped part has that shape, and the WinForms detail view draws nothing for it.
  • A nested ad hoc group's rows are covered by a composer test only: Ad hoc Rules is not shown in the Avalonia detail view.
  • A part ref nested inside another part's conditional (Ad hoc Rules' nested group name) still sets only one state's row with Field Visibility, for the same reason.

Also here: a one-line teardown fix in LexiconFirstSliceEditContextEdgeCaseTests. One test left an edit session open, so the fixture's teardown threw.

Verification: build, comment and token hygiene clean. Targeted tests passed: 272 in xWorksTests over the Composer and Category Edit menu fixtures, 194 in FwAvaloniaTests over the import and view-definition fixtures; after the last commit, FwAvaloniaTests 149/149 and xWorksTests 285/285 and 89/89. Manual steps on Sena 3 in the New UI passed: the template rows and their menu, Subcategories, Field Visibility on "Variant of", the template's Description staying hidden with Active unticked, and "Components" following "Variant of". Full suite not run.

Next: approve, or name a row that now shows the wrong label or menu.

Decisions, and why
  • A <seq> or <obj> inside a conditional keeps its own label. The part ref's label already names the section heading above it; passed down, it would show as a second heading (Subcategories in Category Edit). The sequence still takes the part ref's target layout and child rows.
  • The part ref's visibility sits on the wrapped rows; the conditional is always visible. Field Visibility reads and sets the row's own node, so that node has to hold the value. With the value on the conditional only, the row reported "Always visible" and a change did not stick.
  • Only a conditional inside a part is always visible. A <choice> written directly in a layout keeps its own visibility.
  • Field Visibility writes one override per state's row. An override saved before this fix may hide only one state's row; choosing any visibility again rewrites every state.
  • The conditional node keeps the part ref's label and menu too. A sequence item's heading takes its menu from the item layout's root nodes, which is how the "Affix Templates" heading gets the template menu.
  • The part ref is reported once. Its unhandled-attribute diagnostics come from the conditional's node, not again from each slice inside it.
Preflight review details

Code Review Summary

Branch: claude/lt-22691-template-name-row-7398db
Base: main
Date: 2026-11-12
Review model: Claude Fable 5.2
Files changed: 12

Overview

In the Avalonia detail view, a layout's overrides on a part ref (label, menu, abbreviation,
hotlinks, visibility, expansion, target layout, shown writing systems, child rows) were lost
when the part wraps its content in an <if>, <ifnot> or <choice>. The importer now hands
the part ref to the content inside the conditional, as it already did for content written
directly in the part. The confirmed case is the affix template's name row in Grammar >
Category Edit, which now reads "Template Name" and binds mnuDataTree-POS-AffixTemplate.

One production file changes (XmlLayoutImporter.cs). The rest is tests, plus a one-line
teardown fix in an existing test fixture.

Two follow-up commits after review: a <choice> written in a layout keeps its own
visibility, and Field Visibility writes every state's row of a part (production changes in
the view-definition model, importer, override applier, JSON serializer and override editor,
and in RecordEditView and ObjectMenuAuthority).

Contract/API Changes

  • XmlLayoutImporter: content inside a part-level conditional takes the part ref as its
    caller. A Conditional or ChoiceGroup node is always imported with visibility Always; the
    part ref's visibility is on the content nodes.
  • Node StableIds are unchanged, so saved view overrides still match.
  • No public signature change. No strings, configuration, native or installer change.
  • Follow-up: ViewNode.PartRefId (also in the canonical JSON as partRef) and
    ViewNodeLocation.VisibilityTargets are new.

Findings

Critical - Must address before merge

None.

Important - Should address before merge

None.

Minor - Consider

  • A new comment named a consumer feature as its reason (fixed: reworded to state
    what the node holds)
  • The slice case spelled out the part-ref test that hasPartRef already names
    (fixed: uses hasPartRef)
  • No test for an <obj> inside a conditional, for the ref's <part> children under a
    wrapped slice, or for the template row with Active unticked
    (fixed: three tests added)
  • A part-level conditional holding only <part> or <indent> children has no holder
    for the part ref's visibility
    (author: leave as is. None of the 44 shipped parts that
    wrap content in a conditional has that shape, and the WinForms detail view draws nothing
    for it)
  • Diagnostics about the part ref's own dropped children repeat once per wrapped content
    node
    (author: leave as is. They feed only the coverage report; of the 156 shipped layout
    refs naming such a part, the only one with children holds an <indent>, which is
    imported)
  • The importer withholds the part ref's label from a <seq> or <obj> inside a
    conditional, which serves a display rule of the composer
    (author: accepted in the design
    discussion; the label already names the section heading and would repeat as a second
    heading)

Required Validation / Evidence

  • .\build.ps1 -CommentHygiene -TokenHygiene: clean.
  • .\test.ps1 -CommentHygiene -TokenHygiene -TestFilter ...: xWorksTests 272/272 over the
    Composer fixtures, CategoryEditMenuTests, LexiconMenuAuthorityTests and
    DataTreeMoveReachabilityTests; FwAvaloniaTests 194/194 over the import, view-definition,
    canonical-JSON, override and projector fixtures; then AdhocCoProhibComposeTests 6/6 and
    LexiconFirstSliceEditContextEdgeCaseTests 6/6 after the last two test edits, and 43/43 over
    the three fixtures in LayoutImportCoverageTests.cs after a whitespace-only re-indent of
    the test XML.
  • Manual: the author ran the steps on Sena 3 in the New UI (the affix template rows and
    their menu in Category Edit, the Subcategories section, and Field Visibility on an
    entry's "Variant of" row); all passed.
  • Tests only: a nested ad hoc group's rows. Ad hoc Rules is not shown in the Avalonia
    detail view.
  • Full suite not run (targeted runs only).

Positive Observations

  • One mechanism change in the importer; no per-row or per-layout special case.
  • StableIds stay as they were.
  • The shipped layout is pinned as well as the synthetic fixture:
    ShippedMoInflAffixTemplateDefault_NameRow_TakesTheLayoutLabelAndMenu.

Interview Notes

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

  • A <seq> or <obj> inside a conditional keeps its own label; it takes the part ref's
    target layout and child rows.
  • The part ref's visibility sits on the wrapped rows and the conditional is always visible.
  • The three unfixed findings above stay as they are.
  • The teardown fix in LexiconFirstSliceEditContextEdgeCaseTests (a test left an edit
    session open, so the fixture's teardown threw) is folded into this PR.
  • Jira: the work belongs to LT-22691; no separate ticket.

Suggested Review Focus

  • XmlLayoutImporter.CreateNode: which of the part ref's values reach content inside a
    conditional, and that the part ref is reported once.
  • The seq/obj label rule against the Subcategories section of Category Edit.

🤖 Generated with Claude Code


This change is Reviewable

A row whose part wraps its slice in a conditional now shows the label
and binds the menu its layout asks for. Until now the Avalonia detail
view dropped a part ref's overrides when the part's content sat inside
an <if>, <ifnot> or <choice>, so the row kept the part's own values.

In Grammar > Category Edit, an affix template's name row now reads
"Template Name" and offers the template's menu. It read "Name" and
offered only Help.

The part ref's abbreviation, hotlinks, visibility, expansion, target
layout, shown writing systems and child rows reach such a row the same
way.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Nov 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.61905% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.85%. Comparing base (ef1a81c) to head (6ff99ad).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...nia/ViewDefinition/ViewDefinitionOverrideEditor.cs 96.66% 0 Missing and 1 partial ⚠️
...mon/FwAvalonia/ViewDefinition/XmlLayoutImporter.cs 96.77% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1246      +/-   ##
==========================================
+ Coverage   40.83%   40.85%   +0.02%     
==========================================
  Files        1573     1573              
  Lines      357159   357289     +130     
  Branches    41812    41840      +28     
==========================================
+ Hits       145831   145973     +142     
+ Misses     182022   182011      -11     
+ Partials    29306    29305       -1     
Files with missing lines Coverage Δ
...nia/ViewDefinition/ViewDefinitionJsonSerializer.cs 93.71% <100.00%> (+0.06%) ⬆️
...n/FwAvalonia/ViewDefinition/ViewDefinitionModel.cs 89.77% <100.00%> (+0.09%) ⬆️
...ia/ViewDefinition/ViewDefinitionOverrideApplier.cs 93.85% <100.00%> (+0.02%) ⬆️
Src/xWorks/Avalonia/Hosting/ObjectMenuAuthority.cs 68.62% <100.00%> (+1.31%) ⬆️
...xWorks/Avalonia/Hosting/RecordEditView.Avalonia.cs 64.33% <100.00%> (+0.61%) ⬆️
...nia/ViewDefinition/ViewDefinitionOverrideEditor.cs 91.45% <96.66%> (+0.82%) ⬆️
...mon/FwAvalonia/ViewDefinition/XmlLayoutImporter.cs 89.48% <96.77%> (+2.82%) ⬆️

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

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

NUnit Tests

    1 files  ± 0      1 suites  ±0   8m 16s ⏱️ - 6m 42s
7 273 tests +33  7 188 ✅ +33  85 💤 ±0  0 ❌ ±0 
7 282 runs  +33  7 197 ✅ +33  85 💤 ±0  0 ❌ ±0 

Results for commit 6ff99ad. ± Comparison against base commit ef1a81c.

♻️ This comment has been updated with latest results.

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

Verdict: approve with nits. One should-fix (fine as a follow-up ticket instead) and one nit, both inline.

The goal is right: DataTree.ProcessSubpartNode passes the part ref through to the content of if/ifnot/where/otherwise (DataTree.cs:2628), and the importer now does the same. One mechanism change, no per-row special cases, StableIds unchanged, the shipped layout pinned by a test, and the part ref's diagnostics reported once. The teardown fix is correct.

Verified

  • The composer applies Never to every node kind at Walk (DetailComposer.cs:657), and a Conditional draws no heading of its own (WalkConditional), so moving visibility onto the wrapped rows loses neither hiding nor an ifdata section's header collapse.
  • The 44-part count holds, and NestedGroupName is the only layout ref with child elements.
  • No shipped <if>/<ifnot>/<choice> carries its own visibility.
  • Notebook's Subrecords section keeps no label on its inner <obj>, as before.

Not verified: the manual pass on Sena 3 and the targeted test runs; I did not build or run tests locally.

children, diagnostics);
AddConditionalChildren(contentEl, hasPartRef ? callerEl : null, parts, className,
layoutType, stableId, indented, children, diagnostics);
// The part ref's visibility is held by the content nodes, one per row.

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.

Should fix (or follow-up): Field Visibility only reaches one branch of a two-branch part.

The Avalonia override is stored against the row's own node (ApplyFieldVisibility in RecordEditView.Avalonia.cs writes SetVisibility for the row's stable id). About 27 of the 44 affected parts are <if Disabled=true> / <if Disabled=false> pairs, and each branch's slice is a separate node.

Scenario: in Category Edit, with the template active, right-click Template Name > Field Visibility > Normally hidden. The row hides. Untick Active: the row comes back, because the other branch's slice is still Always. WinForms keeps visibility on the part ref (DataTree.cs:2424), so there it holds in both states. The nested ad hoc group's child rows are imported once per branch and share the same gap.

Not a regression (before this PR the change didn't stick at all), but the PR body says Field Visibility on these rows now works, and the manual check was on "Variant of", which has one branch. Suggest writing the override for the matching child of each sibling conditional from the same part ref, or filing a follow-up and narrowing the claim in the PR body.

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.

Fixed in this PR, in 6ff99ad. Each node that comes from a part ref inside a conditional now records that part ref (ViewNode.PartRefId). LocateTarget returns every node from the same part ref as VisibilityTargets, and ApplyFieldVisibility writes one SetVisibility for each of them. Hiding the template's Name or Description now holds when Active is unticked. Covered by FieldVisibility_OnATemplatesDescription_HoldsWhenActiveIsUnticked and PartRefBranches_ShareOneVisibilityTarget.

One effect, which matches WinForms: an entry ref's "Components" and "Variant of" rows come from one part ref, so hiding one now hides the other.

One case is still open: a part ref nested inside another part's conditional still sets only one state's row. The only shipped case is the nested ad hoc group, and Ad hoc Rules isn't shown in the Avalonia detail view. The PR description now says so, and its Field Visibility claim is updated.

visibility, expansion, indented, null, children, localizationKey, automationId,
routing, menuId: menuId, contextMenuId: contextMenuId, hotlinksId: hotlinksId,
condition: condition);
ViewVisibility.Always, expansion, indented, null, children, localizationKey,

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.

Nit: this (and the ChoiceGroup below) forces Always even when there is no part ref, e.g. a conditional written directly in a layout or inline in a slice, while the comment above describes only the part-ref case. No shipped conditional has its own visibility, so nothing changes today; hasPartRef ? ViewVisibility.Always : visibility would keep the old behavior for the other cases.

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.

Done in eb24bed: hasPartRef ? ViewVisibility.Always : visibility on both the Conditional and the ChoiceGroup. Pinned by ChoiceWrittenInALayout_KeepsItsOwnVisibility.

@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 5 files and all commit messages.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on mark-sil).

mark-sil and others added 2 commits November 12, 2026 14:18
A <choice> written directly in a layout keeps its own visibility again.
Until now every imported conditional was always visible; that rule is
meant only for a conditional inside a part, whose part ref's visibility
moves to the rows it holds.

No shipped layout has such a choice, so nothing on screen changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Field Visibility on a row whose part holds one conditional per state now
sets the row in every state. Until now it set only the row on screen, so
an affix template's Description hidden while Active was ticked came back
once Active was unticked.

An entry ref's "Components" and "Variant of" rows come from one part
ref, so hiding one now hides the other, as the WinForms detail view
does.

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

@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 9 files and all commit messages, and resolved 1 discussion.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on mark-sil).

@mark-sil
mark-sil merged commit 1d61f8d into main Nov 13, 2026
9 checks passed
@mark-sil
mark-sil deleted the claude/lt-22691-template-name-row-7398db branch November 13, 2026 02:23
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