* Set v11 RTM
* Update features in README.md
* Remove the two 900-iteration process-list scroll tests
* Keep Svg.Controls.Skia.Avalonia at 12.0.0.13
* 10.0.11 and Roslyn for net11p7
* Fix module-scan test failing when PowerShell's NGen images are stale
* Opt Pack NuGets out of the MSBuild server to fix SBOM generation
ProDataGrid derives its scroll extent from the current scroll offset, so an
offset that is briefly too large inflates the extent, which permits a larger
offset again. A trackpad reaches that state within a few hundred sub-row
events: instrumented in the running app, a ~1500px list reported 8735px and
kept growing, the thumb collapsed to its minimum, and the end of the list ran
away from the user. A second defect slid the rows sideways by up to 10px - the
star-sized column is measured against the width including the vertical scroll
bar, leaving the grid convinced it has a scroll bar's worth of content to
reach.
Disabling the horizontal scroll bar, giving each grid its own
DefaultRowHeightEstimator, pinning RowHeight, forcing the vertical scroll bar
visible, and disabling scrolling on the template's inner ScrollViewer each
removed a symptom at most; none can break a loop that runs through the scroll
offset, and 12.0.4 is the newest release. A ListBox's virtualizing panel keeps
the extent a function of the items alone. The price is laying out the columns
here - so the header row and the item template have to be kept in step - and
losing sortable, resizable headers.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
A refused unix socket raises SocketException, which derives from
Win32Exception rather than IOException, so it escaped the filter meant to
skip one unreachable process and failed the whole concurrent enumeration
instead: a machine where any .NET process exits between the port scan and
the connect showed an empty list. The classification is now a named
predicate covering both transports.
Two dialog defects shared a shape - state left behind by a query nobody is
waiting for any more. Rebuilding the bound collection on every filter
keystroke made the grid drop its selection and write that null back,
discarding the assemblies of a process the new filter still matched; and the
branch taken when nothing is selected cleared no loading flag, while the
query it superseded was no longer allowed to, so the progress bar animated
over an empty pane. Relatedly, the two-second command budget expired into a
process listed with null metadata, which made a slow machine look like a
runtime that answered with nothing; it is longer now, and expiry names the
process it gave up on, since the only thing that ever reaches the far end of
that budget is a runtime which will never answer.
The test gaps are closed the same way: a real dynamic assembly pins the
in-memory classification, the managed-only assertion is stated as the
property instead of a list of native names to exclude, and the .NET
Framework path gets live tests. Those showed that a desktop CLR process
mostly reports NGen native images rather than the IL assemblies behind them,
which is now recorded as the fidelity gap it is.
Assisted-by: Claude:claude-opus-5:Claude Code
With a modern .NET app the executable in the process list is a native
apphost that carries no IL, so the path a user can see is precisely the
one a decompiler cannot open - ILSpy itself is an example. Ask the
runtime instead: since .NET Core 3.0 every CoreCLR process serves a
diagnostics endpoint that names its managed entry assembly and, via an
EventPipe rundown, every assembly it has loaded, including ones behind a
single-file bundle or with no file at all. The endpoint answers the same
way on Windows, Linux and macOS and needs no privileges beyond same-user,
which also makes it the only workable route on macOS, where native
process introspection is gated by SIP.
The protocol and the nettrace container it returns are implemented here
rather than taken from Microsoft.Diagnostics.NETCore.Client, so the
feature costs no new package reference; the reader is scoped to loader
rundown events and steps over everything else by size. The rundown asks
for the loader keyword alone: the runtime's default set also collects the
JIT and IL-to-native-map rundown, which in a long-running process buries
the module list and overruns the session buffer, costing the very events
the dialog needs.
Windows additionally lists .NET Framework processes, which predate the
endpoint and are read from their OS module list instead.
Assisted-by: Claude:claude-fable-5:Claude Code
Filtering and un-filtering the Debug Steps tree re-arranges rows around
the selection: matches surface under newly expanded groups and the
restore step re-opens the selected step's ancestors. Without scrolling,
the selected row regularly ended up outside the viewport even though it
was expanded into view. The ViewModel now raises a reveal request after
each filter pass while a visible step is selected; the View answers it
by centering the row, because scrolling needs containers and the
ScrollViewer. The measurement anchors on the row header since an
expanded group's container spans its whole subtree, and it runs at
Loaded dispatcher priority so freshly expanded containers have valid
geometry.
Assisted-by: Claude:claude-fable-5:Claude Code
The filter drove every row's IsExpanded from the single IsFiltering flag
via a TreeViewItem style setter. Expansion lived nowhere else: user
gestures (expander arrow, double-tap, arrow keys) all write the property
with SetCurrentValue, which the style binding overwrites on every flip
of the flag. Starting a filter therefore destroyed the expansion state
the user had built up, and clearing it collapsed the whole tree,
burying the still-selected row under collapsed groups.
Row state (visibility + expansion) now lives on a StepNodeViewModel
wrapper per step, two-way bound from the style, so gestures persist in
the view-model. A filter session snapshots expansion on entry, hides
non-matches and opens only the paths to matches while typing, and on
exit restores the snapshot and re-expands the selected step's ancestors
so the selection stays visible. Wrapping is skipped for reference-equal
step lists because step replays re-report the same run and a rebuild
would wipe the state mid-navigation.
Assisted-by: Claude:claude-fable-5:Claude Code
The step recorder is shared by C# AST and ILAst replay, so keep the public infrastructure out of IL.Transforms and pass language-specific node navigation through neutral node metadata.
Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
Normal decompiles do not consume node ranges, so keep AvaloniaEdit node tracking opt-in and enable it only for step-limited replay output.
Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
The replay tests only checked DebugStepHighlight was non-null, which the
ancestor fallback satisfies unconditionally -- a regression widening every
highlight to the enclosing method would have passed. Assert instead that the
range lies in bounds, does not span the whole document, and (unless it is a
zero-length removal caret) covers non-whitespace rendered code.
Assisted-by: Claude:claude-opus-4-8:Claude Code
NodeLookup.AddNode indexed every annotation of every rendered node by reference
identity, but the debug-step highlighter only ever looks up the DebugStepMarker;
the rest were dead keys, and a shared annotation (ResolveResult and friends,
copied across nodes) would resolve to whichever node rendered last. Make
DebugStepMarker public and bridge only it -- behaviour-preserving for resolution
while dropping the per-annotation dictionary churn on every rendered node.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The step tree can run to hundreds of entries per decompile, so finding a
specific mutation means scrolling. Add a filter box in the pane's top-right
corner: a row survives when its description -- or any descendant's -- contains
the text (case-insensitive), keeping the path to every match, and the tree
auto-expands while filtering so matches nested under transform groups stay
visible. Implemented as an item-visibility converter over the existing TreeView
rather than switching to SharpTreeView, which would change the Steps contract
and rewrite the pane's tests for no functional gain here.
Assisted-by: Claude:claude-opus-4-8:Claude Code
A step that removes a node has nothing left to highlight in the resulting text,
so range resolution fell back to the enclosing block and flooded it. Record the
changed node's surviving neighbours as seam anchors (captured before the
mutation) and split a step's candidates into precise / seam / ancestor tiers:
when neither the node nor its marker resolves, place a zero-length caret at the
gap -- the successor's start, else the predecessor's end -- and only fall back
to the enclosing block when no neighbour survives. A zero-length highlight is
rendered as a caret (positioned, pulsed, centered) with no background mark.
Assisted-by: Claude:claude-opus-4-8:Claude Code
MarkNodeStart and BeginSpan captured builder.Length to anchor a node range
or highlight span, but indentation is written lazily on the first token of a
line. A node or span opened at the start of an indented line therefore
recorded its start before the leading tabs, so the debug-step highlight (and
any span) extended back across the indentation to column 0.
Flush the pending indent in both before capturing the offset, matching the
WPF AvalonEditTextOutput.BeginSpan the Avalonia port derived from. The emitted
text is unchanged -- the indent is written either way, in the same place; only
the recorded start moves to the first real character.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Debug Steps "show state before/after" replay re-decompiles the active
tab; the headless UI tests inferred completion by polling IsDecompiling +
Text, which can return on stale state or hit the 60s wait deadline when
that shared signal races under CI load (the observed intermittent CI
timeout). RestartDecompileWithStepLimit now returns the decompile Task so
the replay completion can be awaited deterministically; the tests await it
instead of polling. The production IsDecompiling reset is unchanged -- the
last decompile's finally always resets it; the race was only in the test's
completion inference.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The C# debug-steps view highlights and centers the exact AST node a
transform changed; the ILAst view already had the step tree and
replay-at-step but produced no highlight. Bring it to parity.
IL rendering has no token-writer seam like the C# output visitor, so
per-instruction text spans are recorded by bracketing
ILInstruction.WriteTo via a new INodeTrackingOutput. The dominant
inst.ReplaceWith(newInst) transform pattern detaches the instruction
passed to Step, so ILTransformContext gains EndStep to record the
produced instruction; Stepper additionally records the position's
ancestor chain as fallback candidates before the step-limit throw, so
the "show state before" view -- which halts at the selected step --
still resolves to a surviving ancestor (ultimately the ILFunction).
The highlight-range resolver is shared with the C# language.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Record AST transform groups and mutation steps through the C# pipeline, replay selected steps with the stepper, and carry modified-node ranges through output so the Debug Steps pane can highlight the selected mutation without replacing its full step tree.
Assisted-by: CodeAlta:gpt-5.5:CodeAlta
ILSpy resolves an assembly's references against the target framework it
detects from the TargetFrameworkAttribute. When that attribute is missing,
wrong, or the user wants to force a different framework, there was no way to
hint the correct one, so references could resolve against the wrong runtime
pack or framework directory.
A LoadedAssembly can now carry a TargetFrameworkIdOverride that short-circuits
detection (it is the single value every LoadedAssembly-based resolution path
reads), is persisted in the assembly-list XML, and is carried across a reload
so a runtime change re-resolves against the new framework. The "Set Target
Framework" context-menu entry edits it through a dialog with a free-form text
box and an always-visible list of common monikers to pick from (the app forces
overlay popups, so a dropdown would be clamped inside the small dialog); input
is validated and converted from the short TFM users know (net48) to the long
FrameworkName form the resolver consumes (.NETFramework,Version=v4.8) via
NuGet. The direct DetectTargetFrameworkId callers in the decompiler core
(project export, language version) intentionally keep reading the real
attribute; only reference resolution is overridden.
Resurrects a 2020 prototype (branch tfmoverride) re-implemented against the
current ILSpyX/Avalonia code, whose surrounding structures no longer matched.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The "Open from NuGet feed" chooser rendered every result with the default
NuGet logo, even though the feed search already returns each package's own
IconUrl - it was fetched into NuGetPackageInfo and then ignored. The list
looked like a wall of identical icons, unlike the NuGet gallery.
NuGetPackageInfo is an immutable feed DTO bound straight into the ListBox, so
it can't raise PropertyChanged for an icon that arrives later. Rather than
bolt mutability and an Avalonia image type onto that DTO, introduce a thin
per-row view model (NuGetPackageViewModel) that owns an observable Icon
defaulting to the logo and swaps in the real icon once downloaded, plus an
INuGetIconLoader service that fetches and decodes off the UI thread. Icons
are decoded to 64px (rendered at 32) and cached by URL as the in-flight Task
so each URL downloads once and concurrent requests share it; the loader is
held for the command's lifetime so the cache survives reopening the dialog.
A per-search CancellationTokenSource stops a stale result set's downloads
from painting onto rows recycled by the next search. Any failure - non-http
URL, network error, decode error - collapses to null so a dead icon URL just
keeps the default.
Also disable horizontal scrolling on the results list. With it enabled
(the default), rows measured at unbounded width, so the star column stopped
filling the viewport and selecting a row brought the overflow into view -
the list jumped sideways and grew a scrollbar. The row layout is meant to
fit the width (the description wraps/trims), so horizontal scrolling was
never wanted.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Avalonia port had placed the UI app in an ILSpy.* namespace tree,
while the csproj RootNamespace and every prior release (through 10.1)
use ICSharpCode.ILSpy.*. Restoring the historical namespace reduces the
public API diff against release/10.1 for plugin authors and removes the
shadowing that forced global:: qualifiers in the test project. The
Images class and AccessOverlayIcon enum move back into the root
namespace (as in 10.1), since an ICSharpCode.ILSpy.Images namespace
would shadow the Images class for all code inside ICSharpCode.ILSpy.
Assisted-by: Claude:claude-fable-5:Claude Code
When the current language is not an IDebugStepProvider (e.g. the IL
disassembler), the Debug Steps pane kept displaying the previous
language's step tree, and its commands still triggered re-decompiles
with a step limit the language ignores. Detaching now clears the tree
and the view shows a "not available" note until a step-providing
language is selected again.
Assisted-by: Claude:claude-fable-5:Claude Code
Opening a package from a feed previously meant downloading the .nupkg by
hand and using File > Open (#2313). The new File menu command searches any
public V3 feed (editable package-source list, persisted as an MRU in the
ILSpy settings), offers the latest 100 versions in a dropdown, and downloads
into the NuGet global packages folder so the cache is shared with every
other NuGet consumer on the machine and nothing is fetched twice. The cached
.nupkg is then opened exactly like the regular Open command. Feed access
sits behind INuGetFeedClient so the headless test suite covers search,
paging, version selection, download, cancellation, and error surfacing
without touching the network.
Assisted-by: Claude:claude-fable-5:Claude Code
Window > Debug Steps did nothing. Two bugs in ToolPaneMenuItem:
the getter treated a pane with a null Owner (one hidden by default and
never placed in the layout) as visible, so the menu showed it checked
and toggling tried to hide it; and the show branch used
factory.RestoreDockable, which only un-hides a previously-shown pane and
is a no-op for one that was never in the layout.
Report visibility from real layout membership, and show via
ShowToolPane, which materialises the pane and (re)creates its home dock.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The menu builder hard-set each NativeMenuItem's IsEnabled from static
export metadata (always true), overriding the state that assigning Command
would otherwise derive from the command's CanExecute. The inline
NativeMenuBar on Windows/Linux re-derives from the command and greys out
OS-gated items (e.g. Open from GAC, which is Windows-only), but the macOS
native menu reads NativeMenuItem.IsEnabled directly -- so those items
stayed enabled there even though invoking them is a no-op. Drop the
explicit IsEnabled so every command-backed item follows its CanExecute on
all platforms.
Assisted-by: Claude:claude-opus-4-8: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
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.
Brings the WPF decompiler-pipeline-stepper feature across. Available only
in Debug builds — the entire feature set is gated behind `#if DEBUG`, so
Release users see neither the pane nor the ILAst languages in the picker.