Nearly every headless UI test opened with the same four-line prologue
(resolve the shared MainWindow, show it, cast its DataContext, wait for the
assembly list), then repeated the corelib lookup, the EnsureLazyChildren +
Children.OfType<T>().Single() drill, the registry single-by-header lookups,
and the open-an-assembly-and-wait dance. The duplication made the intent of
each test hard to see and every signature tweak a suite-wide edit.
Collapse those into TestHarness (BootAsync, OpenAssemblyAsync, GetCommand,
GetEntry) and TreeNavigation extensions (FindCoreLib, GetChild<T>, Expand<T>),
then apply them across the suite. Net ~865 lines of boilerplate removed with
no change in behaviour; the full headless suite stays green.
The trailing HeaderedContentControl in the Display panel (Other -> Sort
results by fitness) sat 15 px below the ScrollViewer.Extent's reported
height: the outer StackPanel's measure under-counted that last child's
inner content, so MaxYOffset never grew enough to scroll the checkbox
into view. The Reset-to-defaults border below the ScrollViewer covered
the gap visually, making the checkbox look obscured.
ScrollViewer.Padding alone can't compensate because Avalonia 12's
Simple-theme ScrollContentPresenter collapses Padding on the axis the
scrollbar lives on -- the horizontal 6 px stuck, the vertical 6 px was
dropped. Wrapping the content in a Border with explicit Padding makes
the padding part of the StackPanel's measured extent, and the last
child becomes reachable. Applied to both panels that use a top-level
StackPanel under a ScrollViewer; Misc is unaffected (no ScrollViewer,
two checkboxes never overflow).
The new OptionsPageScrollReachTests.Last_Item_In_Display_Panel_Is_-
Reachable_At_Max_Scroll opens the Options tab, scrolls the Display
panel to ScrollViewer.Extent.Height, and asserts the "Sort results by
fitness" CheckBox's rendered bottom sits at or above the Reset border's
top in window-coordinate space.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Preview tab semantics. ContentTabPage gains IsPreview; the persistent MainTab
starts preview (tree-node clicks replace its Content in place). The user pins
via Window menu, right-click context menu, or inline pin icon — the
just-pinned tab keeps its content/identity and a fresh preview MainTab spawns.
Carve-out tabs (Open in new tab, Options) are born pinned and survive tree
selections.
Assisted-by: Claude:claude-opus-4-7:Claude Code
DecompilerTextView subscribes to SettingsService.DisplaySettings.PropertyChanged
and mirrors the new value into the AvaloniaEdit TextEditor immediately — no
re-decompile, no Apply. Properties wired:
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the WPF modal Options dialog with a Dock document tab — opens via
View → Options at the same MenuOrder=999 mount point, hosts the same three
panels (Decompiler / Display / Misc), and uses the same SettingsSnapshot
commit pattern so closing the tab without Apply discards in-flight edits.
Re-invoking View → Options while one's open just focuses the existing tab.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Mirrors the WPF AssemblyTreeNode.SetPublicAPI recursive walk: a namespace
counts as public-API iff at least one type it contains is public-API. Inline
on NamespaceTreeNode itself as a lazy-evaluated cached aggregate (types don't
change accessibility at runtime), so the cell template's gray-foreground
binding picks up the right value on first render.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the brittle root.GetVisualDescendants().OfType<T>().Single() / .First()
pattern with a new WaitForComponent<T>() extension that polls until the requested
control is in the visual tree, then returns it. Avalonia.Headless tests routinely
queried the visual tree before lazily-templated panes (DataGrid, dock content)
had materialised, surfacing as intermittent 'Sequence contains no elements'
failures across the suite.
Assisted-by: Claude:claude-opus-4-7:Claude Code