Two readability fixes for the metadata grid:
- Override DataGridColumnHeaderHoveredBackgroundBrush / Pressed to the toolbar accent
palette so the Simple theme's saturated dark fill stops burying header text while
sorting / resizing.
- Force StandardCursorType.Arrow on the filter popup's root Border. Without it the
EW-resize cursor set by the column header's drag-grip can leak into the popup if
the pointer enters from there before Avalonia recomputes the cursor.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The Simple theme's :checked default for ToggleButton paints a saturated dark blue
that buries the chip's label. Override with the toolbar's translucent hover accent
(#330078D7) on the inner ContentPresenter so active chips stay readable while still
reading as "selected"; pressed states keep the denser #660078D7 fill for a click cue.
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
[Flags] columns now get a small ▾ button next to the inline TextBox
that opens a popup with one CheckBox per defined flag value. Toggling
boxes rebuilds the filter as a comma-separated flag-name list (the
predicate's flags-aware path consumes it directly), so the user can
discover and combine flags without typing names from memory. The
TextBox stays for free-form input (regex, !-negation, mixed input);
opening the popup syncs the checkboxes from whatever's currently in
the filter.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Enum.ToString rejects multi-character format specs like "X8", so a
flags column raised FormatException out of Copy Row. Mirror what
HexFormatConverter does at bind-time: widen the value to its
underlying integer (Convert.ChangeType + GetTypeCode) before calling
IFormattable.ToString.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Sibling of "Copy" that emits the full row tab-separated, reusing the
same format-aware ReadColumnValue helper so hex columns serialise as
hex on the clipboard.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The DataGrid's built-in cell copy doesn't surface in the context menu
(only via Ctrl+C); removing the seed dropped Copy entirely from the
right-click menu. Add it back as a single-cell-value copy that mirrors
the column's display format (so "0x06000001" lands on the clipboard
rather than "100663297").
Assisted-by: Claude:claude-opus-4-7:Claude Code
Avalonia DataGrid surfaces a per-cell Copy in its default cell context
menu, so seeding our own at the top of the built menu produced a
duplicate "Copy" entry. Revert the seed; cancel our menu when no MEF
entries are visible (the built-in cell menu still shows).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Right-click on a non-token cell was producing nothing because the
GoToToken entry hides itself there and ContextMenuProvider.Build then
returned null, cancelling the menu. Always seed a Copy entry (TSV row
dump matching DataGrid's built-in Ctrl+C behaviour) at the top of the
built menu; MEF entries follow after a separator when applicable.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replacing the default editor menu with the MEF-driven one stripped
the standard text-edit affordances. Always seed Copy and Select All
at the top of the built menu (Copy disabled when there's no
selection); MEF entries follow after a separator. Right-clicking
plain text now produces a usable menu instead of nothing.
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 PointerPressed/Released intercept was too aggressive — it ran
before the TextBox's own focus handler, so the user could no longer
click into the input. The TextProperty-observable change alone is the
fix that makes typing flow into ColumnFilter.Text.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Two fixes for the column-header filter inputs that landed visually but
did nothing on the running app: (1) intercept PointerPressed/Released
on the TextBox so DataGridColumnHeader's sort-click handler doesn't eat
the click before the box can take focus, (2) drive the filter from
TextBox.TextProperty's observable rather than the TextChanged event,
which doesn't fire reliably for a TextBox hosted inside a header.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Hosting an interactive TextBox inside a DataGridColumn.Header proved
unreliable: the column header's PointerPressed handler intercepts clicks
for sort, so the filter input never received focus or keypresses on the
running app. Move the filter row to a sibling StackPanel above the
DataGrid where each TextBox tracks the corresponding column's
ActualWidth via the grid's LayoutUpdated event, keeping per-column
alignment without competing with the header for input.
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
Replace the Avalonia.Data.Binding(Source = filter, Mode = TwoWay) on the
header TextBox with explicit TextChanged + PropertyChanged subscriptions.
The INPC plugin holds a WeakReference to the binding source and resolves
it lazily on write-back; for column-header-hosted controls the target
reliably comes back null, throwing "Non-static method requires a target"
from PropertyInfo.SetValue. The event-based wiring keeps strong refs and
avoids the weak-reference machinery entirely.
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
Close-then-add in the same dispatcher tick was leaving the previous
view rendered until a layout pass caught up — the user saw the
"Decompiling" spinner from the closed decompiler tab persist after
switching to a metadata node, and going back to an entity node from
metadata didn't bring the text view forward. Replacing the dockable
in the VisibleDockables list as a single mutation fires
INotifyCollectionChanged.Replace, which Dock translates into a clean
visual swap.
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
Closes out Phase 1e: every CLI metadata table now has its own typed
leaf. Bytes-level table walking goes through MetadataReader.AsBlobReader
(public API) instead of the WPF Helpers' AsReadOnlySpan path; coded-token
columns are decoded by the new MetadataReaderHelpers shim, which
reflects against the same private TypeDefOrRefTag / HasFieldMarshalTag /
MemberForwardedTag.ConvertToHandle methods + ComputeCodedTokenSize that
the WPF host uses (a known forward-compatibility risk per the plan's
risk register).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the universal placeholder for the 8 PDB-only metadata tables:
Document, MethodDebugInformation, LocalScope, LocalVariable,
LocalConstant, ImportScope, StateMachineMethod, CustomDebugInformation.
StateMachineMethod reads its rows directly via MetadataReader.AsBlobReader
(public API, no internals reflection); the rest follow the typed-handle
pattern used by the CorTables.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the universal placeholder for StandAloneSig, DeclSecurity, and
File. These three are the last CorTables that System.Reflection.Metadata
exposes as direct enumerations / typed handles. The remaining ~12 tables
(InterfaceImpl, NestedClass, ClassLayout, FieldLayout, FieldRva,
FieldMarshal, ImplMap, MethodImpl, MethodSemantics, EventMap,
PropertyMap, the *Ptr family) read raw little-endian metadata bytes via
Helpers.GetValueLittleEndian + coded-token reflection helpers in the
WPF tree, so they wait for the Helpers infrastructure port.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the universal placeholder for ModuleRef, TypeSpec, Constant,
Property, Event, GenericParam, GenericParamConstraint, MethodSpec,
ManifestResource, and ExportedType with their own typed leaves. Same
pattern as the prior CorTables batch — RID / Token / Offset followed
by the table's own column shape.
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