The MEF block of the View menu used MenuOrder = 100/101 for Back/
Forward but no MenuOrder on Sort assembly list / Collapse all tree
nodes, so the latter two implicitly defaulted to 0 and ended up
appearing before Back/Forward despite their numerically larger
explicit order. The intent reads better the other way around:
Back/Forward are high-frequency keyboard-driven actions (Alt+Left/
Right) and belong adjacent to the show-radios block, while Sort and
Collapse are tree-operation bulk actions that belong further down.
Use the same non-overlapping MenuOrder ranges that File now follows:
Navigation: 0 .. 9 (Back, Forward)
Tree: 10 .. 19 (Sort, Collapse)
Options: 999 (Options)
Sort and Collapse moved from MenuCategory = "View" to "Tree" because
the category identifier should describe the operand -- both act on
the assembly tree, neither is a generic "view" action.
Assisted-by: Claude:claude-opus-4-7:Claude Code
FileCommands.cs already declared MenuIcon = "Images/AssemblyListGAC"
on the Open-from-GAC main-menu command, but the reflection lookup
in Images.cs found no field of that name so the menu item rendered
without an icon. Port the SVG asset from master/ILSpy/Images/ into
Assets/Icons/ and add the static IImage registration so the metadata
flow completes.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The File menu's MenuCategory metadata was inherited from the WPF tree
and showed two friction points: the four DEBUG diagnostics
(Decompile All / Disassemble All / Decompile 100x / Dump PDB) lived
in the Open category, so they appeared right after Reload with no
visual separator -- the menu read as if Decompile All was a variant
of Open; and the assembly-list mutators (Remove with load errors,
Clear assembly list, Manage assembly lists) were split across two
categories (Remove and Open) even though they all operate on the
same object.
Reshape into five categories with non-overlapping MenuOrder ranges
so category order is deterministic instead of falling out of MEF
discovery order:
Open (Open, GAC, Reload) MenuOrder 0 .. 9
AssemblyList (Manage, Remove, Clear) MenuOrder 10 .. 19
Save (Save Code, Generate PDB) MenuOrder 20 .. 29
Debug (DEBUG -- ...) MenuOrder 30 .. 39
Exit (Exit) MenuOrder 99999
The "AssemblyList" category replaces the previous "Remove" identifier
(carrying Remove-with-load-errors + Clear) since both members and the
newly-relocated Manage entry are list-of-assemblies operations rather
than pure removal. The category value is a string literal because it
is only ever used as a grouping key by ContextMenuProvider /
MainMenu.AppendRegistryCommands -- never displayed.
Stride-10 within each category gives room to insert later items
without renumbering. The earlier 2.5 / 2.55 / 2.6 / 2.7 fractional
scheme had no category-boundary contract: the existing 2.6 tie
between Remove-with-load-errors and Clear meant their order between
each other depended on whatever order reflection enumerated the
fields in. Generate portable PDB previously had no MenuOrder at all
so its position relative to Save Code was MEF-implementation defined.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Master's manual ILSpy/Properties/AssemblyInfo.cs was dropped during
the Avalonia port; the SDK's auto-generated AssemblyInfo took over
with default-zero versions. As a result the built ILSpy.dll's
assembly metadata read 1.0.0.0 even though DecompilerVersionInfo
(the static class generated from the .template by the build pipeline)
had the right values. Anything that queried Assembly.GetName().Version
or AssemblyInformationalVersionAttribute -- update checks, crash
report context, the title-bar version line we still need to wire --
got the SDK default, not the real revision.
Port the cross-platform-safe slice of master's AssemblyInfo.cs:
Title/Description/Company/Product/Copyright, ComVisible(false), the
two version attributes pointing at DecompilerVersionInfo,
NeutralResourcesLanguage, InternalsVisibleTo("ILSpy.Tests"), and the
CA2243 suppression. Deliberately skipped: [SupportedOSPlatform(
"Windows7.0")] and [TargetPlatform("Windows10.0")] (would re-make the
assembly Windows-only at metadata level and emit CA1416 noise for
cross-platform code paths), [InternalsVisibleTo("ILSpy.AddIn")] (the
AddIn project isn't in the avalonia build path), and the WPF-only
Properties/WPFAssemblyInfo.cs ThemeInfo (System.Windows-typed).
The single-line ILSpy/AssemblyInfo.cs stub that only carried the
ILSpy.Tests InternalsVisibleTo gets folded into the new file and
removed, matching master's layout.
GenerateAssemblyInfo=false in the csproj is the required companion --
without it the SDK emits a second AssemblyVersion attribute and
compilation fails with CS0579.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Both menu builders previously ignored the MenuIcon/Icon metadata on
ExportMainMenuCommand and ExportContextMenuEntry: the strings were
preserved through the WPF -> Avalonia port (~12 main-menu commands
declare MenuIcon, plus a handful of context-menu entries), but the
NativeMenuItem and MenuItem instances they fed into had no icon
assignment. Avalonia's toolbar already had a private path-to-image
reflection resolver; this lifts it into Images.cs as a shared helper
so the two menu sites can consume it too.
NativeMenuItem.Icon is Bitmap-typed (macOS NSImage is the platform
target and has no vector form), so the main-menu path rasterises
SVG icons through RenderTargetBitmap at attach-time -- one-time cost
per menu item at boot. MenuItem.Icon accepts any Control, so the
context-menu path keeps the IImage live inside an <Image> control
and preserves vector rendering.
SaveCodeContextMenuEntry and AssemblyTreeNode.ReloadAssembly were
missing Icon metadata even though their main-menu equivalents
declared MenuIcon. Adding Icon = "Images/Save" and "Images/Refresh"
respectively, matching the toolbar/menu sibling. Other context-menu
entries without Icon metadata are intentionally textless: most are
node-type-specific actions (Remove, Copy results, Search MSDN) where
no main-menu sibling exists to inherit an icon mapping from.
The App.axaml separator style selector "MenuItem Separator" only
matched separators nested inside a templated MenuItem (the main-menu
submenu case). ContextMenuProvider adds Separator directly to
ContextMenu.Items between Category groups, where there's no MenuItem
ancestor for the selector to match -- so context-menu separators
rendered with the Simple theme's default near-invisible style.
Extending to "MenuItem Separator, ContextMenu Separator" covers both
surfaces; toolbar and docking-chrome separators stay scoped out.
MenuIconWiringProbe walks the materialised NativeMenu tree post-build
and asserts File > Open has its Icon populated -- regression net for
the metadata-flow path.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The trailing HeaderedContentControl in the Display panel (Other -> Sort
results by fitness) sat 15 px below the ScrollViewer.Extent's reported
height: the outer StackPanel's measure under-counted that last child's
inner content, so MaxYOffset never grew enough to scroll the checkbox
into view. The Reset-to-defaults border below the ScrollViewer covered
the gap visually, making the checkbox look obscured.
ScrollViewer.Padding alone can't compensate because Avalonia 12's
Simple-theme ScrollContentPresenter collapses Padding on the axis the
scrollbar lives on -- the horizontal 6 px stuck, the vertical 6 px was
dropped. Wrapping the content in a Border with explicit Padding makes
the padding part of the StackPanel's measured extent, and the last
child becomes reachable. Applied to both panels that use a top-level
StackPanel under a ScrollViewer; Misc is unaffected (no ScrollViewer,
two checkboxes never overflow).
The new OptionsPageScrollReachTests.Last_Item_In_Display_Panel_Is_-
Reachable_At_Max_Scroll opens the Options tab, scrolls the Display
panel to ScrollViewer.Extent.Height, and asserts the "Sort results by
fitness" CheckBox's rendered bottom sits at or above the Reset border's
top in window-coordinate space.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Avalonia's Window.Icon doesn't reach the macOS Dock, so the app needs a
proper .app bundle: an ILSpy.icns produced from the existing artwork, an
Info.plist with CFBundleIconFile=ILSpy and CFBundleIdentifier
net.sharpdevelop.ilspy, and a BuildMacAppBundle target that runs
AfterTargets="Publish" for any osx-* RuntimeIdentifier. The target lays
out the standard Contents/MacOS + Contents/Resources structure under
bin/<config>/<tfm>/<rid>/ILSpy.app/ and chmods the apphost executable
so macOS LaunchServices will accept it.
The Avalonia resource glob ('Assets/**') is told to skip the bundle
inputs since they're consumed by the OS loader rather than at runtime
via avares://. The lock files pick up RID-specific runtime asset entries
that NuGet adds when an osx RID is in the project's RuntimeIdentifiers.
The slnf name was a leftover from when ILSpy.csproj was WPF; its
contents are now the Avalonia desktop projects. Drop the dead
ILSpy.BamlDecompiler.csproj ref (the directory no longer holds a
project after the engine moved to ICSharpCode.BamlDecompiler) and the
out-of-scope items (TestPlugin, ILSpy.BamlDecompiler.Tests) -- they're
in neither ILSpy.sln nor any active build flow, so the slnf filter
surfacing them in IDE listings was just noise.
Assisted-by: Claude:claude-opus-4-7:Claude Code
If anything thrown by App.OnFrameworkInitializationCompleted propagated
past it -- a CompositionFailedException from an unresolvable plugin
dependency, say -- the process died before the dispatcher pump started,
so the AppDomain.UnhandledException dialog wired by GlobalExceptionHandler
never had a chance to surface. The user saw a silent exit; the only
trace was a stderr stack on terminal launches and nothing at all when
clicking the icon. Wrap the critical resolution paths so anything that
goes wrong lands in StartupExceptions, and on any failure swap the
MainWindow for a StartupErrorWindow that displays the captured text.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The MainMenu UserControl previously built a regular Avalonia Menu of
MenuItems, which on macOS would render inline in the window instead
of in the system menu bar -- not what Mac users expect. Avalonia's
NativeMenu + NativeMenuBar is the cross-platform abstraction: on
macOS the menu is projected into the system bar, on Windows / Linux
NativeMenuBar's presenter renders the same items inline. The MEF
registry, theme submenu, tool-pane toggles, and dynamic tab list all
flow through unchanged; only the leaf widget type swaps from MenuItem
to NativeMenuItem. KeyModifiers.Control is translated to Meta on
macOS so the system menu bar shows Cmd glyphs instead of Ctrl.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Previous rule mandated "Avalonia: <lowercase phrase>" subjects, which
encouraged restating the area at the cost of subject-line budget. The
prefix has been stripped from all 336 branch commits; update the rule
so future commits match.
Assisted-by: Claude:claude-opus-4-7:Claude Code
R2R was the only legacy plugin still keeping the solution-level build red
(27 errors against the Avalonia tree). Port mirrors the four-corner
substitution used for the rest of the rewrite:
Assisted-by: Claude:claude-opus-4-7:Claude Code
When the user removes an assembly while a decompile is still in flight,
the title-refresh paths (spinner tick, post-Task.Run InvokeAsync,
StopSpinner) walk node.Text -> LoadedAssembly.Text ->
metadata.GetAssemblyDefinition().Version, reading directly from the
PE file's MemoryMappedFile. AssemblyList.Unload calls Dispose
synchronously and unmaps that file; any continuation that lands on the
UI thread after Dispose dereferences freed pages and the CLR reports
the AV through FailFastIfCorruptingStateException -- a catch block
cannot intervene in .NET 5+.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The four `WaitForDecompiledTextAsync` calls in PreviewTabPromotionTests
asserted only on dock structure -- SourceNode, IsPreview, tab count,
Content reference -- never on decompiled Text. Those values are set
synchronously by ShowSelectedNode before DecompileAsync's first await
returns, so the ~25s wait was pure dead weight. Worse, the fire-and-forget
DecompileAsync kept chewing CPU after the test returned and contended on
the ThreadPool with the next test's decompile, which is what pushed the
same workload from 12-16s in isolation to 24-28s in full-suite -- right
up against the 30s budget. One slow GC pause and PinCurrentTab_Flips
timed out at 30.997s exactly.
Mirrors the master-side addition in commit 82edef3bc (`CSharpLanguage.cs`
in the WPF host) so the Avalonia version stays in feature parity with the
shared decompiler's CSharp15_0 enum. The RuntimeAsync setting itself rides
in via the shared `ICSharpCode.Decompiler` library; only the dropdown entry
needed mirroring on the UI side.
Every_ExportToolbarCommand_Resolves_An_Icon used GetField against
ILSpy.Avalonia.Images.Images to verify every [ExportToolbarCommand]'s
ToolbarIcon resolves to a live IImage. Images.Images currently
declares its icons as static readonly fields, but a future refactor
could lazify them into properties (we explored that path earlier in
this session before reverting). Accept either FieldInfo or
PropertyInfo so the test keeps holding under that refactor.
Assisted-by: Claude:claude-opus-4-7:Claude Code
AppLog.cs flips the Startup category from default-on to default-off:
all categories now require an explicit opt-in via the ILSPY_LOG
environment variable (e.g. ILSPY_LOG=Startup) or a runtime
AppLog.Enable("Startup") call. Production launches stay silent and
incur near-zero overhead — IsEnabled short-circuits before any
allocation, the %TEMP%\ilspy-avalonia.log file is only created on
first emitted line.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The InitMainMenu build path already inserts Separators between
MenuCategory groups (Open / Save / Remove / Exit in File, View /
Navigation / Options in View, etc.). The Simple-theme default style
renders them as a 1px line at very low contrast — barely visible
against the menu background, which is why "no groups in the menus"
was a reasonable read.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Previously pin used an explicit 22x22 size, custom padding, and a
Button.preview-pin class with hand-rolled #33000000/#55000000 hover
tints. The close button (its sibling in the tab strip) is a plain
Avalonia.Controls.Button with a ControlTheme applied by Dock's tab
template — no classes, no local sizing. Visual mismatch was noticeable.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The ShowIdentical toggle was wired model-side
(CompareTabPageModel.OnShowIdenticalChanged → RootEntry.EnsureChildrenFiltered)
and view-side (Rebind on PropertyChanged), and the cascade was
correctly setting IsHidden via ComparisonEntryTreeNode.Filter. But the
HierarchicalModel's ChildrenSelector and SetRoots passed node.Children
through unfiltered — so IsHidden flags had no effect on what the
DataGrid actually rendered. Toggling the checkbox looked like a no-op.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The Language and Language-Version pickers in the main toolbar have no
effect when the active tab renders PE-header / metadata-table fields
straight from the metadata, or shows a structural compare diff —
language choice doesn't change what's drawn. Mirrors WPF's per-tab
TabPageModel.SupportsLanguageSwitching flag.
Assisted-by: Claude:claude-opus-4-7:Claude Code
VS-style preview-tab semantics, refined per user spec: pinning a tab
should freeze its contents in place but NOT immediately spawn a fresh
preview tab beside it. The new preview tab opens lazily, on the next
tree-node selection that finds the active tab frozen — at which point
the new content lands in a brand-new tab and the pinned one is left
untouched.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The sweep fires LoadedAssembly.GetLoadResultAsync on every sibling
assembly so users see icons "fill in" rather than each one showing
a loading state until first click. As written the sweep was firing
500 ms after TreeReady — which lands inside the first decompile's
Dispatcher.InvokeAsync window, triggering Server-GC pauses on the
UI thread during apply-text marshal-back. Measured: +460 ms FirstText
vs sweep-disabled on a single-type decompile workload.
Assisted-by: Claude:claude-opus-4-7:Claude Code
When --navigateto (-n) is supplied on the command line the user is
asking us to navigate to a specific entity. Today's code restores the
previously-saved tree path AND then runs the explicit target, which
produces two concurrent decompiles racing on SelectedItem (last write
wins). The saved decompile contaminates perf measurements taken via
`-n T:Some.Type` and adds GC pressure to startup for no user benefit.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The polymorphism modifier on the dock-layout JsonSerializerOptions
ran a fresh AppDomain.GetAssemblies() + SelectMany(GetTypes()) +
IsAssignableFrom walk for every polymorphic base type it built
JsonTypeInfo for — IDockable, IDock, IRootDock, IDockWindow,
IDocumentTemplate, IToolTemplate. Six independent passes over
~100 loaded assemblies, ~500 ms total on warm starts.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Workstation GC pauses every managed thread on Gen-1/2 collections,
so background allocations (decompiler work, layout deserialise,
search streaming) stall the UI thread for the duration of the pause.
Server GC spawns one heap per CPU core with dedicated GC threads;
collections of one heap don't pause the others. Concurrent stays on
so even server collections don't stop-the-world for long.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Preview tab semantics. ContentTabPage gains IsPreview; the persistent MainTab
starts preview (tree-node clicks replace its Content in place). The user pins
via Window menu, right-click context menu, or inline pin icon — the
just-pinned tab keeps its content/identity and a fresh preview MainTab spawns.
Carve-out tabs (Open in new tab, Options) are born pinned and survive tree
selections.
Assisted-by: Claude:claude-opus-4-7:Claude Code
DisplaySettings.SortResults was persisted but unused — RunningSearch hardcoded
ComparerByFitness. Now SearchPaneModel.RestartSearch reads the setting at
start-of-search and passes either ComparerByFitness (default) or ComparerByName
into RunningSearch. Capturing at start matches WPF — mid-run toggles only
affect the next search.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds TabPageMenuItem (mirrors ToolPaneMenuItem) and a live ObservableCollection
on DockWorkspace kept in sync with factory.Documents.VisibleDockables. MainMenu
appends a separator + radio-style MenuItem per tab, with Header bound to Title
and IsChecked bound to IsActive. WPF parity for the previously-skipped tabs
section of the Window menu.
Assisted-by: Claude:claude-opus-4-7:Claude Code
WPF's Open-from-GAC dialog uses a ListView with five SortableGridViewColumns
(Reference Name / Version / Culture / Public Key Token / Location), each
click-to-sort, with the initial sort by name ascending. The Avalonia port
had reduced this to a one-column ListBox showing the raw ToString() —
all field-level information mashed into a single TextBlock, no per-field
sort, no Location column, no localised strings.
Assisted-by: Claude:claude-opus-4-7:Claude Code
User-reported: editor hover tooltips were not wide enough to show full
method signatures. Root cause was a three-way layout interaction —
DocumentationRenderer.AddSignatureBlock created the signature
SelectableTextBlock with TextWrapping.NoWrap, the outer Border capped
MaxWidth at 600px, and the wrapping ScrollViewer disabled horizontal
scrolling. So anything past 600px was simply clipped, with no way to
reveal the rest.
Assisted-by: Claude:claude-opus-4-7:Claude Code
WPF parity: under DEBUG, the language dropdown gains one extra entry per
C# AST transform — "C# - no transforms", "C# - after FirstTransformName",
… "C# - after LastTransformName" — so a developer can pick a step and
see the decompiler's mid-pipeline AST output.
Assisted-by: Claude:claude-opus-4-7:Claude Code
User-reported: Ctrl+LMB on an assembly-tree row both toggled the row in
the multi-selection AND opened a new decompiler tab. ProDataGrid's
Extended-selection mode treats Ctrl+LMB as a toggle, and
OnTreeGridPointerPressed also treated it as "open in new tab", so every
Ctrl+click fired both effects regardless of the e.Handled=true on our
handler — selection lives in the DataGrid template's own pre-bubble
pipeline.
Assisted-by: Claude:claude-opus-4-7:Claude Code
User-reported: opening multiple document tabs and restarting brings them
all back broken except the first one.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The "Compare..." right-click flow opened a tab that came up blank because
two stacked bugs masked each other.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Build the layout and call InitLayout up front in DockWorkspace instead of
letting DockControl run it post-template-apply, and populate the locators
explicitly: tool panes map their id to the [Shared] singleton pane, the
Documents dock and main tab get entries for navigate-by-id, and
HostWindowLocator is registered so panes can float into their own windows.
Doing it before the chrome attaches keeps the layout's owner/factory
wiring ahead of the first content realisation.
Also drops the now-redundant per-VM DataTemplate blocks from App.axaml --
the single ViewLocator already resolves them -- and the unused
MainWindowViewModel.DockFactory property and DockControl init flags.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Language.BracketSearcher defaulted to DefaultBracketSearcher
(no-op); only CSharpLanguage overrode it. Switching the active
language to ILAst or IL left caret-on-bracket inert.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The default ApiVisibility=PublicOnly setting filters search results
through CheckVisibility, which rejects private entities. Compiler-
generated names — async/iterator state machines (<Method>d__N),
display classes (<>c__DisplayClass), anonymous-method closures (<>c)
— are all private, so they were unfindable even when the user
typed the exact full name.
Assisted-by: Claude:claude-opus-4-7:Claude Code
UpdateLastDocumentCanClose was counting only factory.Documents.VisibleDockables
to decide CanClose. After the user drags a tab out into a side-by-side
split, Dock creates a second IDocumentDock that holds the dragged tab.
factory.Documents still points at the ORIGINAL dock (now with one tab),
so the handler updates CanClose=false on its remaining tab — but the
new sibling dock's tab keeps the CanClose=true it had before the drag.
Result: one tab closable, the other not, even though the user has two
documents open.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Stepper.Step's break-at-step-limit relies on Debugger.Break(), which
is a silent no-op when no debugger is attached. The wiring (command ->
RestartDecompileWithStepLimit -> DecompilationOptions.IsDebug ->
context.Stepper.IsDebug -> Stepper.Step) was all correct, but the
common-case "user runs from a terminal" left the gesture invisible.
Assisted-by: Claude:claude-opus-4-7:Claude Code
DockWorkspace previously subscribed to IFactory.ActiveDockableChanged
hoping it would fire on tab-strip clicks. It doesn't — Dock's
FactoryBase only raises that event from InitActiveDockable (layout
structural init at startup/load). User-driven tab clicks set
dock.ActiveDockable = X directly on the dock-model, which raises the
model's own INotifyPropertyChanged but bypasses the factory event.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Ports the WPF assemblyList_CollectionChanged history-prune from
ILSpy/AssemblyTree/AssemblyTreeModel.cs to the Avalonia
DockWorkspace.OnAssemblyListChanged handler. NavigationHistory<T>
already exposed the RemoveAll(Predicate<T>) primitive; the caller
wiring was the missing piece. Without it, a tree-row click after
removing an assembly walked through NavigationEntry.DisplayText
on a stale TreeNodeEntry, which hit MemberReferenceTreeNode.Signature
-> Language.EntityToString -> ILAmbience.ConvertSymbol and NRE'd on
a now-null ParentModule.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Sweep-up commit. The dotnet-format pre-commit hook keeps re-ordering
these usings on every other commit; landing them once stops the hook
from grumbling at unrelated diffs going forward.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Generalises the existing internal StartupLog into a public AppLog with
named categories. The Startup category (default-on) keeps the existing
[startup +Nms] format and Mark/Phase API, so all 44 call sites get a
mechanical StartupLog. -> AppLog. rename without behavioural change.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the reflection-driven Dock.Serializer.SystemTextJson._options
mutation with a from-scratch JsonSerializerOptions in ILSpyDockJson.cs.
Mirrors Newtonsoft's ListContractResolver semantics — polymorphism on
the six IDockable interfaces, IList<T> -> ObservableCollection<T>
substitution at deserialise time, writable-only property persistence,
the standard ignore set (ICommand, [IgnoreDataMember]) — and keeps the
existing back-reference / Task-shape strip plus the singleton-dockable
CreateObject hook.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Without per-dockable view resolution the dock chrome caches one view per
slot and reuses it for every dockable that lands there, so a second pane
sharing an Alignment (Analyzer + Debug Steps both Bottom) renders the
first pane's content under the second pane's tab. Each dockable instead
owns its view (IDockableViewOwner) and DockableViewRecycling hands that
instance back keyed by dockable identity -- there is no app-lifetime
global view cache to leak every transient document tab's view subtree.
Resolution runs through the single application-wide ViewLocator (its
dispatch map is introduced here rather than in the later DataTemplate
cleanup) so the dock never re-resolves an owned view through the template
machinery on a cache hit.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The three editor-command tests (Copy_Entry_Reflects, Copy_Execute, SelectAll_Execute)
selected `System.Linq.Enumerable` (the whole type, 300+ LINQ methods) and waited
on `view.Editor.Document.TextLength > 0` — a 15s `WaitForAsync` poll. In suite
context this was sloppy: it could latch on stale editor content from a previous
test, and in isolation it occasionally raced past 15s on a slow system,
producing the intermittent `failed: 1` we saw earlier.
Assisted-by: Claude:claude-opus-4-7:Claude Code