Switching to a document tab is supposed to pull the tree selection over to
what that tab shows, but it didn't reliably -- the gap was masked until the
right-click change stopped moving the selection on its own. Two problems:
The SelectedItem setter replaced the collection with Clear()+Add(), and the
transient empty step made the grid sync defer its completion flag, which then
suppressed the sync for the real new value -- so the tree visual stopped
following tab activation. Route every selection replacement through a single
batched SelectNodes() that fires the selection-changed fan-out once, with the
final set, so consumers never observe the transient empty (or a transient
multi, which would break metadata-tab reuse).
And a tab decompiled from several nodes carries no single SourceNode, so
activating it restored nothing. Restore the tab's full node set from
CurrentNodes (one or many), and mirror a multi-selection into the grid so every
restored node is highlighted, not just the primary.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Right-clicking a tree row to reach its context menu used to move the real
selection there first, because ProDataGrid selects the row on press. With the
preview document bound to the selection, that meant 'Decompile to new tab' on
node B (while viewing A) jumped the preview to B before the command ran, so you
ended up with B twice instead of the intended A + B. Middle-click avoided it,
but not every mouse has a usable one.
Capture the right-clicked row in ContextRequested (which fires even when a
previous menu's light-dismiss popup swallows the press) and swallow the
right-press so the grid never reselects: the menu now acts on the clicked row
as a Thunderbird-style context target while the selection -- and the document
-- stay put. The targeted row gets a faint focus-box highlight, cleared when
the menu closes (guarded so a stale menu's close can't wipe a newer target).
Also adds TestHarness.ClickItem to collapse the repeated
Items.OfType<MenuItem>().Single(...).RaiseEvent(...) menu-click dance.
Assisted-by: Claude:claude-opus-4-8:Claude Code
To audit what each UI test actually exercises, every step now snapshots the
live window to <TestFixtureName>/<TestName>_<NN>_<ShortDescription>.png: a
booted frame (emitted automatically by TestHarness.BootAsync), one after each
state-changing action, and one before each assertion. Flip ILSPY_TESTS_VISIBLE=1
to render the filmstrip; it lands under %TEMP%/ilspy-test-captures (overridable
via ILSPY_TEST_CAPTURES).
The step number and fixture/test name are derived automatically so inserting a
breakpoint never renumbers the rest. The identity is recorded up front from the
real ITest in an ITestAction hook rather than read live: NUnit's
TestContext.CurrentContext does not flow onto async continuations, so a capture
after an await would otherwise collide under the ad-hoc context. And when
rendering is off the whole call is a true no-op -- not even a dispatcher pump --
so instrumenting a test can never perturb the navigation/tab timing it asserts
on. Full headless suite stays green.
The Reload/Remove/SearchMSDN/OpenContainingFolder/DecompileInNewView
tests used to call `entry.Execute(synthetic TextViewContext)` against
a hand-built context. They now select via the assembly-tree model,
build the context menu through `BuildContextMenuForCurrentState`
(the same call the live `Opening` event makes), and fire the menu
item's `Click` routed event — the same handler the user's click
invokes. This widens the coverage to the menu-build → click-handler
attachment pipeline.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Extends the double-click-to-decompile path with a new-tab variant. RaiseRowActivated
now carries an OpenInNewTab flag; DockWorkspace branches on it to either reuse the
active tab (existing tree-selection path) or spawn a fresh DecompilerTabPageModel.
The view's OnGridDoubleTapped reads KeyModifiers.Shift; a new
DecompileMetadataRowInNewTabCommand exports the same gesture as a context-menu entry.
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
[ExportContextMenuEntry(Header = "DecompileToNewPanel")] sitting in the
Analyze category — opens a fresh DecompilerTabPageModel, asks the dock
workspace to put it next to the existing tab, and assigns the selected
tree-node(s) as its CurrentNodes (which fires the decompile pipeline the
same way a regular selection change does).
Assisted-by: Claude:claude-opus-4-7:Claude Code