The InitMainMenu build path already inserts Separators between
MenuCategory groups (Open / Save / Remove / Exit in File, View /
Navigation / Options in View, etc.). The Simple-theme default style
renders them as a 1px line at very low contrast — barely visible
against the menu background, which is why "no groups in the menus"
was a reasonable read.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Previously pin used an explicit 22x22 size, custom padding, and a
Button.preview-pin class with hand-rolled #33000000/#55000000 hover
tints. The close button (its sibling in the tab strip) is a plain
Avalonia.Controls.Button with a ControlTheme applied by Dock's tab
template — no classes, no local sizing. Visual mismatch was noticeable.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The ShowIdentical toggle was wired model-side
(CompareTabPageModel.OnShowIdenticalChanged → RootEntry.EnsureChildrenFiltered)
and view-side (Rebind on PropertyChanged), and the cascade was
correctly setting IsHidden via ComparisonEntryTreeNode.Filter. But the
HierarchicalModel's ChildrenSelector and SetRoots passed node.Children
through unfiltered — so IsHidden flags had no effect on what the
DataGrid actually rendered. Toggling the checkbox looked like a no-op.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The Language and Language-Version pickers in the main toolbar have no
effect when the active tab renders PE-header / metadata-table fields
straight from the metadata, or shows a structural compare diff —
language choice doesn't change what's drawn. Mirrors WPF's per-tab
TabPageModel.SupportsLanguageSwitching flag.
Assisted-by: Claude:claude-opus-4-7:Claude Code
VS-style preview-tab semantics, refined per user spec: pinning a tab
should freeze its contents in place but NOT immediately spawn a fresh
preview tab beside it. The new preview tab opens lazily, on the next
tree-node selection that finds the active tab frozen — at which point
the new content lands in a brand-new tab and the pinned one is left
untouched.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The sweep fires LoadedAssembly.GetLoadResultAsync on every sibling
assembly so users see icons "fill in" rather than each one showing
a loading state until first click. As written the sweep was firing
500 ms after TreeReady — which lands inside the first decompile's
Dispatcher.InvokeAsync window, triggering Server-GC pauses on the
UI thread during apply-text marshal-back. Measured: +460 ms FirstText
vs sweep-disabled on a single-type decompile workload.
Assisted-by: Claude:claude-opus-4-7:Claude Code
When --navigateto (-n) is supplied on the command line the user is
asking us to navigate to a specific entity. Today's code restores the
previously-saved tree path AND then runs the explicit target, which
produces two concurrent decompiles racing on SelectedItem (last write
wins). The saved decompile contaminates perf measurements taken via
`-n T:Some.Type` and adds GC pressure to startup for no user benefit.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The polymorphism modifier on the dock-layout JsonSerializerOptions
ran a fresh AppDomain.GetAssemblies() + SelectMany(GetTypes()) +
IsAssignableFrom walk for every polymorphic base type it built
JsonTypeInfo for — IDockable, IDock, IRootDock, IDockWindow,
IDocumentTemplate, IToolTemplate. Six independent passes over
~100 loaded assemblies, ~500 ms total on warm starts.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Workstation GC pauses every managed thread on Gen-1/2 collections,
so background allocations (decompiler work, layout deserialise,
search streaming) stall the UI thread for the duration of the pause.
Server GC spawns one heap per CPU core with dedicated GC threads;
collections of one heap don't pause the others. Concurrent stays on
so even server collections don't stop-the-world for long.
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
DisplaySettings.SortResults was persisted but unused — RunningSearch hardcoded
ComparerByFitness. Now SearchPaneModel.RestartSearch reads the setting at
start-of-search and passes either ComparerByFitness (default) or ComparerByName
into RunningSearch. Capturing at start matches WPF — mid-run toggles only
affect the next search.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds TabPageMenuItem (mirrors ToolPaneMenuItem) and a live ObservableCollection
on DockWorkspace kept in sync with factory.Documents.VisibleDockables. MainMenu
appends a separator + radio-style MenuItem per tab, with Header bound to Title
and IsChecked bound to IsActive. WPF parity for the previously-skipped tabs
section of the Window menu.
Assisted-by: Claude:claude-opus-4-7:Claude Code
WPF's Open-from-GAC dialog uses a ListView with five SortableGridViewColumns
(Reference Name / Version / Culture / Public Key Token / Location), each
click-to-sort, with the initial sort by name ascending. The Avalonia port
had reduced this to a one-column ListBox showing the raw ToString() —
all field-level information mashed into a single TextBlock, no per-field
sort, no Location column, no localised strings.
Assisted-by: Claude:claude-opus-4-7:Claude Code
User-reported: editor hover tooltips were not wide enough to show full
method signatures. Root cause was a three-way layout interaction —
DocumentationRenderer.AddSignatureBlock created the signature
SelectableTextBlock with TextWrapping.NoWrap, the outer Border capped
MaxWidth at 600px, and the wrapping ScrollViewer disabled horizontal
scrolling. So anything past 600px was simply clipped, with no way to
reveal the rest.
Assisted-by: Claude:claude-opus-4-7:Claude Code
WPF parity: under DEBUG, the language dropdown gains one extra entry per
C# AST transform — "C# - no transforms", "C# - after FirstTransformName",
… "C# - after LastTransformName" — so a developer can pick a step and
see the decompiler's mid-pipeline AST output.
Assisted-by: Claude:claude-opus-4-7:Claude Code
User-reported: Ctrl+LMB on an assembly-tree row both toggled the row in
the multi-selection AND opened a new decompiler tab. ProDataGrid's
Extended-selection mode treats Ctrl+LMB as a toggle, and
OnTreeGridPointerPressed also treated it as "open in new tab", so every
Ctrl+click fired both effects regardless of the e.Handled=true on our
handler — selection lives in the DataGrid template's own pre-bubble
pipeline.
Assisted-by: Claude:claude-opus-4-7:Claude Code
User-reported: opening multiple document tabs and restarting brings them
all back broken except the first one.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The "Compare..." right-click flow opened a tab that came up blank because
two stacked bugs masked each other.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Build the layout and call InitLayout up front in DockWorkspace instead of
letting DockControl run it post-template-apply, and populate the locators
explicitly: tool panes map their id to the [Shared] singleton pane, the
Documents dock and main tab get entries for navigate-by-id, and
HostWindowLocator is registered so panes can float into their own windows.
Doing it before the chrome attaches keeps the layout's owner/factory
wiring ahead of the first content realisation.
Also drops the now-redundant per-VM DataTemplate blocks from App.axaml --
the single ViewLocator already resolves them -- and the unused
MainWindowViewModel.DockFactory property and DockControl init flags.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Language.BracketSearcher defaulted to DefaultBracketSearcher
(no-op); only CSharpLanguage overrode it. Switching the active
language to ILAst or IL left caret-on-bracket inert.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The default ApiVisibility=PublicOnly setting filters search results
through CheckVisibility, which rejects private entities. Compiler-
generated names — async/iterator state machines (<Method>d__N),
display classes (<>c__DisplayClass), anonymous-method closures (<>c)
— are all private, so they were unfindable even when the user
typed the exact full name.
Assisted-by: Claude:claude-opus-4-7:Claude Code
UpdateLastDocumentCanClose was counting only factory.Documents.VisibleDockables
to decide CanClose. After the user drags a tab out into a side-by-side
split, Dock creates a second IDocumentDock that holds the dragged tab.
factory.Documents still points at the ORIGINAL dock (now with one tab),
so the handler updates CanClose=false on its remaining tab — but the
new sibling dock's tab keeps the CanClose=true it had before the drag.
Result: one tab closable, the other not, even though the user has two
documents open.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Stepper.Step's break-at-step-limit relies on Debugger.Break(), which
is a silent no-op when no debugger is attached. The wiring (command ->
RestartDecompileWithStepLimit -> DecompilationOptions.IsDebug ->
context.Stepper.IsDebug -> Stepper.Step) was all correct, but the
common-case "user runs from a terminal" left the gesture invisible.
Assisted-by: Claude:claude-opus-4-7:Claude Code
DockWorkspace previously subscribed to IFactory.ActiveDockableChanged
hoping it would fire on tab-strip clicks. It doesn't — Dock's
FactoryBase only raises that event from InitActiveDockable (layout
structural init at startup/load). User-driven tab clicks set
dock.ActiveDockable = X directly on the dock-model, which raises the
model's own INotifyPropertyChanged but bypasses the factory event.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Ports the WPF assemblyList_CollectionChanged history-prune from
ILSpy/AssemblyTree/AssemblyTreeModel.cs to the Avalonia
DockWorkspace.OnAssemblyListChanged handler. NavigationHistory<T>
already exposed the RemoveAll(Predicate<T>) primitive; the caller
wiring was the missing piece. Without it, a tree-row click after
removing an assembly walked through NavigationEntry.DisplayText
on a stale TreeNodeEntry, which hit MemberReferenceTreeNode.Signature
-> Language.EntityToString -> ILAmbience.ConvertSymbol and NRE'd on
a now-null ParentModule.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Sweep-up commit. The dotnet-format pre-commit hook keeps re-ordering
these usings on every other commit; landing them once stops the hook
from grumbling at unrelated diffs going forward.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Generalises the existing internal StartupLog into a public AppLog with
named categories. The Startup category (default-on) keeps the existing
[startup +Nms] format and Mark/Phase API, so all 44 call sites get a
mechanical StartupLog. -> AppLog. rename without behavioural change.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the reflection-driven Dock.Serializer.SystemTextJson._options
mutation with a from-scratch JsonSerializerOptions in ILSpyDockJson.cs.
Mirrors Newtonsoft's ListContractResolver semantics — polymorphism on
the six IDockable interfaces, IList<T> -> ObservableCollection<T>
substitution at deserialise time, writable-only property persistence,
the standard ignore set (ICommand, [IgnoreDataMember]) — and keeps the
existing back-reference / Task-shape strip plus the singleton-dockable
CreateObject hook.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Without per-dockable view resolution the dock chrome caches one view per
slot and reuses it for every dockable that lands there, so a second pane
sharing an Alignment (Analyzer + Debug Steps both Bottom) renders the
first pane's content under the second pane's tab. Each dockable instead
owns its view (IDockableViewOwner) and DockableViewRecycling hands that
instance back keyed by dockable identity -- there is no app-lifetime
global view cache to leak every transient document tab's view subtree.
Resolution runs through the single application-wide ViewLocator (its
dispatch map is introduced here rather than in the later DataTemplate
cleanup) so the dock never re-resolves an owned view through the template
machinery on a cache hit.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The three editor-command tests (Copy_Entry_Reflects, Copy_Execute, SelectAll_Execute)
selected `System.Linq.Enumerable` (the whole type, 300+ LINQ methods) and waited
on `view.Editor.Document.TextLength > 0` — a 15s `WaitForAsync` poll. In suite
context this was sloppy: it could latch on stale editor content from a previous
test, and in isolation it occasionally raced past 15s on a slow system,
producing the intermittent `failed: 1` we saw earlier.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The user-reported "Debug Steps pane is empty after clicking Show Steps" bug
was a View-lifecycle issue: `DebugStepsPaneModel` declares
`IsVisibleByDefault = false`, so the `DebugSteps` UserControl was realised by
the dock factory only when its tab was first activated. By then
`BlockILLanguage.DecompileMethod` had already fired `OnStepperUpdated` into
the void — no subscriber existed yet.
LMB double-click is WPF's expand/collapse gesture, already wired through
`OnTreeGridDoubleTapped`. An earlier patch also added it as a third "open in
new tab" gesture in `OnTreeGridPointerPressed`, so users got BOTH effects on
every double-click (the row toggled *and* a new tab opened). Restore parity
by removing the `isDoubleClick` branch from `OnTreeGridPointerPressed`; MMB
single-click and Ctrl+LMB single-click remain the two new-tab gestures.
Regression test in `HeadlessMmbPointerTests`.
Imports the three SVG assets that were missing from `ILSpy.Avalonia/Assets/Icons/`
and registers them in `Images/Images.cs`, so the MEF-injected toolbar buttons
for "Reload all assemblies", "Sort assembly list by name", and "Collapse all
tree nodes" now render their icons instead of falling back to the tooltip text
(`MainToolBar.axaml.cs:226` `ResolveIcon` returned null for these). Adds a
regression test asserting every `[ExportToolbarCommand]`'s `ToolbarIcon`
resolves to a real `IImage` field on the registry.
Two missing toolbar buttons (Manage Assembly Lists, Show Search) and an
incorrect button order; the layout now mirrors WPF MainToolBar.xaml +
InitToolbar exactly. Also fixes an InitializeButtons no-op under
Avalonia.Headless (Loaded never fired; switched to AttachedToVisualTree).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Persists the live dock layout to ILSpy.Layout.json next to ILSpy.xml on
MainWindow.OnClosing; loads it on DockWorkspace ctor; falls back to
factory.CreateLayout when the file is absent or fails to deserialize.
WPF stays XML in ILSpy.xml — this is Avalonia-side only.
Assisted-by: Claude:claude-opus-4-7:Claude Code
1) post-refresh-redecompile: AssemblyTreeModel.RefreshInternalAsync now calls
DockWorkspace.ForceRefreshActiveTab() after SelectNode. F5 on the same
assembly list doesn't rebuild the tree → FindNodeByPath returns the same
tree-node reference → SelectedItem setter early-outs → ShowSelectedNode's
lastShownNodes dedup short-circuits, leaving stale decompiled text. The
force path resets lastShownNodes and re-runs the decompile pipeline.
Mirrors WPF's RefreshDecompiledView() call.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Cherry-picked from master commits 4c8e606a6 + 4c347ef6b. Refresh (F5) on a
selection inside a lazy-loaded resource tree (e.g. a .baml entry inside an
embedded .resources file) was collapsing the selection back to the resources
folder because FindNodeByPath ran before the assembly's metadata-file
finished loading and the resource children hadn't materialised yet.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Splits Windows-only tests out of the cross-platform ILSpy.Avalonia.Tests
project so the Pdb2Xml / OpenFromGac code paths (which compile only on
Windows) can be exercised without conditional `#if WINDOWS` blocks inside
the main test suite.
Assisted-by: Claude:claude-opus-4-7:Claude Code