Selecting a C# version, switching to a version-less language (IL), then back to
C# left the toolbar's version ComboBox blank even though LanguageService had
restored CurrentVersion. The MVVM-toolkit setter runs OnCurrentLanguageChanged
(which assigns CurrentVersion, pushing it onto the bound SelectedItem) before it
raises PropertyChanged for CurrentLanguage (which repopulates ItemsSource from
the new language's versions). So SelectedItem is set against the previous
language's still-stale, empty list, rejected, and never re-pulled once
ItemsSource updates.
Re-assert the version combo's SelectedItem from the bound CurrentVersion after
each ItemsSource swap settles, keeping the model untouched.
Assisted-by: Claude:claude-opus-4-8:Claude Code
CaretHighlightAdornerTests waited on the wall clock (3s budget) for the
adorner's 1s real-time DispatcherTimer to remove it. Avalonia.Headless has no
simulated clock, so under a loaded CI runner the dispatcher-thread timer plus
the poll loop slipped past the tight budget and the test timed out
intermittently.
Extract the teardown the lifetime timer runs into Dismiss(); the test grabs the
live adorner and calls it directly, verifying the unregister half without any
timing dependency. Registration is still exercised through the real
OnReferenceClicked path, and production behaviour is unchanged.
Assisted-by: Claude:claude-opus-4-8:Claude Code
These three project settings were present in the WPF build but lost in the
move to Avalonia. RollForward=major matters most: the default publish is
framework-dependent (publish.ps1 uses --no-self-contained) and the app targets
net10.0, so without it a machine that only has a newer major runtime installed
fails to start the app. DynamicAdaptationMode (DATAS) lets server GC shrink its
heap count to the live load for a smaller idle working set, and Debug-only
CheckForOverflowUnderflow traps arithmetic wrap during development.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Generate-PDB and assembly-node decompile failures reported only the exception
Message, which is too thin to act on: the runtime-async PDB-generation crash
surfaces as a bare "Object reference not set to an instance of an object" with
no indication of where it came from. Append the whole exception (type, message,
stack trace, inner exceptions) inside a default-collapsed "Exception details"
fold so the report stays scannable while the trace is one click away.
The fold span is counted in WriteLine() calls rather than embedded newline
characters, so the trace is emitted one line per WriteLine() -- a single
Write() of the multi-line string would collapse to one counted line and be
dropped as a single-line fold.
Also fixes the PDB tab title showing a literal "{0}": the GeneratingPortablePDB
resource is a format string whose placeholder was never filled.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Debug.Assert failures were routed straight to the single-button unhandled-
exception dialog and deduplicated by call site, so the only available action
was effectively "ignore". Restore the developer dialog the WPF host offered:
Throw raises the assertion as an exception, Debug breaks into the debugger,
Ignore continues, and Ignore All suppresses that call site for the session.
Asserts fire on the background decompiler thread, so the dialog marshals to the
UI thread and blocks the caller on a nested dispatcher frame until a choice is
made, matching how the WPF version stayed responsive.
Separately, cancelling a long-running operation tab left it blank; it now shows
"Operation was cancelled." so the frozen tab explains why it stopped.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Toggling a display setting that affects decompiler output (folding, member or
using expansion, debug info, IL detail, indentation) routed through
ForceRefreshActiveTab, whose ShowSelectedNode re-projects the tree selection
and so activates the preview tab -- yanking the user off whatever tab they were
on (e.g. the live Options page they were editing).
Add RefreshDecompilerOutputInPlace, which re-decompiles the cached decompiler
content directly without re-projecting or activating anything, and route the
Redecompile display-setting reaction to it. F5 reload and dependency-load keep
using ForceRefreshActiveTab, where re-projecting the selection is intended.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Project/solution export (and other RunInNewTabAsync work) opened a tab with a
static title and a permanently indeterminate progress bar, even though the
whole-project decompiler already produces per-file DecompilationProgress that
was being dropped.
Wire that progress through: DecompilationOptions carries an
IProgress<DecompilationProgress> that DecompileAsProject sets on the
WholeProjectDecompiler; a new RunInNewTabAsync overload hands the work a
UI-thread-marshaled Progress<T> feeding the tab. The tab now animates the
braille spinner over its title (the operation name) and shows a determinate bar
plus the file currently being written and an "N of M" count. Solution export
reports per-assembly rather than per-file, since its assemblies decompile in
parallel and per-file reports would race. The determinate bar is pinned to 75%
of the overlay width so it doesn't jitter as the status text changes.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Debug Steps pane previously only served the ILAst language (one node per
IL transform). The C# decompiler's intermediate AST states were instead
exposed as a row of extra "C# - after TRANSFORM" dropdown languages, which
cluttered the language list and only let you jump to one fixed step at a time.
Generalise the pane to an IDebugStepProvider interface: ILAst keeps its IL
steps, and the C# language now contributes one step per AST transform, mapping
a selected step onto options.StepLimit (already honoured by CreateDecompiler).
Each language also supplies its own options object, so the writing-options
checkboxes show for ILAst and nothing for C#. The per-transform dropdown
languages are removed in favour of 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 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
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
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
Restores the result-navigation gestures the pane shipped with before the
port: Ctrl+T/M/S jump the picker to Type/Member/Constant, Down/Up arrow
hand focus between the search box and the result list, and Ctrl+Enter or a
middle-click open a result in a new document tab instead of reusing the
active one. New-tab activation routes through the existing
OpenNodeInNewTab so it matches the tree's open-in-new-tab behaviour.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The metadata-grid hover infrastructure shipped but no table entry fed it,
so every cell tooltip the previous version had was silently gone: the
heap-offset/value hints on string and blob columns, the entity description
on token columns, and the per-bit breakdown on flag columns.
Reinstate all of them. FlagsTooltip renders the rich per-bit view (a
checkbox per flag plus the selected member of each mutually exclusive
sub-range) instead of a flat string; GenerateTooltip on the table base
describes what a token column points at; the remaining columns expose
their heap-offset and value hints. MetadataCellTooltip now hands a
FlagsTooltip back as a built control and stringifies everything else.
Assisted-by: Claude:claude-opus-4-8:Claude Code