Clicking a reference left AvaloniaEdit's selection-drag mode stuck, so a
subsequent mouse move (with no button held) extended a selection.
AvaloniaEdit's SelectionMouseHandler captures the pointer and enters
selection mode on press, and extends the selection on every move while that
mode is active -- it keys off the mode, not whether a button is down. Only
its bubble-phase PointerReleased handler resets the mode and releases the
capture, and that handler early-returns when the event is already handled.
The reference-click handler ran in the tunnel phase and marked the release
handled before AvaloniaEdit saw it, so AvaloniaEdit's cleanup never ran.
Move the release handler to the bubble phase so it runs after AvaloniaEdit
has reset its state. AvaloniaEdit subscribes from the TextArea constructor,
before this view attaches its handlers, so the ordering is deterministic and
navigation can stay synchronous.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The "IL with C#" view decompiles each method body as a bare handle, so a
static constructor is decompiled without its type's field declarations in
the syntax tree. MoveFieldInitializersToDeclarations then could not find a
declaration to move the static-field-initializer statement onto, asserted
(kind was Static, not Primary) and dropped the statement -- crashing Debug
builds and silently losing the assignment in Release.
Dropping the statement is only correct for the primary-constructor case,
where the assignment's backing member is synthesized and has no separate
declaration. For static/instance initializers a missing declaration just
means the member is not part of this partial syntax tree, so the
assignment must remain in the constructor body.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Navigating to a target on startup first eagerly loads every relevant
assembly's metadata so the entity search that follows can resolve it.
That pre-load used the throwing GetMetadataFileAsync, so a restored
session that still referenced an assembly whose file had since been
deleted or moved crashed startup with an unhandled
DirectoryNotFoundException instead of simply skipping the gone entry.
Use GetMetadataFileOrNullAsync there: a missing or unreadable assembly
now resolves to null and is skipped, which the entity search already
tolerates (it uses the OrNull variant too).
Assisted-by: Claude:claude-opus-4-8:Claude Code
Runtime async is a compiler feature that emits ordinary async/await (a
C# 5 construct), so reconstructing it should not require selecting C# 15.
The dedicated RuntimeAsync setting was also redundant: AsyncAwaitDecompiler
already runs the runtime-async transforms only when AsyncAwait is enabled.
Fold the behavior into the AsyncAwait setting and drop the separate toggle.
Assisted-by: Claude:claude-opus-4-8:Claude Code
When a breadcrumb path was longer than the bar, the Simple theme's horizontal
scrollbar appeared in a reserved layout row inside the 28px bar: it shifted the
crumbs up when it showed and covered more than half the bar's height.
Three ways to handle the overflow were considered:
1. Collapse the head into an "..." overflow dropdown (leading crumbs fold into
a left button; the deepest crumbs stay visible) - the Windows Explorer / VS
nav-bar / Files behaviour.
2. Hide the scrollbar entirely and scroll the trail with the mouse wheel,
keeping the current-node end visible.
3. [CHOSEN] A thin, auto-hiding overlay scrollbar that draws over the content
(reserves no height, so nothing shifts) and fades in only when the path
overflows and the pointer is over the bar.
Avalonia's Simple ScrollViewer reserves an Auto grid row for the horizontal
scrollbar and its ScrollBar has no auto-hide, so the overlay is built here: the
breadcrumb's built-in bar is hidden (no reserved row, no shift) and a thin,
button-less ScrollBar is layered at the bottom, bound to the viewer's Offset,
ScrollBarMaximum, and Viewport via small Vector/Size converters. It fades in on
pointer-over and the mouse wheel scrolls the trail horizontally.
Assisted-by: Claude:claude-opus-4-8:Claude Code
ILSpy showed the current location only implicitly in the tree selection and kept search in a separate docked pane. The omnibar, modelled on the Files community app and jiripolasek's EditorBar, puts an address-bar atop each decompiler document: a breadcrumb of the node (Assembly > Namespace > Type > Member) whose segments navigate and whose chevrons list child nodes, turning into a search box on typing that reuses the existing search engine. It coexists with the docked search pane and ships off by default behind the Options / Display 'Tab options' EnableOmnibar toggle, which applies live.
Assisted-by: Claude:claude-opus-4-8:Claude Code
CopyFSharpCoreDll built the ILSpy-tests path with hardcoded Windows backslashes
("..\\..\\..\\ILSpy-tests\\FSharp\\FSharp.Core.dll"). On non-Windows, '\' is a
normal filename character, so the whole string became a single bogus path,
File.Exists always returned false, and the FSharp ILPretty tests were silently
Assert.Ignore'd even when ILSpy-tests was checked out. Build the path from
Path.Combine segments instead so it resolves on every platform (same target:
three levels up from the ILPretty test-case directory).
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Linux and macOS jobs were near-identical copies; their shared head
(checkout, SDK setup, version, restore, build, both test steps, test-log
upload) drifts the moment one is edited and the other is not. Collapse
them into a single platform-parameterized matrix job, and hoist the .NET
SDK version/quality and the staging directory to workflow-level env so an
SDK bump is a one-line change across all jobs.
The Windows job keeps its id (Build) so internal references stay stable,
but gains a display name to read as "Desktop (Windows)" alongside the
matrix "Desktop (linux)/(macos)" checks. Branch-protection required
checks must be repointed to the new check names.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The decompiler test suite is platform-aware: on non-Windows,
Tester.SupportedOnCurrentPlatform keeps only the dotnet-hosted Roslyn
configs and [Platform("Win")] gates the fixtures that truly need
Windows (legacy csc, mcs, 32-bit, roundtrip), so running the suite on
the Linux and macOS jobs gives real coverage of the non-Windows
decompilation paths instead of leaving them untested. Both jobs now
also check out the ILSpy-tests submodule, because the TargetNet40
configs compile against its legacy reference assemblies even when the
compiled output is never executed.
Assisted-by: Claude:claude-fable-5:Claude Code
The first CI run on a macOS runner surfaced that these tests encoded
the Windows/Linux menu shape, while MainMenu.Attach intentionally
diverges on macOS: TranslateGesturesForMacOS rewrites Control to Meta
so shortcuts follow the Cmd-key convention, and PromoteHelpToMacAppMenu
moves the Help items into the application menu and drops the _Help
top-level. The tests now assert the macOS-correct expectations on
macOS, including that the Help content lands in the app menu rather
than vanishing.
Assisted-by: Claude:claude-fable-5:Claude Code
The macOS CI artifact is an unsigned zipped ILSpy.app; the signed
distribution channel is a dmg the release manager creates offline with
create-dmg.ps1 (codesign and notarization stay optional parameters, so
no signing secrets ever live in CI). Distribution via Homebrew uses a
tap repository holding only a cask file that points at the dmg attached
to the GitHub release; the cask template and per-release update steps
are documented next to the scripts.
Assisted-by: Claude:claude-fable-5:Claude Code
Two new jobs run alongside the Windows matrix: each builds the desktop
solution filter on its own OS, runs the headless UI tests there, and
packages Release self-contained bundles (zip/deb/rpm on Linux, zipped
ILSpy.app on macOS) via the BuildTools packaging scripts. Decompiler
tests, NuGet packing, and the Windows installers stay in the Windows
job, and neither new job checks out the ILSpy-tests submodule, since
that only feeds the decompiler tests. The Windows job is untouched so
existing required-status-check names keep working.
Assisted-by: Claude:claude-fable-5:Claude Code
With the move to Avalonia the app runs on Linux and macOS, so the
distribution tooling grows beyond Windows: publish.ps1 gains a
-Platform parameter (the default keeps the Windows behavior unchanged),
and new BuildTools scripts package the self-contained publish outputs
into a zip, deb, and rpm on Linux and a zipped ILSpy.app on macOS.
Nothing is signed at package time; signing happens offline on the
release manager machine. Info-ZIP zip is used instead of
Compress-Archive because the latter drops unix mode bits. deb/rpm are
built with dpkg-deb/rpmbuild directly (both preinstalled on GitHub
ubuntu runners) following sourcegit-scm/sourcegit's proven model,
including the libicu OR-chain in the deb control file and disabled
auto-requires for the self-contained rpm payload. Pre-release versions
map '-' to '~' so both package managers sort them before the release.
The macOS bundle's Info.plist now carries placeholders stamped at
package time; on Windows the spec/control/desktop files are kept LF via
.gitattributes because Linux tools consume them verbatim.
Assisted-by: Claude:claude-fable-5:Claude Code
On FIPS-mode systems the platform crypto provider refuses to create
SHA-1 instances (OpenSSL: error:03000098 invalid digest), so merely
displaying a strong-named assembly's identity failed. The public-key
token is a non-secret identity hash whose algorithm is fixed by
ECMA-335, so the two token sites now use dotnet/runtime's managed
Sha1ForNonSecretPurposes, vendored with its license header intact and
shielded from the repo formatter via generated_code in .editorconfig
so future upstream syncs diff cleanly. IncrementalHash was considered
and rejected: like SHA1.Create(), it resolves the digest through the
host crypto policy, and Roslyn's equivalent token code also relies on
the platform SHA-1, so it offers no precedent for FIPS safety.
Assisted-by: Claude:claude-fable-5:Claude Code
WPF revealed files through the shell COM API (SHOpenFolderAndSelectItems):
selecting several assemblies and choosing "Open Containing Folder" opened
one Explorer window per folder with all of that folder's files selected,
reusing a window already open at the location. The Avalonia port replaced
this with explorer.exe /select, invoked once per file, so revealing N
assemblies spawned N new windows and a single reveal never reused an
existing one.
Restore the shell-COM reveal on Windows behind the existing cross-platform
ShellHelper, grouping the selection by containing folder. macOS keeps a
single Finder "open -R" for the whole selection; Linux, lacking a portable
select-item hook, opens each distinct parent folder once instead of one
window per file. The folder grouping is a pure, unit-tested seam so the
behaviour is verified without launching the OS file manager.
The Windows COM portion is adapted from the pre-Avalonia ShellHelper; its
author's copyright notice is retained per its MIT license.
Assisted-by: Claude:claude-opus-4-8:Claude Code
LoadedAssembly caches one type system per TypeSystemOptions value, but
the consumers disagreed on which one to use: tree nodes and the
metadata navigator resolved through GetTypeSystemOrNull (a separate
default-options instance) and the search built its request with default
settings. Each module could therefore materialise several type systems,
and none of the display surfaces respected settings that change entity
shapes (nullability, tuples, extension methods, ...). WPF routed all of
these through GetTypeSystemWithCurrentOptionsOrNull.
Reintroduce that helper on top of CreateEffectiveDecompilerSettings and
use it in TypeTreeNode, AssemblyReferenceTreeNode, and the metadata
navigator; the search request now derives its settings the same way, so
all of them hit the same cached instance. NamespaceTreeNode.Decompile
enumerates through the type system matching the decompilation options
it was handed.
Assisted-by: Claude:claude-fable-5:Claude Code
The Avalonia port gave DecompilationOptions a parameterless constructor
that silently defaults to new DecompilerSettings(). Several paths picked
it up and decompiled with default settings where the WPF version used
the user's current options: tree member filtering (CSharpLanguage.
ShowMember), PDB generation, the single-file / project / solution Save
Code paths, and the DEBUG decompile-all commands.
Promote the live-snapshot logic that was private to DecompilerTabPage-
Model (settings clone + Display-option bridge + toolbar language
version) to SettingsService.CreateEffectiveDecompilerSettings and use
it at every entry point. Remove the parameterless DecompilationOptions
constructor and make SolutionWriter require settings, so reaching for
defaults is an explicit choice rather than a silent fallback - that
default is exactly what masked these regressions. Search deliberately
keeps default settings (it only needs a type system to materialise).
Assisted-by: Claude:claude-fable-5:Claude Code
These types were public through release/10.1; the Avalonia port had
dropped them to internal. Plugins build custom tree integrations on
top of the member tree nodes, so restore their 10.1 visibility.
NaturalStringComparer becomes public too, but keeps its static-class
shape.
Assisted-by: Claude:claude-fable-5:Claude Code
The global:: prefixes existed because the test project's namespace
ICSharpCode.ILSpy.Tests used to shadow the app's old top-level ILSpy
namespace. With the UI code back under ICSharpCode.ILSpy there is
nothing left to shadow, so plain fully qualified names resolve fine.
Assisted-by: Claude:claude-fable-5:Claude Code
The Avalonia port had placed the UI app in an ILSpy.* namespace tree,
while the csproj RootNamespace and every prior release (through 10.1)
use ICSharpCode.ILSpy.*. Restoring the historical namespace reduces the
public API diff against release/10.1 for plugin authors and removes the
shadowing that forced global:: qualifiers in the test project. The
Images class and AccessOverlayIcon enum move back into the root
namespace (as in 10.1), since an ICSharpCode.ILSpy.Images namespace
would shadow the Images class for all code inside ICSharpCode.ILSpy.
Assisted-by: Claude:claude-fable-5:Claude Code
Windows maps CON, PRN, AUX, NUL, COM1-9 and LPT1-9 to devices -- on many
builds even with an extension appended, so a type named Con made both
whole-project export and the save dialog fail with IOException '\\.\Con'.
CleanUpName only checked for reserved names after re-appending the file
extension, where they never match, and the save-dialog default-name
helpers did not check them at all. The escape appends the underscore to
the base name (con_.txt, not con.txt_) because device-name parsing
ignores everything after the first dot, and is applied per path segment
so reserved directory names produced by namespaces are covered too. The
ILSpy.Tests.Windows fixture verifies on a real Windows filesystem that
the escaped names are creatable.
Assisted-by: Claude:claude-fable-5:Claude Code
Reference links navigated on pointer-press, which meant a press-drag
over a link could never select its text -- the press fired navigation
and handled the event before a selection could start. Move navigation
to pointer-release and only follow the link when the pointer did not
drag past the click threshold, matching the WPF view's mouse-up
handling. A press-drag now selects link text like any other span.
Assisted-by: Claude:claude-fable-5:Claude Code
A Debug.Assert raised on a background decompile thread runs through
TraceInternal.Fail, which holds the global trace lock while invoking
the listener; the listener blocks on the UI thread to show the
assertion dialog. Meanwhile the UI thread, mid-navigation, runs a
layout pass whose virtualizing panel raises a binding error that
Avalonia logs via Trace.WriteLine -- which blocks acquiring that same
global trace lock. The two threads deadlock and the app freezes with
no dialog.
Mark the listener thread-safe and disable the global trace lock so
TraceInternal dispatches to it without serializing, removing the
contended lock between the asserting thread and the UI thread.
Assisted-by: Claude:claude-fable-5:Claude Code
Selecting text inside the popup closed it: the selection drag captures
the pointer, so sweeping past the popup edge fired the content's
PointerExited with IsPointerOver already false. The close veto now
covers pointer-over, keyboard-focus-within (so a finished selection
survives for Ctrl+C, as the WPF tooltip did), and pointer capture held
inside the popup; only a document change force-closes.
Assisted-by: Claude:claude-fable-5:Claude Code
Doc-comment crefs in hover tooltips rendered as link-styled but inert
text, and cref resolution was never wired up. Crefs now resolve against
the current tab's assembly list and navigate through the same
NavigateToReferenceEventArgs channel the analyzer uses; href links open
via the TopLevel launcher. Avalonia inlines are not input elements, so
links are hit-testable TextBlocks embedded via InlineUIContainer.
Interactive content also requires the popup to survive the pointer
travelling onto it: the editor's exit handler now tolerates the
overlay-popup enter/exit ordering via the distance corridor, and the
popup closes when the pointer leaves its own content instead.
Assisted-by: Claude:claude-fable-5:Claude Code
The hover documentation popup has been invisible since
X11PlatformOptions.OverlayPopups was enabled (c075c2bc8): popups are
hosted in an OverlayPopupHost, a ContentControl that resolves its
control theme through the Popup's logical parent. The popup was
constructed detached (valid for the native PopupRoot hosts used
before), so the host stayed template-less, never materialized its
child, and rendered as nothing. Declaring the popup in the view's
XAML gives the host a styling root.
Assisted-by: Claude:claude-fable-5:Claude Code
The WPF app showed the disassembled IL header when hovering a member
reference in IL view, and the opcode's XML documentation when hovering
an instruction. The Avalonia port lost both: ILLanguage fell back to
the base ambience one-liner and the opcode tooltip carried only the
name and encoding. Opcode docs additionally need a modern-.NET source:
the WPF code read the .NET Framework reference-assembly docs, which do
not exist on modern .NET, so MscorlibDocumentation now falls back to
the ref pack parallel to the hosting runtime.
Assisted-by: Claude:claude-fable-5:Claude Code
When the current language is not an IDebugStepProvider (e.g. the IL
disassembler), the Debug Steps pane kept displaying the previous
language's step tree, and its commands still triggered re-decompiles
with a step limit the language ignores. Detaching now clears the tree
and the view shows a "not available" note until a step-providing
language is selected again.
Assisted-by: Claude:claude-fable-5:Claude Code
Compilation uses the .NET builds of the Roslyn toolsets (tasks/netcore*,
bincore csc.dll/vbc.dll launched through the dotnet host). ilasm/ildasm
options use the '-' prefix, which all platforms accept. The dotnet-hosted
compilers have no implicit references or SDK path: net40 compiles pass
mscorlib explicitly, and vbc gets -sdkpath, _MYTYPE=Empty and
-vbruntime:Microsoft.VisualBasic.Core.dll (the facade in the ref packs is
not followed for runtime helpers). The TestRunner gets a self-contained
build for the host platform.
Configurations depending on Windows-only tools or runtimes (legacy
csc/vbc, Roslyn 1.x/2.x, mcs, Force32Bit, executing net40 binaries) are
filtered from the matrix off-Windows via Tester.SupportedOnCurrentPlatform
or gated with [Platform("Win")]. PdbGen comparisons normalize document
name separators, and Correctness/Async uses Console.IsInputRedirected
instead of the Windows-only Console.CapsLock.
Assisted-by: Claude:claude-fable-5:Claude Code
Hosts without a .NET Framework installation (e.g. Linux and macOS) have
no GAC; the only system-wide assembly store there is the shared-framework
directory of the runtime executing the decompiler, and
UniversalAssemblyResolver only consulted it through the version <= 4.0
legacy fallback. This made e.g. the type-forwards of a netstandard facade
(pointing to a versioned System.Runtime) unresolvable, which left
well-known types like Nullable<T> without a definition and among other
things misaligned nullability decoding (Nullable<T> occupies no slot in
the nullable metadata, so it must be recognized).
On Windows nothing the GAC answered changes; the new fallback only adds
resolutions that previously failed outright.
Assisted-by: Claude:claude-fable-5:Claude Code
Captures the compiler-matrix model, the test kinds and their pipelines,
how to add tests per kind, the #if/preprocessor-symbol comparison rules,
and the probe-a-construct-across-all-compilers workflow, so this no
longer has to be reverse-engineered from the runners.
Assisted-by: Claude:claude-fable-5:Claude Code
The decompiled view only offered navigation on member names; finding
what an override actually overrides required opening the analyzer.
Attaching a reference to the modifier token gives the same
go-to-definition affordance Visual Studio has on 'override'. The
reference resolves via InheritanceHelper.GetBaseMember, so it targets
the nearest overridden member and degrades to plain text when the base
member cannot be resolved.
Assisted-by: Claude:claude-fable-5:Claude Code
Two failure modes that both showed the user the "no" cursor with no
other feedback:
1. ResolveDropTarget returned null when e.Source had no
SharpTreeViewItem ancestor, so Explorer drops landing in the gap
beneath the last row (or onto an empty list) never reached
SharpTreeNode.InternalDrop.
2. The middle 50% of a row produces a DropPlace.Inside target on the
row's own node. Most concrete SharpTreeNodes inherit the base CanDrop
(returns false), so a literal "drop a .dll onto an assembly row"
refused even though AssemblyListTreeNode happily accepts that payload.
For (1), fall back to (Root, Children.Count, DropPlace.Inside) when no
row is hit. For (2), introduce PickAcceptingTarget which retries with
the empty-space (root) target when the initial CanDrop is false. Both
OnDragOver and OnDrop go through the same picker so the cursor and the
actual drop agree on the outcome. The retry skips when the initial
target already IS the root, so a real refusal still surfaces as "no".
The marker adorner stays hidden for empty-space drops because the
place is always Inside (Item is null for that case); the existing
DropPlace.Inside early return covers it.
DropTarget, DropPlace, and the two new helpers are internal-visible to
the ILSpy.Tests project for the unit tests that assert the fallback
chain.
Assisted-by: Claude:claude-opus-4-7[1m]:Claude Code
Frozen tabs each cache their own decompiled output, but the language
change handler only refreshed ActiveDecompilerTab — hard-wired to the
preview tab — and the display-setting refresh only reached the preview
tab's cached content model, so frozen (and floated) tabs kept showing
stale output until manually re-selected. The handler predates the
freeze feature, which introduced multiple sibling decompiler tabs
without widening it.
Both paths now iterate every decompiler tab across all document docks
(the walk CancelPendingOperationsAsync already used, extracted as
AllDecompilerTabs), and the language handler uses Redecompile instead
of the CurrentNode re-assignment hack.
Assisted-by: Claude:claude-fable-5[1m]:Claude Code
Three follow-ups so the WPF-era ILSpy.xml survives a load-save-load
cycle through the Avalonia host without losing data the user expected
to keep.
Preserve unknown children verbatim. LoadFromXml only interprets a
known set of element names (KnownChildren) and stashes everything
else; SaveToXml re-emits the stash after the known section. The
AvalonDock <DockLayout> blob the WPF host writes is incompatible with
the Avalonia Dock host and we don't want to interpret it, but blank-
ing it on save would discard a WPF user's saved layout the first
time they launched the Avalonia build. The same stash future-proofs
older builds against fields added in newer ones.
Emit the legacy on-disk shape on save. WindowBounds is written as a
CSV body "L,T,W,H" (the WPF Rect TypeConverter format) instead of
Left/Top/Width/Height attributes, and ActiveTreeViewPath <Node>
values are \xNNNN-hex-escaped on write. With both directions of the
conversion in this file a file written by the Avalonia build is
still readable by an older ILSpy 10.x install, and the diff against
a pre-existing ILSpy.xml stays small during the transition.
Wire SelectedSearchMode and ActiveAutoLoadedAssembly. The WPF host
persisted both: the search-pane mode picker so the user's last
choice survives restarts, and the file path of the auto-loaded
(dependency) assembly the saved tree-path lands in so the restore
can re-open it before walking. The Avalonia code read both into
properties that nothing wrote to. Now SearchPaneModel restores
SelectedSearchMode on construction and writes back on change via
SettingsService, and AssemblyTreeModel walks the selection's
ancestor chain to find the owning AssemblyTreeNode, stores its
LoadedAssembly.FileName when IsAutoLoaded, and re-opens that file
(when it still exists) before RestoreSelectedPathAsync.
Assisted-by: Claude:claude-opus-4-7[1m]:Claude Code
The new Avalonia SessionSettings reads WindowBounds as Left/Top/Width/Height
attributes and reads ActiveTreeViewPath node values raw, but ILSpy 10.x wrote
WindowBounds as a CSV element body ("L,T,W,H", the Rect TypeConverter format)
and \xNNNN-hex-escaped every non-letter-or-digit char in each Node value (so
"TomsToolbox.Wpf" round-tripped through XML as "TomsToolbox\x002EWpf"). On
first launch against an existing ILSpy.xml that means the saved window
position resets to the default and the selected tree node never restores --
the escaped path can't match a live node's ToString().
Accept the CSV body when the WindowBounds element has no attributes, and
decode \xNNNN escapes in tree-view node values. Both fall back through the
existing ParseDouble defaults if a piece is missing, so a corrupted entry
won't crash startup.
Lock the shape in with tests against the actual section the WPF host emits,
plus a forward-compat pair confirming LoadFromXml ignores unknown children
(DockLayout, SelectedSearchMode, ActiveAutoLoadedAssembly) and SaveToXml
never echoes them back -- the AvalonDock schema doesn't translate to the
Avalonia Dock host and would otherwise persist forever as dead state.
Assisted-by: Claude:claude-opus-4-7[1m]:Claude Code
WPF showed a row Offset column and decoded the Kind GUID into one cell
combining heap offset, friendly name, and raw GUID; the Avalonia table
only carried the bare heap offsets. Offset follows the GetRowOffset
convention every other table uses. Kind is plain text rather than a
hex column plus tooltip so the per-column filter matches the friendly
names.
Assisted-by: Claude:claude-fable-5[1m]:Claude Code
Selecting a row in the CustomDebugInformation table now previews its
Value blob beneath it, completing WPF row-details parity: state-machine
hoisted local scopes, compilation options, metadata references, and
tuple element names parse into typed sub-grid rows; source-link JSON
shows as text; everything else — unrecognized kinds, embedded source,
and malformed structured blobs — degrades to a hex dump, as in WPF.
The parser switches on the Kind GUID directly via KnownGuids; the
entry deliberately carries no decoded-kind state, leaving that to the
kind-column work that builds on this.
Assisted-by: Claude:claude-fable-5[1m]:Claude Code