Skip to content

Round-trip the legacy WPF SessionSettings shape - #3766

Merged
christophwille merged 2 commits into
masterfrom
fix-session-settings-wpf-shape
Jun 11, 2026
Merged

Round-trip the legacy WPF SessionSettings shape#3766
christophwille merged 2 commits into
masterfrom
fix-session-settings-wpf-shape

Conversation

@siegfriedpammer

Copy link
Copy Markdown
Member

Summary

The Avalonia rewrite changed the on-disk shape of <SessionSettings> in ILSpy.xml. Users upgrading from ILSpy 10.x silently lose their saved window position, restored tree-view selection, search-mode pick, and auto-loaded dependency on first launch — the new code reads the new shape only, ignores the legacy CSV/escape encodings, and writes a fresh subset that drops anything it doesn't recognise (including the AvalonDock <DockLayout> blob).

This branch makes the load+save cycle non-lossy against a real WPF-era ILSpy.xml.

  • Two-direction compatibility with the legacy shape. WindowBounds accepts both the new Left/Top/Width/Height attribute form and the WPF <WindowBounds>L,T,W,H</WindowBounds> CSV body; <ActiveTreeViewPath><Node> values are \xNNNN-hex-decoded on read and re-encoded on write. With both directions in the file an older ILSpy 10.x install can still read what the Avalonia build writes, and the diff against a pre-existing ILSpy.xml stays small during the transition.
  • Unknown children stay untouched. LoadFromXml stashes any child whose name isn't in KnownChildren (e.g. the AvalonDock <DockLayout> blob, a future field added in a newer build); SaveToXml re-emits them verbatim after the known section. We don't try to interpret the WPF dock layout — but writing zeros over it would discard the user's saved layout the first time they launched the Avalonia build.
  • Wire SelectedSearchMode and ActiveAutoLoadedAssembly. Both were read into properties that nothing in the Avalonia code wrote to. Now SearchPaneModel restores the saved mode on construction and writes back on change via SettingsService; 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 so the saved tree path can resolve into the dependency.

The EnableSmoothScrolling attribute is intentionally not re-added — that feature was removed in the rewrite (per maintainer note) and now flows through DisplaySettings' own attribute-passthrough so it doesn't crash the load.

Test plan

  • Unit tests in ILSpy.Tests/SessionSettingsTests.cs cover: legacy CSV WindowBounds body, \xNNNN Node unescape, the full WPF-era section shape, unknown-children load (ignored, no throw), unknown-children save (re-emitted verbatim), legacy WPF encodings on save (CSV + escaped nodes), and round-trip of SelectedSearchMode + ActiveAutoLoadedAssembly. Full ILSpy.Tests suite still green (817 passing, 3 skipped — same as master).
  • End-to-end smoke: launched the built app against a sandbox ILSpy.xml containing the WPF-era SessionSettings shape from a real 10.x user (with <WindowBounds>882.6666666666666,342,750,550</WindowBounds>, \x002E-escaped <Node> values, <SelectedSearchMode>TypeAndMember</SelectedSearchMode>, and the AvalonDock <DockLayout> blob); closed via WM_CLOSE so SettingsService.Save ran; verified the file came back with <WindowBounds>882,342,750,550</WindowBounds> (CSV body, no attributes), TomsToolbox\x002EWpf still escaped, <SelectedSearchMode> retained, and <DockLayout> preserved verbatim.

🤖 Generated with 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
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
@christophwille
christophwille merged commit aaca186 into master Jun 11, 2026
9 checks passed
@christophwille
christophwille deleted the fix-session-settings-wpf-shape branch June 11, 2026 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants