Errors thrown by an analyser used to collapse to a single-line
ex.Message on the row; that lost the stack trace and made the
InvalidCastException in GetCodeMappingInfo nearly impossible to
diagnose from the UI alone.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Language.GetCodeMappingInfo was unconditionally casting the incoming
EntityHandle to TypeDefinitionHandle. MethodUsesAnalyzer calls it
with the method's own handle and expects the language to walk to
the declaring type — the WPF impl does, the Avalonia port didn't.
Right-clicking a constructor → Analyze → expanding "Uses" therefore
threw at runtime; the error surfaced only as a single-line message
in the analyzer pane because AnalyzerErrorNode dropped the stack.
Assisted-by: Claude:claude-opus-4-7:Claude Code
AnalyzeContextMenuEntry surfaces "Analyze" in the assembly-tree
right-click menu when every selected node implements IMemberTreeNode
(types, methods, fields, properties, events). Execute hands each
member to AnalyzerTreeViewModel.Analyze, which dedupes by metadata
token + parent module so re-running on the same entity refocuses
instead of duplicating. Const fields are excluded — they're literals
at every use-site, nothing to scan for.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Lay the foundation for the analyzer pane. None of these classes are
wired into the live view yet — that lands when the pane view-model and
the ProDataGrid view are filled in two commits down. Background-fetch
behaviour on AnalyzerSearchTreeNode is also deferred.
Assisted-by: Claude:claude-opus-4-7:Claude Code
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
Bring the per-step screenshot helper (TestCaptureExtensions.CaptureForReview)
into tree without yet adding the call sites — the existing instrumented
test files stay clean for now and can be re-instrumented selectively
when needed.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Browsing portable PDB metadata required drilling three levels deep:
Assembly → Metadata → Debug Directory → Debug Metadata (Embedded).
The PDB's tables are now exposed as a top-level MetadataTreeNode
alongside the host module's Metadata folder, so PDB browsing is one
click away. The PdbProvider already cached for decompilation is
reused — no second parse.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Mirrors WPF's TextView/EditorCommands.cs: CopyContextMenuEntry and
SelectAllContextMenuEntry are [ExportContextMenuEntry] MEF exports
gated by TextViewContext.TextView, with Category=Editor and low Order
so they appear at the top of the editor right-click menu. The
hand-rolled MenuItem fallback in DecompilerTextView.OnContextMenuOpening
is removed — the MEF entries replace it.
Assisted-by: Claude:claude-opus-4-7:Claude Code
HandleFileDrop now resolves each opened LoadedAssembly to its
AssemblyTreeNode (via AssemblyListTreeNode.FindAssemblyNode) after
any Move and writes the set into model.SelectedItems. Lookup happens
post-Move because Move re-creates the node wrappers — a pre-Move
reference would dangle. Selecting drives the decompiler view to
render the new assembly, which is what makes the LoadAsync task
observable to the user.
Assisted-by: Claude:claude-opus-4-7:Claude Code
ProDataGrid's row-drag pipeline only sees in-grid drags, so external
file drops bypass it entirely. Wires Avalonia's standard DragDrop
pipeline alongside it: DragDrop.AllowDrop on the tree DataGrid plus
DragOver / Drop handlers that read DataFormat.File from the
IDataTransfer, resolve each IStorageItem to a local path, and route
through AssemblyListPane.HandleFileDrop.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Wires ProDataGrid's native row-drag API on the assembly tree:
CanUserReorderRows=True, RowDragHandle=Row, RowDragStarting cancels
drags whose source isn't a top-level AssemblyTreeNode (or is a
package-nested entry). AssemblyRowDropHandler validates that the
target is a sibling (not Inside, not deeper than top-level) and
routes the reorder through AssemblyList.Move so the same persistence
path the rest of the app uses (Unload / OpenAssembly) captures the
new ordering.
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
Audit had ExitCommand listed as Missing because it grepped for a separate
ExitCommand.cs file (the WPF layout). Avalonia bundles File-menu commands
into FileCommands.cs so the audit missed it. Command was already implemented
correctly — uses IClassicDesktopStyleApplicationLifetime.Shutdown() rather
than mainWindow.Close() (cleaner since Avalonia apps can have multiple
windows).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Builds on the LanguageVersions virtual + HasLanguageVersions helper that
landed in the prior tree-completeness commit:
Assisted-by: Claude:claude-opus-4-7:Claude Code
CaretHighlightAdorner is an IBackgroundRenderer on KnownLayer.Caret with a
hand-rolled animation curve (Stopwatch + DispatcherTimer at ~60fps invalidating
the layer) instead of WPF's RectAnimation/BeginAnimation, since AvaloniaEdit
has no XAML-style animation primitive for ad-hoc visuals. Same timing as WPF
(300ms grow, 300ms shrink, opacity fade 450-650ms, 1s lifetime).
Assisted-by: Claude:claude-opus-4-7:Claude Code
GrayscaleAwareImage : Image bakes a luma-transformed Bitmap on the disabled
edge (BT.601 weights) and swaps Source. Replaces the blanket Opacity=0.35
setter that was the previous cue. Applied to the static toolbar buttons in
MainToolBar.axaml plus the MEF-built buttons constructed in BuildButton.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replace LoadedAssembly's eager Task.Run(LoadAsync) with a Lazy<Task<LoadResult>> so
construction is free and the load only starts on the first GetLoadResultAsync /
await / .Result access. Active assembly's load fires from the saved-path restore
and runs without competition; everything else stays cold until either the user
expands a tree node OR the cooldown sweep fires.
Assisted-by: Claude:claude-opus-4-7:Claude Code
FindMemberNode's IMethod branch only checked direct MethodTreeNode children of
the type. Accessor methods (get_/set_/add_/remove_/invoke_) live as children of
their owning PropertyTreeNode / EventTreeNode, so the lookup always returned
null — and MMB on a metadata-grid accessor row silently no-opped because
OpenNodeInNewTab couldn't resolve the IMethod to a tree node.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Mirrors WPF's mechanism: ILSpyTreeNode.OnChildrenChanged calls ApplyFilterToChild
on every newly-added child while the parent IsVisible, writing the Filter result
into SharpTreeNode.IsHidden. Because SharpTreeNode.IsVisible defaults to true,
the cascade fires while a node's children are added in its constructor — so a
PropertyTreeNode's get_/set_ accessors are stamped IsHidden=true under the
default ShowApiLevel before the property even attaches to its type. The base
SharpTreeNode.ShowExpander then naturally returns false ("Children.Any(!isHidden)"
sees no visible children), no per-node override needed.
Assisted-by: Claude:claude-opus-4-7:Claude Code
PropertyTreeNode now eagerly adds Getter / Setter as MethodTreeNode children
(when present); EventTreeNode does the same for Add / Remove / Invoke
accessors. The ShowExpander => false overrides drop so the base default
(Children.Any) lights up the chevron when accessors exist.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Label and input now share the same Panel slot inside a horizontal DockPanel —
the funnel icon docks right and exactly one of {label, input} is visible at a
time. Hovering the header (or setting a filter, or opening the popup) swaps
the column name out for the input; otherwise the label stays. Two-row stack
is gone, so the data-rows no longer shift down when an input gets revealed.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Each metadata column header now shows a small funnel icon to the right of the
column name. The TextBox / dropdown trigger collapses by default and only
becomes visible when the pointer is over the header, when a filter is set, or
while the [Flags] popup is open. Active filters tint the funnel SteelBlue so
the user can see at a glance which columns are constraining the view.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Each ContentTabPage now carries the SharpTreeNode it represents (SourceNode),
stamped at construction time so the activation event fires with it already
populated. DockWorkspace subscribes to factory.ActiveDockableChanged: when a
new tab becomes active — whether by MMB carve-out, the "Decompile to new tab"
context-menu entry, or the user clicking a different tab in the strip — the
assembly-tree selection is pulled across to that tab's SourceNode. A guard
prevents the change from cascading back into ShowSelectedNode and overwriting
MainTab's content. Existing single-tab reuse path stamps MainTab.SourceNode
on every selection so flipping back to it stays a no-op.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The new-tab path always built a DecompilerTabPageModel, so MMB / context-menu
on a metadata-table tree node forced the node into a decompiler tab and a second
metadata view couldn't coexist with the first. Move the carve-out into a single
DockWorkspace.OpenNodeInNewTab that asks node.CreateTab for custom content
(metadata tables, resource viewers, …) — wiring the same NavigateToCellRequested
/ RowActivated subscriptions AttachCustomContent applies on the reuse path —
and only falls back to spawning a fresh decompiler tab when the node has no
custom page-type. The assembly-tree MMB handler, the metadata-grid MMB handler,
and the "Decompile to new tab" context-menu entry now all delegate to this one
method.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Wires MMB on the assembly tree and the metadata grid to the existing "open in
new tab" path, matching the WPF gesture (DecompileInNewViewCommand carries
InputGestureText = "MMB"). Plain double-click reverts to reusing the active tab;
the unreliable Shift+double-click variant is dropped.
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
DataGrid.DoubleTapped raises a RowActivated event on the metadata
page model. The dock workspace subscribes during AttachCustomContent
and resolves the row's metadataFile + Token to an IEntity via the
type system, then selects the matching tree node — which triggers
ShowSelectedNode → CreateTab → decompiler view, the same path a
click in the assembly tree takes. Falls through silently for rows
whose token doesn't resolve to an IEntity (heap rows, AssemblyRef,
constants).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replace the string-token flag predicate with a numeric bitmask on
ColumnFilter (FlagMask, default -1 meaning "all rows"). The dropdown
on a [Flags] column now drives the mask directly:
Assisted-by: Claude:claude-opus-4-7:Claude Code
Right-clicking an entity hyperlink in the decompiler view now offers
"Go to token" — when the click lands on a reference whose payload is
an IEntity backed by a real metadata module, the entry routes through
the dock workspace to the matching CLI metadata table at the entity's
row. The decompiler text view also gets a context-menu host so other
[ExportContextMenuEntry] contributors that target a TextView surface
become reachable here too.
Assisted-by: Claude:claude-opus-4-7:Claude Code
A column filter wrapped in `/.../` is parsed as a case-insensitive
regex; anything else stays on the existing substring path. Unparseable
patterns fall through to a literal substring match so a typo can't
take down the filter row. Compiled regexes are cached per filter
text.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The path-based restore (SessionSettings.ActiveTreeViewPath +
FindNodeByPath) already round-trips through the metadata sub-tree's
stable ToString() values — these tests pin that down: selecting
TypeDef and a debug-only Document table both save paths whose final
segments resolve back to the originating node, including the second
"Metadata: Debug Metadata (Embedded)" hop into the embedded PDB
sub-tree.
Assisted-by: Claude:claude-opus-4-7:Claude Code
SessionSettings now carries HideEmptyMetadataTables (default true,
matching prior behavior) and MetadataTablesTreeNode honors it: when
true, tables with zero rows are hidden; when false, every CLI
TableIndex is surfaced. The setting persists across launches via
ILSpy.xml. There's no options dialog yet to flip it, but the plumbing
is in place for a future settings UI to toggle.
Assisted-by: Claude:claude-opus-4-7:Claude Code
DebugDirectoryTreeNode is now a lazy folder. Expanding it walks the
debug directory and adds a nested MetadataTreeNode per
EmbeddedPortablePdb entry, so the user can browse the debug-only
metadata tables (Document, MethodDebugInformation, …) the same way
they browse the host module's tables. The grid view (entry list) is
preserved when the node itself is clicked.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The metadata page's KeyDown handler now mirrors the "Go to token"
context-menu entry: when Ctrl+G fires while focus is on a token-kind
DataGridCell (or, as a fallback, while one is hovered), dispatch the
same NavigateToCellRequested path as the hyperlink-style click. The
focus-resolution + dispatch logic lives behind an internal
TryNavigateToTokenInCell hook so tests can drive it without simulating
the keyboard event end-to-end.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Upgrade the integration filter test to drive the live DataGrid: locate
the TextBox the column builder embedded in the Name column's header,
set its Text, and assert on DataGridCollectionView.Count — the visible
row count the user actually sees. Drop the temporary verification
fixture; the regression coverage now lives alongside the predicate /
event-forwarding unit tests.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Object-initializer Filter assignment lands on the DataGridCollectionView
instance but is gone by the time the grid finishes wiring it up — the
live view ends up with Filter=null, so Refresh() returns the unfiltered
source. Construct the view first, attach to the grid, then assign Filter
so the predicate sticks. The verification oracle now asserts on the
view's Count (the actual visible-row count) rather than re-running the
predicate manually, which catches this exact regression.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Selecting a tree node was firing ShowSelectedNode twice — once via
SelectedItems.CollectionChanged and once via SelectedItem
PropertyChanged. Each call ran node.CreateTab(), producing a fresh
MetadataTablePageModel with a fresh ColumnFilters set. The DataGrid
ended up rendering the second page's columns, but the first page's
filters were what tests (and any code reading the model) saw — so
typing into a header TextBox updated a filter no one was reading.
Track the last shown nodes inside ShowSelectedNode and short-circuit
when they match.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Three unit tests pin down the bridge between the column-header TextBox
that MetadataColumnBuilder bakes into each header and the page model:
typing into the box updates ColumnFilter.Text, programmatic filter
changes push back to the box, and either direction raises the
page-level ColumnFilterChanged event the view subscribes to.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The page now subscribes to its own ColumnFilters' PropertyChanged via
CollectionChanged and re-raises ColumnFilterChanged. The view listens
to that single event instead of chasing per-filter listeners on every
schema swap, which was leaving the auto-refresh path silent on the
running app even though the predicate itself was correct.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The CommunityToolkit.Mvvm source generator over a `value` field interacts
poorly with Avalonia's INPC binding plugin: typing into the per-column
filter TextBox throws TargetException("Non-static method requires a
target") because the cached accessor's target reference comes back null
on write. Renaming the backing field and property avoids the collision
with the contextual `value` setter identifier.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replace the single text-bar above the grid with one filter input per
column, baked into each DataGridColumn's header. The page model carries
a ColumnFilters collection alongside Columns; the filter predicate ANDs
every non-empty column's case-insensitive substring match. Column
instances are built fresh per page so sibling tabs don't share filter
state, and the column name moves to Tag for cell-level lookups now that
Header is a Panel.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Right-click on a [Kind=Token] cell now exposes a "Go to token" menu item
that reuses the same NavigateToCellRequested path as the hyperlink-style
left-click. Visibility is gated on the click landing on a token-kind
column, so plain columns and non-metadata grids stay clean.
Assisted-by: Claude:claude-opus-4-7:Claude Code
MetadataCellTooltip resolves `{ColumnName}Tooltip` on the bound row by
reflection. The view subscribes to PointerMoved, walks up to the
DataGridCell under the pointer, and applies the resolved string via
ToolTip.SetTip whenever the cell changes.
Assisted-by: Claude:claude-opus-4-7:Claude Code