Pull in the submodule's new root Directory.Build.props so the decompiler
test fixtures aren't subject to this repo's TreatWarningsAsErrors=true.
Assisted-by: Claude:claude-opus-4-8:Claude Code
A clean build currently has zero warnings; enforce that so new ones can't
accumulate unnoticed. Set TreatWarningsAsErrors in the root
Directory.Build.props rather than per project. ICSharpCode.ILSpyCmd keeps
its existing TreatWarningsAsErrors=false (project-level wins over the
imported default), and the three legacy net472 projects (the VS add-ins
and the installer builder) opt out explicitly since they only build on
Windows and aren't covered by the cross-platform CI that gates this.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Microsoft.DiaSymReader / .PortablePdb packages pull NETStandard.Library
1.6.1, which drags in 4.3.0 builds of System.Net.Http, System.Private.Uri
and System.Text.RegularExpressions, all carrying known advisories (NU1902/
NU1903). They are framework-provided at runtime on net10.0; pin the patched
4.3.4 / 4.3.2 / 4.3.1 via central transitive pinning so NuGet audit passes.
Enabling transitive pinning also aligns a handful of other transitive
packages to their declared central versions.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The 23 typed table-row viewmodels each inlined the same
MetadataOffset + GetTableMetadataOffset + GetTableRowSize * (rid - 1)
arithmetic, and the four heap tree nodes each repeated an identical
lazy-cache / "{name} Heap (count)" header / CreateTab shell that differed
only in the heap name and how rows are walked. A single mistyped
TableIndex or a divergent cache shell would have been invisible across
that many copies.
Add MetadataTableTreeNode.GetRowOffset (with a MetadataReader+offset
overload for the one entry that lacks a MetadataFile) and a generic
MetadataHeapTreeNode<TEntry> base carrying the cache, header, and tab,
leaving subclasses to supply only the name and row materialiser.
Assisted-by: Claude:claude-opus-4-8:Claude Code
DockWorkspace carried the reflection-heavy metadata-grid resolution: read a
clicked token cell, resolve a double-clicked row to its tree node, and find
the metadata-table node a token points at -- with the "read the row's
metadataFile private field + int token" reflection block copy-pasted between
the cell-click and row-activation paths. As the WPF host kept this metadata
logic out of the main workspace, lift it into a MetadataNavigator service.
DockWorkspace keeps the dock-side actions (select, scroll-to-row, history
stamping, open-in-new-tab) and now delegates resolution; the public
NavigateToToken / TryResolveRowToTreeNode surface is unchanged, so the
external callers (ShowInMetadata, DecompileMetadataRowInNewTab) don't move.
The duplicated reflection collapses into one TryReadRow.
Assisted-by: Claude:claude-opus-4-8:Claude Code
AssemblyTreeModel carried a ~180-line node-finding subsystem -- resolve a
reference (entity / namespace / resource / assembly / metadata file) or a
saved path to its tree node -- that has nothing to do with the class's
selection and list-lifecycle responsibilities. Lift the implementation into
a TreeNodeLocator, with the locator taking the tree root and assembly list
as parameters so it stays stateless.
The model keeps its public FindTreeNode / FindNodeByPath / GetPathForNode as
thin delegators (they're called from DockWorkspace, SearchPaneModel,
AssemblyTreeNode and tests), so no external call site changes -- the god
class just sheds the implementation.
Assisted-by: Claude:claude-opus-4-8:Claude Code
ApplyDocument was a 116-line god-method and the constructor a 112-line wall
of unrelated wiring. Both are linear sequences with no shared control flow,
so split them into intention-named steps:
ApplyDocument (now ~40 lines) orchestrates RebuildFoldings,
RestoreOrResetViewState and SwapCustomElementGenerators; the constructor
(now ~30 lines) calls SetupElementGenerators / SetupBackgroundRenderers /
SetupHoverHandlers / SetupContextMenuAndHyperlinks / SetupRichPopup /
SetupZoomAndCopy. Order is preserved exactly. Pure refactor, no behaviour
change.
The five renderer/generator/popup fields lose `readonly` since they are now
assigned in the ctor-only Setup helpers rather than the ctor body; they are
still single-assignment in practice.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The C# 13 'allows ref struct' anti-constraint is emitted as a single
PrimitiveType token (TypeSystemAstBuilder), so it reached WritePrimitiveType
rather than WriteKeyword and fell through uncoloured. Add it to the
value-type keyword group alongside the other constraint, 'unmanaged'.
Assisted-by: Claude:claude-opus-4-8:Claude Code
WriteKeyword coloured every modifier the decompiler emits except
'required' (C# 11 required members), which fell through and rendered in
the default text colour. Add it to the modifiers group.
Assisted-by: Claude:claude-opus-4-8:Claude Code
WriteKeyword/WritePrimitiveType/WriteIdentifier/WritePrimitiveValue each
ended with the same begin-span / base-write / end-span guard. Replace it
with `using (Colored(color)) base.WriteX(...)`, backed by an allocation-free
ref-struct scope so the per-token hot path takes no closure.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Options page is non-modal and live-apply, so -- unlike the WPF host,
which ran a full assembly-list Refresh() when its modal Options dialog
closed -- a setting that changes the decompiler/disassembler output (brace
folding, member/using expansion, debug info, IL detail, indentation) had
nothing to make it take effect; toggling it did nothing until the user
re-navigated.
Classify every DisplaySettings property in one table (DisplaySettingReactions:
editor-live / tree-text / tree-shape / re-decompile / none), grounded in the
actual consumers (ApplyDisplaySettings, GetIndentationString, the IL/mixed
languages). OnSettingsChanged dispatches on it, and a coverage test asserts
the table spans every settable property so a newly-added one can't silently
fall through.
Fixing the re-decompile case exposed that ForceRefreshActiveTab was itself a
no-op for an unchanged node -- ShowSelectedNode re-sets CurrentNodes, whose
setter dedups -- so RefreshDecompiledView (also used after dependency
resolution) never actually re-ran. Add DecompilerTabPageModel.Redecompile()
to force past the dedup and call it from ForceRefreshActiveTab.
Also drop the EnableSmoothScrolling setting: it drove TomsToolbox's
AdvancedScrollWheelBehavior in WPF, which the Avalonia port doesn't use and
never replaced, so the checkbox persisted a value nothing read.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The UseNestedNamespaceNodes and HideEmptyMetadataTables handlers both
force a materialised node to re-create its children (clear, re-arm
LazyLoading, EnsureLazyChildren), guarded on it not already being lazy.
That's a general tree-node operation -- a soft, in-place rebuild that
re-runs LoadChildren without reloading the assembly -- so it belongs on
SharpTreeNode next to the lazy contract it builds on, not as a private
helper in the model. Both call sites now use node.ReloadChildren().
Assisted-by: Claude:claude-opus-4-8:Claude Code
The drag-drop / file-drop callback selected the dropped assemblies with a
raw SelectedItems.Clear() + Add() loop, which flashes a transient empty
selection. The model has SelectNodes precisely to avoid that -- it brackets
the swap so the selection-changed fan-out fires once, with the final set,
never empty. A transient empty poisons the grid-sync deferred guard, after
which the tree stops following tab activation. Route the callback through
SelectNodes; a regression test asserts no empty SelectedItem is observed
mid-drop.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The eight DecompileX overloads each opened with the same five lines:
resolve the module, build the decompiler, emit both reference warnings and
the assembly-name comment. Fold that into BeginDecompile(IEntity, ...),
which returns the decompiler; each overload then adds only its own (varying)
type-header comment. The three DecompileExtension overloads, identical bar
the comment target, now share a DecompileExtensionCore.
Assisted-by: Claude:claude-opus-4-8:Claude Code
DecompileAsync and ShowText both pushed the same eight properties (syntax
extension, highlighting model + spans, foldings, references, definition
lookup, UI elements, text) onto the page. Fold them into ApplyOutput,
which reads the collateral straight off the now-immutable output and takes
the syntax extension and text as parameters -- so the decompile path keeps
computing GetText off the UI thread and supplies it, while ShowText passes
its own. Drops the six hand-captured locals in the decompile apply step.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The "resolve assembly -> entity handle -> uncached DecompilerTypeSystem ->
ResolveEntity" sequence was copy-pasted in the code-view hover navigation
and the assembly-tree node finder. Move it onto EntityReference as a
Resolve(AssemblyList) member so both call sites just supply the list they
already have, and the uncached-per-call decision lives in one place.
Assisted-by: Claude:claude-opus-4-8:Claude Code
OpenNewTab's summary (and its sourceNode param doc) sat above ShowToolPane,
and FreezeCurrentTab's summary sat above SettleSelection, so two members
were undocumented while two carried a second, wrong summary. Move each back
to its member and fix the dead <see cref="OnActiveDockableChanged"/> (the
event is OnDocumentsPropertyChanged).
Assisted-by: Claude:claude-opus-4-8:Claude Code
Six command outputs ended with the same AddButton(OpenExplorer ...) +
WriteLine() tail. Add AddOpenFolderButton / AddRevealFileButton on
ISmartTextOutput and route each site through them. This also drops the
no-op `isFile ? path : path` in the package-extraction output, which now
picks the reveal vs open helper directly.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The "walk up to the enclosing AssemblyTreeNode" helper was byte-identical
in the open-terminal and open-containing-folder commands. Lift it to a
static on AssemblyTreeNode and route both through it.
Assisted-by: Claude:claude-opus-4-8:Claude Code
ApplyDocument and the theme-change rebuild both removed the active
RichTextColorizer, nulled the reference, and added a new one. Pull that
into SetColorizer(RichTextModel?) -- a null model just clears it -- so the
two callers only supply the model (the per-document one, or the one rebuilt
from the captured spans on a palette change).
Assisted-by: Claude:claude-opus-4-8:Claude Code
SetActiveDockable + SetFocusedDockable were always called together at five
sites across the factory and the workspace. Fold the pair into one
factory method so the invariant (a surfaced dockable is also focused) is
expressed once; the per-site "add to the dock first" step, which varies
(unconditional / only-if-absent / none), stays at the call site.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The SortResX target only set its command on Windows (powershell), so the
resx-sorting build step was a silent no-op on Linux/macOS even though
pwsh (PowerShell 7+) is available there -- mirror the PowerShell cmdlet
project's powershell/pwsh split. The included Resources.resx change is
that step catching up one entry (ExportProjectSolution) that had drifted
out of order while the target was disabled. The script is EOL-preserving,
so this stays LF with no cross-OS churn.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The two were identical bar the skip predicate (the home tab vs the kept
tab); both snapshot the visible documents and route each through CloseTab.
Extract the shared loop as CloseTabsWhere(predicate).
Assisted-by: Claude:claude-opus-4-8:Claude Code
The WPF host attached a registry-driven context menu to the search list
(ContextMenuProvider.Add) and showed a tooltip on every result column;
the Avalonia port carried neither. Right-clicking a result now opens the
same menu the trees use -- the selected result's entity is handed to the
entries via TextViewContext.Reference, so Analyze and the scope-search
entries light up -- and the Location/Assembly cells regain their
full-text tooltips (the Name cell already showed the file path per
Fix#1263).
Assisted-by: Claude:claude-opus-4-8:Claude Code
The TryGetExport consolidation missed five more copies of the
GetExport-or-null idiom in the assembly-list pane and analyzer tree view
(TryGetLanguageSettings / TryGetContextMenuRegistry /
TryGetAnalyzerTreeViewModel plus an inline OpenNodeInNewTab guard).
Route them through AppComposition.TryGetExport like the rest.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The classic-desktop lifetime cast for the dialog owner, the clipboard and
application shutdown was repeated across the file commands, file pickers,
project export, the PDB selector, the taskbar service and the analyzer
copy entries. Replace those copies with a single UiContext exposing
MainWindow, Clipboard and Shutdown.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The explorer.exe / open / xdg-open switch for opening a folder, revealing
a file, or opening a file with its default app was copied across eight
commands (export, save, PDB, diagram, package extraction, CFG diagram).
Replace those copies with one ShellHelper exposing OpenFolder, RevealFile
and OpenWithDefaultApplication, and route every site through it.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The decompiler text view's lastTooltipSegment was assigned on hover and
cleared on close but never read anywhere, so it tracked nothing. Remove
the field and its three assignments.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The WPF host carried two ILSpy.csproj build steps the Avalonia port left
behind. ApplyStackExtension runs editbin /stack:16777216 on the apphost so
deeply nested types/expressions don't overflow the 1 MB main-thread stack
during decompilation (the managed runtime can't grow the main thread's
stack at run time on Windows). SortResX keeps the .resx entries sorted so
localisation diffs stay clean; its script was still in BuildTools but no
target invoked it.
Both are gated to do nothing off Windows / without the MSVC tools / on CI,
so they only act in the same local-Windows scenario they did before. The
VC-tools props import is restored alongside, since it is what populates
$(VCToolsVersion) for the editbin gate.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Eleven call sites each wrapped AppComposition.Current.GetExport<T>() in a
try/catch that returned null, to survive design-time previews and minimal
test hosts where the composition host isn't up. Replace those copies with
one AppComposition.TryGetExport<T>()/TryGetExports<T>() and inline it at
every site, deleting the per-class wrappers.
The shared helper does not blanket-swallow: host absence is checked
explicitly (the static field, not the throwing Current), and the built-in
TryGetExport(out) reports a missing export as false. So an export that is
present but genuinely broken now surfaces its activation error instead of
being silently turned into null.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The File-menu entry was a stub that popped a "not implemented" dialog,
while the working generation logic lived only in the assembly context-
menu entry. Lift that logic into a shared PdbGenerator and route both
entry points through it, so the menu command now generates PDBs for the
selected assemblies (enabled only when the selection holds a valid one).
This was the last NotImplementedDialog caller, so the dialog is removed.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The reflection-driven column builder turned every public property into a
column, so each {Column}Tooltip companion (NameTooltip, FlagsTooltip,
...) rendered as its own raw column next to the column it describes. That
text is already surfaced as the sibling cell's hover tooltip by
MetadataCellTooltip, which reflects the property off the row directly, so
dropping these properties from the column set leaves the tooltips intact
and removes the duplicate columns.
Assisted-by: Claude:claude-opus-4-8:Claude Code
DockWorkspace keeps the last remaining document un-closeable (CanClose
false) so the document area can't be emptied, and Dock's own close
button already honours that. The context-menu Close entry did not, so a
right-click still offered to close the final tab. Bind its IsEnabled to
the tab's CanClose, which is observable, so it tracks tabs opening and
closing.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The document tab strip's multi-row layout and the mouse-wheel gesture
that toggles it were only reachable by the wheel itself, with no
discoverable UI and no way to opt out of the gesture. Surface both as
checkboxes under a new "Tab options" group on the Display page, and add
a MouseWheelTogglesTabStripRows setting (default on) so the wheel toggle
can be turned off while keeping the persisted multi-row choice.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Clone / Rename / Delete / Select act on the selected list but were always
enabled, so clicking them with nothing selected silently no-op'd (the
handlers already guarded on SelectedListName). Bind their IsEnabled to the
list's selection via ObjectConverters.IsNotNull so they disable until a list
is picked; New / Reset / Add-preconfigured / Close stay enabled.
Assisted-by: Claude:claude-opus-4-8:Claude Code
SharpTreeView is a ListBox with AutoScrollToSelectedItem left at its default
(true), so it chased the selected row whenever its index shifted: expanding an
unrelated node pushed the selection off-screen and the ListBox yanked the
viewport back to it, fighting the expand's own reveal -- the "weird scrolling".
The rule is that a user mutating a control directly should not have the app
mutate the view too; only navigation from a *different* control (search
results, code/metadata links, analyzer nodes, Back/forward) may sync the tree.
The app already does that sync explicitly through the model
(TreeSelectionBinder -> CenterNodeInView), so the ListBox's own auto-scroll is
redundant and wrong for in-tree actions. Disable it.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The search panel rendered hits in a bare ListBox with no headers, so the only
ordering was the fixed fitness/name streaming order chosen at search time --
the user could not re-sort by Name, Location or Assembly. Replace the ListBox
with the app's standard sortable Avalonia DataGrid (mirroring OpenFromGacDialog
/ MetadataTablePage): three template columns that keep the per-field icons,
each with a SortMemberPath so clicking a header sorts by Name / Location /
Assembly. No initial column sort is applied, so the default fitness ranking is
preserved until the user clicks a header.
The code-behind is unchanged: ListBox and DataGrid share the
SelectingItemsControl surface, so the existing selection / activation /
keyboard / middle-click handlers port as-is (verified by the existing search
niceties tests now running against the DataGrid).
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Export Project/Solution, Save Code and Compare tests decompiled whatever
the default assembly list seeds -- System.Linq, CoreLib, the Uri assembly --
so each spent ~10s purely on type count (Solution/CreateSolution/SaveCode/
DecompileAssembly ~10s, CompareView ~4.5s).
Add FixtureAssembly, which emits a minimal two-type assembly via
PersistedAssemblyBuilder, and point these tests at it. They still exercise the
full project/solution writer, save-to-file and compare-tree pipelines, but
each now runs well under a second (e.g. Solution_Mode 10.8s -> 0.3s,
CreateSolution 10.8s -> 0.2s, ShowIdentical 4.5s -> 0.3s).
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Pdb2Xml command (ILSpy) and the PDB round-trip tests (Decompiler.Tests)
reference Microsoft.DiaSymReader*, previously gated on the build host being
Windows. That made dotnet restore resolve a different graph on Windows than
on Linux -- the packages (and their transitive tail: DiaSymReader.PortablePdb,
the legacy NETCore.Platforms/Win32 packages) appear only on Windows, and
DiaSymReader.Native flips between Direct and CentralTransitive. So a checkout
could not be developed across OSes without the committed packages.lock.json
churning on every Windows restore.
Drop the OS gate (keep Debug-only) so the restored graph, and the committed
lock, are identical on every OS. The consuming code is still gated by DEBUG
and WINDOWS, so on non-Windows the packages are restored but never compiled
in; the native asset only resolves for win-* RIDs.
The "Verify package contents" step (which checks the committed *.filelist
snapshots still match the built VSIX/MSI contents) had been excluding
packages.lock.json from its git diff to tolerate that per-OS churn. With the
locks now identical across OSes the carve-out is unnecessary, so it goes back
to a plain git diff --exit-code.
Assisted-by: Claude:claude-opus-4-8:Claude Code
ICSharpCode.Decompiler.Tests builds for the host RID (so the native
Microsoft.DiaSymReader.Native assets the Windows PDB tests need are copied),
which made its packages.lock.json host-specific (linux-x64 vs win-x64) and
drift between Linux and Windows restores. Add an explicit <RuntimeIdentifiers>
list next to the single <RuntimeIdentifier>: the lock then records every RID
host-independently while the build still targets the host RID. Apply the same
list to the TestRunner it pulls in.
TestRunner's lock is now fully portable. The test project's lock still carries
the OS-conditional DiaSymReader.Native difference in its base graph, which the
*.filelist-scoped "Verify package contents" step already tolerates.
Assisted-by: Claude:claude-opus-4-8:Claude Code
create-filelists.ps1 filtered to bin\Debug with `-contains` (collection
membership, always false for a substring test), so it skipped every artifact;
drop the broken filter. Point the installer ProductIcon at the relocated
ILSpy.ico (Images -> Assets) and regenerate the committed .filelist snapshots.
Assisted-by: Claude:claude-opus-4-8:Claude Code
publish.ps1 now takes a -Configuration parameter (the workflow passes the
GNU-style --configuration <Debug|Release>, which pwsh binds to it) and uses
it for both the -c flag and the output paths. The win-x64 framework-dependent
bundle is produced for every configuration; the win-arm64 framework-dependent
and win-x64 self-contained bundles stay Release-only, matching what the zip
steps consume.
The build workflow publishes per-configuration before zipping, and the
framework-dependent binaries zip now reads straight from the publish output
(...\win-x64\publish\fwdependent) for the active configuration instead of
globbing the build output.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Replace the Windows-only .bat helpers (clean / debugbuild / releasebuild /
restore / updatedeps and BuildTools/format) with cross-platform pwsh
scripts at the repo root: restore.ps1, build.ps1 (-Configuration), clean.ps1,
updatedeps.ps1 and BuildTools/format.ps1, alongside the existing publish.ps1.
Enable a packages.lock.json for every project by hoisting
RestorePackagesWithLockFile into the root Directory.Build.props (the four
core libraries set it individually before) and commit the generated locks,
so restores are repeatable and CI can cache packages off them.
Cache the NuGet packages folder in the three setup-dotnet workflows
(build-ilspy, build-frontends, codeql-analysis), keyed on the lock files
per the setup-dotnet caching guidance.
Scope the Debug "Verify package contents" check to the *.filelist outputs
it actually generates. A project's packages.lock.json is keyed only by
(framework, RID), with no host-OS axis, so a lock produced on Linux
legitimately differs from one produced on Windows whenever an OS-conditional
PackageReference applies (Debug+Windows pulls Microsoft.DiaSymReader*). The
Windows restore then rewrites those locks; that churn must not fail a step
whose job is to police the VSIX/MSI file lists.
Also drop the dead ILSpy.BamlDecompiler publish line from
publishlocaldev.ps1, mirroring the earlier publish.ps1 fix.
Assisted-by: Claude:claude-opus-4-8:Claude Code
publish.ps1 is a WPF-era script that was never updated for the current
tree, and the "Publish x64/arm64" CI step aborted on two stale things:
- It published ./ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj, which
does not exist here (the BAML library is ICSharpCode.BamlDecompiler and
already ships transitively via the ILSpy dependency graph) -> MSB1009.
- It publishes the ReadyToRun plugin RID-specifically without restoring
again, but the plugin declared no RuntimeIdentifiers, so the RID-less
solution restore produced no win-x64/win-arm64 assets -> NETSDK1047.
Drop the dead BamlDecompiler lines and give the plugin RuntimeIdentifiers
mirroring ILSpy.csproj, so it restores and publishes for every platform
the host supports (the plugin is pure managed and RID-agnostic).
Assisted-by: Claude:claude-opus-4-8:Claude Code
Keep the headless UI tests from timing out on the slow Windows runner:
decompile a small CoreLib type (System.Object) in the folding and
token-history tests instead of a large one, and raise the shared
decompile-wait timeout so a cold first decompile (JIT plus building a
CoreLib-scale type system) finishes in time.
Assisted-by: Claude:claude-opus-4-8:Claude Code
XmlDocLoader's modern-.NET fallback built the ref-pack path from the EXACT runtime
patch version (shared/Microsoft.NETCore.App/10.0.8 -> packs/Microsoft.NETCore.App.Ref/
10.0.8/ref), but the targeting (ref) pack version usually differs from the installed
runtime patch -- e.g. pack 10.0.0 -- and a relocated CI install (DOTNET_INSTALL_DIR)
may carry only a different feature band. The exact-version lookup then missed and the
provider came back null, leaving CoreLib hovers undocumented. Broaden the search:
prefer an exact match, then the newest pack sharing the runtime's major.minor, then
the newest pack present.
Assisted-by: Claude:claude-opus-4-8:Claude Code
NavigationHistory.Record's 0.5s debounce collapsed ANY two selections inside the
window -- including two DIFFERENT nodes -- so a navigation whose decompile finished
quickly (cheap targets, or a fast machine) never pushed the previous node onto the
back stack, silently losing history. Its real purpose is only to swallow the
double-fire a single click produces (SelectedItems.CollectionChanged + SelectedItem
PropertyChanged, same node) and tree-refresh re-selects, so gate the collapse on the
rapid entry being equal to the current one; a distinct node now records normally.
The view-state/navigation tests navigate between two cheap namespace nodes (a C#
namespace decompiles to just its "// Some.Name.Space" line) instead of decompiling
CoreLib types in full -- fast enough for the headless 15s budget on slow CI runners,
and a direct regression test for the collapse bug above.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Three comments described the old WPF host: the NoWarn rationale and the
DiaSymReader-gating note in ILSpy.csproj, and the no-[Shared] note in
DecompilerSettingsViewModel. Reword them to describe the current
System.Composition / build behavior directly. No code or suppression changes;
the TomsToolbox.Composition.Analyzer (MEF002/MEF004 source) stays.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Avalonia app targets net10.0, so dotnet build emits ILSpy/bin/<config>/net10.0/,
but the framework-dependent zip still globbed a net10.0-windows folder that does not
exist, so it captured nothing. Point that zip at net10.0. The self-contained and arm64
zips keep net10.0-windows because that is the explicit -o path publish.ps1 (and the
installer/VSIX) write the published output to.
Assisted-by: Claude:claude-opus-4-8:Claude Code