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
.gitattributes marked *.cs as `text` with no `eol=`, so the checked-out line
ending was left to each machine's git config. On the CI runner the .cs come out
LF, but .editorconfig sets end_of_line=crlf, so dotnet-format's --verify-no-changes
flagged every line of every file and the Format check step failed repo-wide
(independent of code formatting). Pinning eol=crlf makes the checkout match the
editorconfig on every platform. Blobs stay LF; only the working-tree EOL is fixed.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Make explicit what was only implied: the pre-commit hook IS the formatter, so
always let it run and never commit .cs with --no-verify (bypassing it is what
lets unformatted code accumulate). Scope the "new files must be CRLF" rule to
Windows -- on Linux/macOS that fights the repo's text=auto LF storage and makes
the whole tree look phantom-modified. Update the test example to the
Microsoft.Testing.Platform syntax the repo now pins. The parity-tracker line is
dropped (that tracker is on its way out).
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Windows build workflow had several run steps on one very long line --
the framework-dependent 7z zip with six glob paths, the restore/build/test
invocations, and the nuget push calls -- which were hard to scan and review.
Reformat them: single-command steps use a YAML folded scalar (run: >) with
one argument group per line, and the long lines inside the nuget-push |
blocks use PowerShell backtick continuation. The folded scalars rejoin to
byte-identical command strings, so runtime behavior is unchanged. No step
ordering, conditions, or the net10.0-windows publish paths are touched.
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
The list-root node had no Decompile override, so selecting it produced
empty output. Restore the behaviour of emitting a "// List:" header and
each contained assembly under a comment rule.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The previous version's About page carried a Check-for-updates / Download
button and an "automatically check for updates every week" toggle. Embed
both back into the About output: the button reuses the toolbar banner's
shared UpdatePanelViewModel (same check, same state), and the toggle is
two-way bound to UpdateSettings.AutomaticUpdateCheckEnabled.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The previous version let you pick the theme from a dropdown on the Display
options page; the Avalonia port only exposed theme switching via the menu.
Bind a ComboBox to SessionSettings.Theme -- the same property the menu's
SetThemeCommand sets and ThemeManager applies live -- so the page switches
the theme too, and resetting Display defaults restores the default theme.
Assisted-by: Claude:claude-opus-4-8:Claude Code
AvaloniaEdit's copy puts only plain text on the clipboard, so pasting into
an HTML-aware target (a document, mail, chat) lost the colours the previous
version preserved. Build a coloured HTML fragment from the selection that
merges BOTH the xshd syntax highlighter and ILSpy's semantic RichTextModel
(the decompiler's reference / theme colours the xshd alone doesn't carry),
and place it alongside the text under the native HTML format -- text/html on
Linux/macOS, CF_HTML on Windows. Wired to Ctrl+C (tunnel, so it pre-empts
the plain-text copy) and the right-click Copy entry.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Port the mixed IL/C# disassembler language that the previous version
shipped: it disassembles IL with the decompiled C# source interleaved
above each instruction (mapped via sequence points) as gray comments. The
language was missing entirely from the Avalonia tree, though it is a
Release-visible entry in the language dropdown.
Assisted-by: Claude:claude-opus-4-8:Claude Code
A member that decompiled to a very large amount of text could hang or
exhaust memory on the UI thread with no escape. Cap display output at five
million characters and, on overflow, show a message offering to display
anyway (at an extended limit) or save to disk.
Also bridge three Display options that were persisted but never reached the
decompiler -- brace folding, debug-symbol info, and the indentation string
-- so toggling them once again affects the generated source.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The search already stopped accepting results at 1000 and cancelled the
producer, but the user was never told the list had been truncated. Append
the "Search aborted, more than 1000 results found" row (a no-op to click).
Assisted-by: Claude:claude-opus-4-8:Claude Code
An on-demand resolved dependency had no way to be promoted into the
assembly list, so it was discarded on the next list reload. Restore the
context entry that clears IsAutoLoaded and re-saves the list.
Assisted-by: Claude:claude-opus-4-8:Claude Code
FindTreeNode only handled entity references, so activating an assembly,
resource, or namespace search result -- and clicking a resource or
namespace link -- silently did nothing. Restore the non-entity cases
(LoadedAssembly / MetadataFile / Resource / namespace) the previous
version handled.
Assisted-by: Claude:claude-opus-4-8:Claude Code
TestPlugin was still WPF (net10.0-windows, UseWpf, System.Windows,
TomsToolbox, the old ICSharpCode.ILSpy namespace), so it could not load
into the cross-platform app. Re-target it to net10.0 and map each
extension point onto the port's contracts: the custom Language, About-page
addition, context-menu entry, main-menu/toolbar command, and an Avalonia
options page whose view the ViewLocator resolves by naming convention. The
command needs [ImportingConstructor] for System.Composition to satisfy its
service dependency. Reference it build-only from the tests (no DLL copied
into the test output) so a composition test can load it without the
headless app auto-loading the plugin into every other test.
Assisted-by: Claude:claude-opus-4-8:Claude Code
A plugin whose assembly won't load, or an exported command whose
constructor can't be satisfied, previously either blanked the app into the
startup error window or threw mid-build and took the whole menu/toolbar
down with it. Collect such failures in a CompositionErrors sink instead,
skip the offending part, keep running, and show the errors in a document
tab via an ITextOutput once the window is up. Add a Composition log
category for opt-in file logging.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Rename Avalonia.Xaml.Behaviors to its successor Xaml.Behaviors.Avalonia
(12.0.0.1), and bump Dock (12.0.0.2), Svg.Controls.Skia.Avalonia,
AvaloniaUI.DiagnosticsSupport, CliWrap, coverlet.MTP, and NUnit to their
latest releases.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The document tab strip can now flow tabs onto multiple rows or stay a
single scrolling row; the mode is toggled by the mouse wheel over the
strip and persisted in SessionSettings. In single-line mode an overflow
dropdown at the strip's trailing edge lists every open document for quick
switching. Popups render in the window overlay layer (OverlayPopups) so
the dropdown's menu doesn't open as a native X11 child window that loses
focus and self-dismisses before it becomes visible.
Assisted-by: Claude:claude-opus-4-8:Claude Code
#3753: when many documents are open, the tab strip scrolls a single row,
hiding most tabs behind the overflow scroller.
Dock's DocumentTabStrip hardcodes a horizontal StackPanel inside a
PART_ScrollViewer in its theme template and ignores an ItemsPanel set via
a Style, so a MultiRowTabStripBehavior sets the WrapPanel ItemsPanel
directly and disables the scroll-viewer's horizontal scrolling, giving
the WrapPanel a bounded width so tabs wrap onto new rows. Enabled on the
document strip (not tool tabs) from App.axaml.
Drag-reorder across rows is not yet validated (Dock's drop-position logic
assumes a single horizontal strip), so this stays an opt-in prototype for
now rather than a closing fix for the issue.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Project/solution export, PDB generation, Create Diagram and Extract
Package all ran via RunWithCancellation on the active preview tab --
whose cancellation token tree-node navigation cancels. So selecting any
node while one of these ran killed it: you couldn't browse while an
export was in progress.
Add DockWorkspace.RunInNewTabAsync, which opens a new frozen document
tab (its own decompile/cancellation scope, never the navigation target),
runs the work there, and shows the report in it. Route the long-running
command sites through it. A frozen tab is immune to the selection-driven
re-decompile, so navigation can no longer cancel the work -- and the
report opens in its own tab instead of replacing the current view.
The single-node Save Code path intentionally stays on the active tab
(matches the prior version and is quick).
Assisted-by: Claude:claude-opus-4-8:Claude Code
Right-click Save Code on a node without a Save() override (type,
namespace, member) did nothing: the context-menu entry only called
node.Save() and stopped when it returned false, unlike Ctrl+S which
falls through to a generic decompile-to-file save. The prior version
always had this fallback in both paths; the port dropped it from the
context entry.
Extract the shared "Save() else decompile the node to a single file"
flow into SaveCodeHelper and route both Ctrl+S and the context-menu
entry through it, targeting the clicked node. As before, the fallback
runs in the active decompiler tab with its progress/cancel overlay and
then shows a "decompilation complete" breadcrumb with an Open-folder
button; the default file name derives from the node's text.
Assisted-by: Claude:claude-opus-4-8: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 freeze work pointed Dock's DocumentContextMenu (which it shows on a
document-tab right-click, overriding the standard ContextMenu) at a menu
holding only "Freeze tab". That hid the Close / Close all but this /
Close all entries defined in App.axaml, and on a frozen tab — where the
Freeze item is hidden — the whole menu came up empty.
Build the full menu (Close / Close all but this / Close all, plus the
preview-only Freeze entry) in PreviewTabContextMenuBehavior and drop the
now-superseded App.axaml ContextMenu setter, so there is a single source
for the document-tab menu.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Selecting a list in the dialog only set SessionSettings.ActiveAssemblyList
directly, which merely persists the choice for the next launch — it never
reloaded the tree, so the active list appeared unchanged. Route selection
through AssemblyTreeModel.ActiveListName instead, whose setter reloads the
list (OnActiveListNameChanged -> ShowAssemblyList) and persists the choice.
Also make double-clicking an entry a shortcut for the Select button (both
go through the same activation path).
Assisted-by: Claude:claude-opus-4-8:Claude Code
The metadata grid's double-tap handler activated grid.SelectedItem, so
rapidly clicking a column-header sort button registered as a double
click and navigated to the selected row. Resolve the row from the
double-tapped element instead and activate only when the double-click
actually landed on a data row.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The command always built a decompiler tab and force-decompiled the
node, so a metadata-table (or resource) node opened an empty code tab
instead of its grid. Route a single node through OpenNodeInNewTab, which
honours the node's CreateTab() custom content and falls back to a
decompiler tab for ordinary nodes; multi-node selections keep the
decompile-the-union behaviour.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The tree node's text leads with the table's token-kind byte (e.g.
"02 TypeDef (1234)"), but the opened tab's title dropped it and showed
just "TypeDef (1234)". Lead the tab title with the same byte so the two
match.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Jumping to a token (the "Go to token" entry and token-cell clicks) set
suppressHistoryRecording while selecting the target table, so the jump
left no back-stack entry and Back skipped over it. Stop suppressing so
the destination is recorded, and carry the target row on the entry
(TreeNodeEntry.MetadataRow) so Back/Forward restores the exact token
rather than the top of the table.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Commands and context-menu entries kick off async work as `_ = SomeAsync()`,
discarding the task. When one faulted, the failure only surfaced via
TaskScheduler.UnobservedTaskException after the GC finalized the dropped
task -- late, on the finalizer thread, and disconnected from the gesture
that caused it (a click would appear to do nothing, then a mysterious
crash report arrived seconds later).
Add a HandleExceptions() extension that observes such a task on the UI
context and routes any fault to the existing GlobalExceptionHandler at
the point of failure (treating cancellation as a normal outcome), and
route the user-invoked command/entry sites through it. The global
unhandled-exception net already existed; this just makes these failures
prompt and attributable instead of deferred to GC.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The quick Save Code path exports a project (one assembly) or a solution
(several) straight through a file picker, with no way to tune the
output. Add a dedicated, discoverable "Export Project/Solution..." entry
(File menu + assembly context menu, alongside Save Code) that opens a
configuration dialog: pick the output folder, preview the projects that
will be written (with invalid / duplicate-name / PDB-eligible badges),
toggle project-format and decompiler options, optionally sign with a
strong-name key, and optionally emit a portable PDB per assembly --
defaulting source-embedding off since the project's .cs are on disk.
The export runs on a clone of the live settings (never the persisted
instance) behind the tab's cancellable progress UI and reports into the
active decompiler tab. StrongNameKeyFile now flows through
DecompilationOptions into the project writer, which had no reachable
setter before. The engine (ProjectExporter) and the preview computation
are split out from the window so they are headless-testable.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The portable-PDB writer carried its knobs (no-logo, pdb id, progress,
progress title) as a growing list of optional WritePdb parameters.
Turn the type into a configured instance whose options are properties,
and add EmbedSourceFiles (default true): when a PDB is generated next
to a project export whose .cs are already on disk, embedding the source
again is redundant, so the caller can turn it off. The per-document
checksum/hash is computed either way, so documents still resolve.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The single-assembly project save was already wired, but selecting
multiple assemblies and choosing Save Code only ever saved one of
them: SolutionWriter had never been ported, so there was no path that
emitted a .sln with one decompiled project per assembly.
Port SolutionWriter UI-agnostically and route both Save Code surfaces
(the context-menu entry and the Ctrl+S command) through a shared
SolutionExport helper that recognises a multi-assembly selection,
prompts for the .sln path, runs the export behind the tab's cancellable
progress UI, and reports the result into the active decompiler tab
(matching how the other long-running commands surface their output).
Unlike the prior app, the project-export path writes the .csproj to
disk itself and only returns a breadcrumb through the text output, so
each project's on-disk file is located after decompiling rather than
captured from the output stream.
Assisted-by: Claude:claude-opus-4-8:Claude Code
These PackageVersion entries had no PackageReference anywhere in the tree -- WPF-era leftovers (TomsToolbox.Wpf.*, Microsoft.Xaml.Behaviors.Wpf, AvalonEdit, Dirkster.AvalonDock, DataGridExtensions), unused theme/diagnostics variants (Avalonia/Dock Fluent themes, Avalonia.Diagnostics, Fonts.Inter), the VSTest SDK (the suite runs on Microsoft.Testing.Platform), and bare System.* versions that pin nothing under central package management. Restore leaves every packages.lock.json unchanged, confirming none were in use.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Finishes the decompiler text-view context menu: the remaining tree-only entries now also act on a right-clicked symbol (IEntity). Decompile to new tab opens the entity's definition in a fresh tab -- which required honouring the previously-dead InNewTabPage flag on NavigateToReferenceEventArgs (OnNavigateToReference now opens the resolved node via OpenNodeInNewTab). Scope search to assembly/namespace read the entity's ParentModule.AssemblyName / Namespace, matching the existing inassembly:/innamespace: filters.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The decompiler text-view context menu was missing the navigation and folding entries the previous version had. Add Decompile (go to definition) on a right-clicked symbol -- it navigates the tree to the entity's definition via the existing NavigateToReference bus message -- and Toggle folding / Toggle all folding, which reuse the editor's existing Ctrl+M logic (extracted into public DecompilerTextView methods). The folding entries only show when the document has foldings.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Avalonia has no global ICommand re-query signal like WPF's CommandManager.RequerySuggested, so state-dependent main-menu commands were frozen at their startup CanExecute: a NativeMenuItem only re-reads CanExecute when the command raises CanExecuteChanged, and nothing did. The assembly list is empty when the menu is built, so Clear assembly list was disabled forever; Save and Remove-assemblies-with-load-errors had the same latent bug. Add a weak global CommandManager (held weakly so menu/toolbar items aren't pinned) that SimpleCommand routes CanExecuteChanged through -- exactly as WPF's SimpleCommand routed it -- and invalidate it at the central state-change points: tree selection, the active assembly list, and background-load-sweep completion (so load errors surface). One signal, every platform: NativeMenuItem maps CanExecute onto IsEnabled, which the macOS Cocoa, Linux DBus, and in-window menu exporters all track.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The decompiler text-view context menu only read SelectedTreeNodes, so Analyze never appeared when right-clicking a reference in the code. Read the resolved entity from context.Reference too (the same seam Show-in-Metadata uses), so right-clicking a type/member in the decompiled output offers Analyze and pushes it into the analyzer pane.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Right-clicking a document tab opened an empty menu. Add Close, Close all but this, and Close all, bound to commands on ContentTabPage that delegate to DockWorkspace. Close all now keeps the persistent preview/home tab (the One) rather than leaving whichever tab Dock's last-dockable veto happened to spare, so the app always retains a main tab to project the next selection onto.
Assisted-by: Claude:claude-opus-4-8:Claude Code
SharpTreeView.SelectedItems is IList?, so reading .Count on the realised grid raised CS8602. Null-forgive the dereferences in the Ctrl+A and plain-click selection tests, where the grid is always realised.
Assisted-by: Claude:claude-opus-4-8:Claude Code
TreeKeyboardController (and its ITreeKeyboardTarget interface) drove keyboard gestures on the old ProDataGrid trees, including a reflection workaround for the grid's shift-range selection that only added and never shrank a range. Both trees are SharpTreeView (ListBox) now -- which handles those gestures natively in SharpTreeView.OnKeyDown and gets anchor extend/shrink for free -- so nothing constructs the controller. Remove it and the now-vestigial interface implementations on the two tree view-models.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Two implemented tree gestures had no coverage (flagged by a gesture audit): middle mouse button on a row opens it in a new document tab, and Ctrl+R analyzes the selected member into the analyzer pane.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Analyze dropped the entity's node into the tree collapsed, so the user had to expand it every time to see its analyzers (Used By, Uses, ...). Expand the freshly-added node on creation; re-analyzing an existing entity returns the same node without re-expanding it.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The startup welcome page renders the same About content in the reusable main tab. Help > About opened a second, static About tab beside it, so the user saw the page twice. Track the welcome content and, while it is still the live main-tab content, have Help > About activate it rather than open the singleton. Falls through to the singleton once a tree-node selection has replaced the welcome page.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Center-on-select reveals a navigated-to node centred in the viewport, but it was also yanking rows the user can already see: opening a visible row in a new tab (or any model-driven selection) pulled it to the middle. The skip cannot be decided from post-selection geometry because AutoScrollToSelectedItem first drags an off-screen row to an edge, making it look visible. Snapshot visibility in TreeSelectionBinder.SyncModelToTree before the selection changes: an already-visible primary is focused without scrolling, while a genuinely off-screen one (Back navigation, go-to-definition) is still centred.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Simple theme drew keyboard focus as a dotted, fill-less adorner that vanished outright once a context menu took focus, and right-click versus keyboard invocation marked the target inconsistently. Render focus as a selection-coloured fill with a dotted darker-blue border via a FocusAdornerTemplate, and reuse the same visual as a ContextTargetVisual template part for the right-click target. A keyboard-invoked menu (Shift+F10 / Apps) now marks the focused row and restores its focus and adorner on close, since Avalonia's ContextMenu never restores focus itself.
Assisted-by: Claude:claude-opus-4-8:Claude Code
ScheduleBackgroundLoadSweep waited for TreeReady and then an extra unconditional
5s before populating sibling-assembly icons -- an artificial wall-clock delay on
top of the TreeReady gate. Fire the sweep as soon as the tree is on screen; the
TreeReady gate already keeps it off slow startups and the SemaphoreSlim(4)
throttle still bounds the load storm.
Reopening_About_Reuses_The_Same_Tab matched any tab whose Title is "About", but
two tabs can carry that title: the singleton menu-About (IsStaticContent: true,
opened via OpenSingletonTab) and the transient boot welcome page (ShowWelcome,
IsStaticContent: false, a non-static main-tab page shown when nothing is
restored). The welcome page's presence races with boot, so when it lingered the
test's .Single(IsAbout) saw two tabs and threw "Sequence contains more than one
matching element" -- a ~50% order-dependent flake.
The singleton is what the test means to verify, so match it precisely
(IsStaticContent: true). OpenSingletonTab/CloseDockable reuse was always sound;
this was purely an imprecise test predicate. Deterministic now (5/5 full-suite
runs green; was ~50%).