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
FilterText on MetadataTablePageModel drives a case-insensitive Contains
predicate over each row's stringified property values; the view wraps
Items in a DataGridCollectionView so the filter applies without rebinding.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds MetadataTokenReference(MetadataFile, Handle). DockWorkspace
subscribes to a metadata page's NavigateToCellRequested when its
Content is attached; the click handler reads the row's MetadataFile
and the column's int token via reflection, walks to the matching
MetadataTableTreeNode under the assembly's Tables container (mapping
HandleKind → TableIndex by value), selects it, and seeds ScrollToRow
on the freshly-bound MetadataTablePageModel. The view's ApplyScrollTarget
already honours ScrollToRow by scrolling + selecting the row.
Assisted-by: Claude:claude-opus-4-7:Claude Code
[ColumnInfo(Kind=Token)] properties now produce a DataGridTemplateColumn
whose cell is a transparent-background, blue-foreground Button — the
hyperlink affordance the user clicks to jump to the target table row.
Click raises NavigateToCellRequested(Row, ColumnName) on the owning
MetadataTablePageModel; Phase 3c subscribes from the dock workspace,
resolves the token to its target table tree node, and scrolls into view.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The generic MetadataTableTreeNode<TEntry> base now overrides
CreateTab() to return a MetadataTablePageModel populated from
LoadTable() + reflected columns. Every typed CorTable / DebugTable
inherits the grid behaviour for free; the per-table text dump is gone.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Tree-node selection now updates the inner Content of one persistent
ContentTabPage instead of swapping the dockable in the dock. The
wrapper view (ContentTabPageView) keeps both inner views — the
decompiler text editor and the metadata grid — pre-realised in the
visual tree from construction time and toggles which is visible based
on Content's runtime type.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Tree-node selections always operate on whatever document tab is
currently active. If the active tab is the wrong concrete type for the
new selection (e.g. a decompiler tab is up and the user picks a
metadata node, or vice-versa), the active dockable is replaced in
place — close the old, add the new, no sibling tab left behind.
Previously the host added a sibling tab and switched focus, which
piled up dockables and made "back to entity from metadata" feel
broken because the user could see two tabs competing for the slot.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Picking DOS Header → COFF Header → DOS Header was opening three
dockables because ShowCustomTab unconditionally added a fresh tab. The
docking host now searches for an open tab of the same concrete type,
copies the new node's tab state onto it, and re-activates it — same
shape as how the single decompiler tab gets reused across tree-node
selections.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The grid view's row-virtualisation handles the full heap (CoreLib's
truncation-footer wording are gone. Existing heap tests are rewritten
to assert against MetadataTablePageModel; the obsolete
MetadataHeapTreeNode.PreviewLimit constant follows the cap into the
deleted set.
Assisted-by: Claude:claude-opus-4-7:Claude Code
DOS / COFF / Optional / DataDirectories / DebugDirectory now expose
their entries through CreateTab() instead of writing a fixed-width text
table from Decompile. The grid view shows each entry's columns
reflected directly off the row shape — Phase 1's text table was a
stand-in until this view shipped. The existing PE-header tests are
rewritten to assert against MetadataTablePageModel.Items / Columns;
adds a Waiters.WaitForMetadataTabAsync helper used by every grid-tab
test.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Lays the rails for Phase 2 without flipping any existing nodes onto the
new path: MetadataTablePageModel + MetadataTablePage view +
MetadataColumnBuilder (cached per row type), an ILSpyTreeNode.CreateTab()
hook the docking host now consults before falling through to the
decompiler tab, and an App.axaml DataTemplate routing the model to the
view. PE-header / heap / table nodes still decompile as text — they get
flipped to CreateTab() one batch at a time in subsequent commits.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the universal placeholder for Field, MethodDef, Param,
MemberRef, CustomAttribute, and Assembly with their own typed leaves.
Each carries the columns the WPF view shows (RID / Token / Offset / row
data) — the analogous tooltips and click handlers stay deferred to the
Phase-3 navigation work since Phase 1 only renders text. The remaining
25 CorTables continue to land as placeholders until the next pass.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds the generic MetadataTableTreeNode<TEntry> base — caches LoadTable's
result and runs it through MetadataTextWriter for the Phase 1 text dump.
Ports Module / TypeRef / TypeDef / AssemblyRef as the first concrete
leaves; the remaining 31 CorTables and 8 DebugTables continue to land
through the universal placeholder until subsequent commits replace
them. Also fixes MetadataTextWriter to convert enum values to their
underlying integer before applying digit-bearing format strings, since
Enum.ToString rejects "X8".
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds the synthetic "Tables" folder that lazy-loads one leaf per
non-empty CLI metadata table, sitting between the PE headers and the
heaps to match WPF tree shape. The leaves are universal placeholders for
now — typed per-table viewers (35 CorTables + 8 DebugTables) replace
them in the next commit.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds the four CLI heap views (#Strings, #US, #GUID, #Blob) under each
assembly's Metadata folder. Phase 1 caps each heap's text dump at 200
preview rows with a truncation footer; the full set arrives with the
DataGrid view in Phase 2. Heap rows are materialised lazily on first
demand so opening the Metadata folder doesn't pay for heaps the user
never visits.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds the five PE-format leaves (DOS / COFF / Optional / DataDirectories
/ DebugDirectory) under each assembly's Metadata folder. Phase 1 renders
them as fixed-width text tables via Decompile; Phase 2 will swap to a
DataGrid tab. Introduces Entry / BitEntry / ColumnInfoAttribute and a
MetadataTextWriter helper that the heap and table nodes reuse.
Assisted-by: Claude:claude-opus-4-7:Claude Code
First slice of the Metadata Explorer port (Task 31). Adds a synthetic
MetadataTreeNode child to every loaded PE assembly, slotted before the
References folder to mirror WPF's AssemblyTreeNode ordering. The container
itself decompiles to the same metadata-summary text WPF's DumpMetadataInfo
emits — kind, version, debug header (if any), per-table row counts.
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
Right-click on a Type / Method / Field / Property / Event / Namespace row
opens the MS-Docs landing page for the selected entity in the default
browser. URL shape:
https://learn.microsoft.com/dotnet/api/<reflection-name>
with backticks (generic arity) → hyphens, '+' (nested types) → '.', and
'.ctor' → '-ctor' so constructors resolve correctly.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Reveals the assembly's file in the OS file manager. Walks the selection's
parent chain to the enclosing AssemblyTreeNode so the entry works on
members / namespaces too, not just the assembly row.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds the IMemberTreeNode interface (the same contract WPF uses) and makes
the five entity-bearing tree-node types — Type / Method / Field / Property /
Event — implement it so generic tooling can reach the underlying IEntity
without knowing which concrete tree node it has on hand.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Mirrors the WPF host's nested ReloadAssembly entry. Same visibility shape
as Remove (every selected node must be an AssemblyTreeNode). Execute calls
AssemblyList.ReloadAssembly on each selection's path, snapshots the
pre-reload tree paths so that after the LoadedAssembly instances are swapped
out the model can re-select an equivalent node via FindNodeByPath. The
existing SelectedItem -> ShowSelectedNode plumbing then re-decompiles into
the active tab.
Assisted-by: Claude:claude-opus-4-7:Claude Code
AvaloniaEdit's TextEditorOptions.RequireControlModifierForHyperlinkClick
only flows into its *built-in* LinkElementGenerator (via the
IBuiltinElementGenerator.FetchOptions hook the editor calls from
UpdateBuiltinElementGeneratorsFromOptions). Subclasses added to
ElementGenerators by hand inherit whatever the parameterless base
constructor sets — which is "true" — so the editor-level flag flipped in
DecompilerTextView didn't actually reach our generator.
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
Each tree-expansion, selection change, command execution, async wait and
key/mouse injection inside an [AvaloniaTest] now carries a short imperative
comment on the line above (e.g. "// expand typeNode", "// select methodA",
"// wait for assemblies to load", "// execute aboutCmd"). The comments are
the same scaffolding the manual debug-with-screenshot workflow uses to
follow what's happening at each breakpoint, surfaced into the committed
source so the tests are readable without the breakpoint markers attached.
Assisted-by: Claude:claude-opus-4-7:Claude Code
AssemblyTreeNode now overrides CanDelete / Delete / DeleteCore (matching
the WPF host: Delete unloads the assembly via AssemblyList.Unload, with
package-nested entries opting out via CanDelete=false) and exports a
nested RemoveAssembly context-menu entry tagged Header="_Remove".
Assisted-by: Claude:claude-opus-4-7:Claude Code
AssemblyListPane attaches a ContextMenu to its TreeGrid at construction
time. The menu's Opening handler builds entries from the
ContextMenuEntryRegistry against a fresh TextViewContext (the live
selection + the grid as OriginalSource), and cancels Opening when no
entry would be visible so an empty popup never flashes up.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds the MEF extension surface that lets commands contribute right-click
entries the same way they contribute main-menu and toolbar items today:
Assisted-by: Claude:claude-opus-4-7:Claude Code
CommandLineArguments was already parsed at App.OnFrameworkInitializationCompleted
but never read. AssemblyTreeModel grows HandleCommandLineArgumentsAsync that
applies the parsed values:
Assisted-by: Claude:claude-opus-4-7:Claude Code
Flips Editor.Options.RequireControlModifierForHyperlinkClick to false in
DecompilerTextView's constructor — that's the value AvaloniaEdit's built-in
LinkElementGenerator forwards onto every VisualLineLinkText it constructs,
so toggling it once at the editor level removes the Ctrl requirement for
every hyperlink in the decompiled output (in-app references, About-page
resource links, future external URLs alike).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Generalises NavigationHistory<SharpTreeNode> into NavigationHistory<NavigationEntry>
with two subtypes — TreeNodeEntry (a tree-node selection in a specific tab)
and StaticPageEntry (a static page like About in a specific tab) — mirroring
the WPF host's NavigationState/ViewState pair.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the previous WriteReference(string, Uri) approach with a
LinkElementGenerator subclass that scans the rendered document for the
target phrases ("MIT License" / "third-party notices"), matching the
WPF host's MyLinkElementGenerator pattern. The custom generator collection
travels through AvaloniaEditTextOutput → DecompilerTabPageModel.
CustomElementGenerators → DecompilerTextView (which installs and tears
them down per ApplyDocument).
Assisted-by: Claude:claude-opus-4-7:Claude Code
The "MIT License" and "third-party notices" phrases inside the About blurb
now write as ReferenceSegments carrying resource: URIs instead of plain
text. The new tab subscribes to NavigateRequested and opens the embedded
LICENSE / third-party-notices.txt content in a new tab on click.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the NotImplementedDialog stub with a real AboutCommand that builds
an AvaloniaEditTextOutput (ILSpy version, .NET version, embedded
ILSpyAboutPage.txt) and routes it through DockWorkspace.OpenNewTab as a
DecompilerTabPageModel. Adds an IAboutPageAddition extension point so future
plugins can append to the page. ILSpyAboutPage.txt / third-party-notices.txt
/ LICENSE are embedded via the same shape WPF uses.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds a leading paragraph describing what each test verifies and Arrange /
Act / Assert markers (split into numbered phases for multi-step tests) so
the intent of each fixture is readable from comments alone.
Assisted-by: Claude:claude-opus-4-7:Claude Code