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
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 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
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
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
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
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
Sorting rebuilds every top-level assembly node (AssemblyList.Sort clears the
collection and re-adds it, so the tree tears down and recreates all nodes). That
dropped the selection entirely and snapped the list back to the top, so the user
watched the tree reshuffle out from under them. Capture the selected assemblies
before the sort and re-select them after, so the view settles on the same items
(the selection binder keeps the primary in view) instead of jumping to the top.
The assembly/analyzer panes now host SharpTreeView, but ~20 headless tests
still queried the old ProDataGrid surface (DataGrid/DataGridRow/Hierarchical
Model/HierarchicalNode), so they could not exercise the live tree. Retarget
them to SharpTreeView/SharpTreeViewItem and the flattener (ItemsSource) the
control actually exposes, and centralise the row/selection lookups in
TreeNodeAssertions so the scroll assertions ride the new container type.
Driving the retargeted tests against the real control surfaced four genuine
gaps, fixed here in SharpTreeView:
- Ctrl+A selected nothing on the first press because the base ListBox only
selects all once an item inside it is focused; handle it explicitly and make
the control itself focusable so the gesture works the moment the pane gains
focus.
- Left/Right navigation focused the parent/first-child container but never
moved the selection (selection must follow the caret); add SelectAndFocus.
- The expander toggle had no stable name; name it PART_Expander so hit-target
assertions can find it, and align the test to the shipped 13px column (kept
at 13 so the +/- box centres on the connector lines).
- Tree rows stretch to content width with horizontal scroll, so a row centre
can sit past the viewport's right edge; the pointer tests now click within
the visible grid width instead of the off-screen row centre.
The UseNestedNamespaceNodes re-bind test asserted a ProDataGrid Hierarchical
Model swap that no longer exists; rewrite it to assert the live flattener
reshapes the visible rows in place when the setting toggles.
WPF-parity reusable drag-drop: SharpTreeView now owns the drag gesture, the Before/Inside/After hit-testing, and the insert-marker (an AdornerLayer line), and delegates the actual drop to the target SharpTreeNode.CanDrop/Drop via thin Avalonia IPlatform adapters (AvaloniaDataObject / AvaloniaPlatformDragEventArgs). The drag start uses node.CanDrag + node.Copy for the payload but is orchestrated by the control (Avalonia's DnD is async/pointer-based, unlike WPF's synchronous IPlatformDragDrop).
AssemblyTreeNode gains CanDrag + Copy (drag the assemblies' file paths); AssemblyListTreeNode gains CanDrop/Drop that opens (dedupes) + Moves to the drop index -- so reorder and external file-drop unify. Post-drop selection is a view concern, delegated back to the pane via SelectAssembliesAfterDrop. AssemblyListPane sheds all its drag/file-drop code. Reorder + file-drop tests now drive the node contract directly.
Ports assembly reorder onto the ListBox-based tree using Avalonia's DragDrop pipeline: a left-drag off a top-level assembly row starts DragDrop.DoDragDropAsync carrying a marker DataFormat (the dragged set is held in a field -- it's an internal move), DragOver/Drop dispatch reorder vs. file-drop on that marker, and the reorder itself goes through AssemblyList.Move via the new CanReorder/ReorderAssemblies methods (same validation as the old AssemblyRowDropHandler: top-level non-package assemblies only, never Inside/onto-self). The reorder tests now drive CanReorder/ReorderAssemblies directly instead of ProDataGrid's RowDropHandler, and are no longer [Ignore]d (4 passing).
Not yet ported: the drag insert-marker line (drop feedback is the Move cursor for now).
Replaces the ProDataGrid hierarchical tree in AssemblyListPane with the new ListBox-based SharpTreeView: Tree.Root binds to the model root, selection mirrors at the SharpTreeNode level (deleting the ~186 LOC HierarchicalModel sync + the TreeKeyboardController reflection workaround), the Thunderbird context-target / MMB-new-tab / Delete / Ctrl+R paths port over, file-drop is preserved, and the API-level filter re-applies in place via ILSpyTreeNode.RefreshRealizedFilter (the model self-filters into IsHidden; the flattener drops hidden nodes).
Styling to match the classic tree: flush list (no ListBox padding/border), exact WPF +/- expander box, 20px rows, gray dotted connector lines wired through the existing TreeLines control with an 18.5px indent step so a child's +/- box sits under the parent's icon and the line passes through it.
KNOWN GAP: assembly drag-reorder is not yet ported (AssemblyTreeDragReorderTests [Ignore]d, task #19) and ~20+ assembly headless tests still query the old DataGrid and need retargeting to SharpTreeView (task #20). Production builds green and the app runs; the test suite is red on those un-retargeted tests.
Extending a tree selection with Shift+Down then pressing Shift+Up didn't shrink the range back toward the anchor -- the dropped rows stayed selected, and in the middle of a list the anchor drifted so the range crept the wrong way. Root cause is upstream in ProDataGrid: a shift+nav key moves the grid's current row and anchor correctly, but SelectFromAnchorToCurrent calls SetRowsSelection(start,end), which only ADDS the range and never deselects rows outside it, so a shrink is a no-op. After the grid processes a shift+nav key, TreeKeyboardController prunes the selection to exactly the [anchor..current] range. It deselects out-of-range rows via the internal SetRowSelection(slot, isSelected:false, setAnchorSlot:false) rather than SelectedItems.Remove: removing an item makes the grid re-derive its current row and corrupt the anchor for the next key, whereas SetRowSelection deselects a slot in place and leaves current/anchor intact. The slot members are read reflectively and defensively (falls back to the pre-fix behaviour if a future ProDataGrid renames them). Both the assembly and analyzer trees get it via the shared controller.
Extract the standard tree keyboard gestures -- Left/Right expand-collapse + parent/child nav, numpad +/-/*, and new type-ahead incremental search -- out of AssemblyListPane into a reusable TreeKeyboardController that drives any hierarchical DataGrid. The model implements ITreeKeyboardTarget (PrimarySelectedNode + SelectNode) so each tree keeps its own select-and-reveal behaviour; expansion goes through the grid's IHierarchicalModel. Both the assembly tree and the analyzer tree now attach one, so they share consistent keyboard behaviour. Delete (unload) and Ctrl+R (analyze) stay assembly-specific in the pane.
Numpad + expands the focused node, - collapses it, and * expands it recursively. The recursive walk only descends into children whose SharpTreeNode.CanExpandRecursively is true, which is false for lazy-loading nodes -- so * stays bounded and won't try to materialise an entire assembly. Mirrors WPF SharpTreeView.
Standard tree keyboard navigation in the assembly list: Left collapses an expanded node or moves to the parent when already collapsed; Right expands a closed node or steps into its first child. Operates on the primary selection via the hierarchical model (FindNode/Expand/Collapse) and SelectNode; the parent step is gated on the parent being a visible row so it never selects the hidden root.
Pressing Delete unloaded the assembly but left the grid showing a row selected while model.SelectedItems went empty -- so a second Delete read an empty selection and no-op'd ("spamming Delete breaks"). Record the deleted node's flattened index and, once the visible tree rebuilds, re-select the node that now occupies that slot (clamped to the new end) via SelectNode, which re-syncs grid and model. Selects nothing when the list empties.
Unload / Clear / ReloadAssembly / HotReplaceAssembly all called LoadedAssembly.Dispose() on the assembly they removed, which disposes its MetadataFile and unmaps the underlying file. But open document tabs and tree nodes can still hold that MetadataFile, and the list has no safe point at which to know those references are gone -- so disposing risked unmapping a file out from under a live reader (use-after-dispose). Drop the removed assembly and let the GC reclaim it once nothing references it instead. The DockWorkspace cancel-on-remove is now a courtesy, not a guard against an unmap race.
Right-clicking a tree row to reach its context menu used to move the real
selection there first, because ProDataGrid selects the row on press. With the
preview document bound to the selection, that meant 'Decompile to new tab' on
node B (while viewing A) jumped the preview to B before the command ran, so you
ended up with B twice instead of the intended A + B. Middle-click avoided it,
but not every mouse has a usable one.
Capture the right-clicked row in ContextRequested (which fires even when a
previous menu's light-dismiss popup swallows the press) and swallow the
right-press so the grid never reselects: the menu now acts on the clicked row
as a Thunderbird-style context target while the selection -- and the document
-- stay put. The targeted row gets a faint focus-box highlight, cleared when
the menu closes (guarded so a stale menu's close can't wipe a newer target).
Also adds TestHarness.ClickItem to collapse the repeated
Items.OfType<MenuItem>().Single(...).RaiseEvent(...) menu-click dance.
Assisted-by: Claude:claude-opus-4-8:Claude Code
To audit what each UI test actually exercises, every step now snapshots the
live window to <TestFixtureName>/<TestName>_<NN>_<ShortDescription>.png: a
booted frame (emitted automatically by TestHarness.BootAsync), one after each
state-changing action, and one before each assertion. Flip ILSPY_TESTS_VISIBLE=1
to render the filmstrip; it lands under %TEMP%/ilspy-test-captures (overridable
via ILSPY_TEST_CAPTURES).
The step number and fixture/test name are derived automatically so inserting a
breakpoint never renumbers the rest. The identity is recorded up front from the
real ITest in an ITestAction hook rather than read live: NUnit's
TestContext.CurrentContext does not flow onto async continuations, so a capture
after an await would otherwise collide under the ad-hoc context. And when
rendering is off the whole call is a true no-op -- not even a dispatcher pump --
so instrumenting a test can never perturb the navigation/tab timing it asserts
on. Full headless suite stays green.
Nearly every headless UI test opened with the same four-line prologue
(resolve the shared MainWindow, show it, cast its DataContext, wait for the
assembly list), then repeated the corelib lookup, the EnsureLazyChildren +
Children.OfType<T>().Single() drill, the registry single-by-header lookups,
and the open-an-assembly-and-wait dance. The duplication made the intent of
each test hard to see and every signature tweak a suite-wide edit.
Collapse those into TestHarness (BootAsync, OpenAssemblyAsync, GetCommand,
GetEntry) and TreeNavigation extensions (FindCoreLib, GetChild<T>, Expand<T>),
then apply them across the suite. Net ~865 lines of boilerplate removed with
no change in behaviour; the full headless suite stays green.
The command resolves each selected assembly's references through the
per-assembly resolver, which adds the targets to the live list as
auto-loaded entries. It then finished with a full list Refresh (F5),
whose LoadList rebuilds the list from persisted state -- which never
contains on-demand auto-loaded assemblies -- so the just-resolved
dependencies were discarded the instant they were added and the command
looked like a no-op. Re-decompile the active view instead (the original
behaviour) without reloading the list. F5 still drops auto-loaded
assemblies by design; the two refresh paths must stay distinct.
Two selection-sync defects in the assembly tree. Ctrl+A only selected
the last row on the first press: SelectedItem is the last entry of
SelectedItems and every collection change re-raised it, so the grid->model
sync bounced back through SyncSelectionFromModel and assigned the singular
SelectedItem, collapsing the just-made multi-selection. The syncingSelection
guard now also covers that notification.
Clicking one row of a multi-selection left every row selected: ProDataGrid
keeps the selection on press so a row-drag can move all of them (multi-row
reorder is a real feature), but 12.0.0 has no release-side collapse for a
plain click. Mirror the usual behaviour by collapsing to the clicked row on
release when the pointer did not move far enough to be a drag.
Two first-run behaviours that the previous version had and the port had
lost. When a launch has nothing to restore (no saved tree path, no
command-line target), greet the user with the About page in the main
tab instead of an empty "(no selection)" view; the greeting is fired
after the tree is ready and only takes the main tab when it's still
empty and active, so it can't steal activation from a tab the user
opened meanwhile (e.g. Compare...). And seed the default list with the
.NET framework ILSpy itself runs on - every managed assembly in the
running runtime's shared-framework directory. Headless tests opt out of
that seed (re-opening ~150 assemblies per test is ~10x slower) and fall
back to the minimal trio, leaving their expectations unchanged.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The dialog lost the entry point for building a preconfigured list when
the view-model wasn't ported; the backing AssemblyListManager.
CreateDefaultList was already present and cross-platform. Re-add the
button and a flyout of the generated lists. The three GAC-based
framework lists resolve nothing without a GAC, so they're only offered
when a GAC directory exists (i.e. on Windows); the per-installed-runtime
entries work everywhere.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The +/- expander was a 13x13 toggle whose visible glyph (a 9x9 box) was
also the only hit-testable surface, so the real tap target was barely
9px. Grow the toggle to 16x16 but keep its laid-out width at 13 via a
negative right margin, because TreeLines hardcodes a 13px expander
column with the glyph centred at +8.5 and would otherwise misalign. A
transparent wrapper fills the 16x16 so the whole area receives input;
the visible glyph is unchanged.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The user reported that expanding an enum (System.DayOfWeek) with a search
term active showed only Base/Derived nodes — every FieldTreeNode child
hidden. This is NOT a recent regression. It traces back to commit
45461ddde, which made LanguageSettings.SearchTermMatches honour the
SearchTerm and wired SearchPaneModel to push its term into
LanguageSettings on every keystroke.
The PE TypeDef table includes a <Module> pseudo-type plus any user-declared
top-level types in the empty namespace. Until now AssemblyTreeNode skipped
the empty namespace when building NamespaceTreeNodes and instead hung the
global types directly off the assembly node, breaking the long-standing
tree shape (every type lives under a namespace folder).
The Reload/Remove/SearchMSDN/OpenContainingFolder/DecompileInNewView
tests used to call `entry.Execute(synthetic TextViewContext)` against
a hand-built context. They now select via the assembly-tree model,
build the context menu through `BuildContextMenuForCurrentState`
(the same call the live `Opening` event makes), and fire the menu
item's `Click` routed event — the same handler the user's click
invokes. This widens the coverage to the menu-build → click-handler
attachment pipeline.
Assisted-by: Claude:claude-opus-4-7:Claude Code
HandleFileDrop now resolves each opened LoadedAssembly to its
AssemblyTreeNode (via AssemblyListTreeNode.FindAssemblyNode) after
any Move and writes the set into model.SelectedItems. Lookup happens
post-Move because Move re-creates the node wrappers — a pre-Move
reference would dangle. Selecting drives the decompiler view to
render the new assembly, which is what makes the LoadAsync task
observable to the user.
Assisted-by: Claude:claude-opus-4-7:Claude Code
ProDataGrid's row-drag pipeline only sees in-grid drags, so external
file drops bypass it entirely. Wires Avalonia's standard DragDrop
pipeline alongside it: DragDrop.AllowDrop on the tree DataGrid plus
DragOver / Drop handlers that read DataFormat.File from the
IDataTransfer, resolve each IStorageItem to a local path, and route
through AssemblyListPane.HandleFileDrop.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Wires ProDataGrid's native row-drag API on the assembly tree:
CanUserReorderRows=True, RowDragHandle=Row, RowDragStarting cancels
drags whose source isn't a top-level AssemblyTreeNode (or is a
package-nested entry). AssemblyRowDropHandler validates that the
target is a sibling (not Inside, not deeper than top-level) and
routes the reorder through AssemblyList.Move so the same persistence
path the rest of the app uses (Unload / OpenAssembly) captures the
new ordering.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Audit had ExitCommand listed as Missing because it grepped for a separate
ExitCommand.cs file (the WPF layout). Avalonia bundles File-menu commands
into FileCommands.cs so the audit missed it. Command was already implemented
correctly — uses IClassicDesktopStyleApplicationLifetime.Shutdown() rather
than mainWindow.Close() (cleaner since Avalonia apps can have multiple
windows).
Assisted-by: Claude:claude-opus-4-7:Claude Code
FindMemberNode's IMethod branch only checked direct MethodTreeNode children of
the type. Accessor methods (get_/set_/add_/remove_/invoke_) live as children of
their owning PropertyTreeNode / EventTreeNode, so the lookup always returned
null — and MMB on a metadata-grid accessor row silently no-opped because
OpenNodeInNewTab couldn't resolve the IMethod to a tree node.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Mirrors WPF's mechanism: ILSpyTreeNode.OnChildrenChanged calls ApplyFilterToChild
on every newly-added child while the parent IsVisible, writing the Filter result
into SharpTreeNode.IsHidden. Because SharpTreeNode.IsVisible defaults to true,
the cascade fires while a node's children are added in its constructor — so a
PropertyTreeNode's get_/set_ accessors are stamped IsHidden=true under the
default ShowApiLevel before the property even attaches to its type. The base
SharpTreeNode.ShowExpander then naturally returns false ("Children.Any(!isHidden)"
sees no visible children), no per-node override needed.
Assisted-by: Claude:claude-opus-4-7:Claude Code
PropertyTreeNode now eagerly adds Getter / Setter as MethodTreeNode children
(when present); EventTreeNode does the same for Add / Remove / Invoke
accessors. The ShowExpander => false overrides drop so the base default
(Children.Any) lights up the chevron when accessors exist.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Each ContentTabPage now carries the SharpTreeNode it represents (SourceNode),
stamped at construction time so the activation event fires with it already
populated. DockWorkspace subscribes to factory.ActiveDockableChanged: when a
new tab becomes active — whether by MMB carve-out, the "Decompile to new tab"
context-menu entry, or the user clicking a different tab in the strip — the
assembly-tree selection is pulled across to that tab's SourceNode. A guard
prevents the change from cascading back into ShowSelectedNode and overwriting
MainTab's content. Existing single-tab reuse path stamps MainTab.SourceNode
on every selection so flipping back to it stays a no-op.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The new-tab path always built a DecompilerTabPageModel, so MMB / context-menu
on a metadata-table tree node forced the node into a decompiler tab and a second
metadata view couldn't coexist with the first. Move the carve-out into a single
DockWorkspace.OpenNodeInNewTab that asks node.CreateTab for custom content
(metadata tables, resource viewers, …) — wiring the same NavigateToCellRequested
/ RowActivated subscriptions AttachCustomContent applies on the reuse path —
and only falls back to spawning a fresh decompiler tab when the node has no
custom page-type. The assembly-tree MMB handler, the metadata-grid MMB handler,
and the "Decompile to new tab" context-menu entry now all delegate to this one
method.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Wires MMB on the assembly tree and the metadata grid to the existing "open in
new tab" path, matching the WPF gesture (DecompileInNewViewCommand carries
InputGestureText = "MMB"). Plain double-click reverts to reusing the active tab;
the unreliable Shift+double-click variant is dropped.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the brittle root.GetVisualDescendants().OfType<T>().Single() / .First()
pattern with a new WaitForComponent<T>() extension that polls until the requested
control is in the visual tree, then returns it. Avalonia.Headless tests routinely
queried the visual tree before lazily-templated panes (DataGrid, dock content)
had materialised, surfacing as intermittent 'Sequence contains no elements'
failures across the suite.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Mirrors the WPF host's nested ReloadAssembly entry. Same visibility shape
as Remove (every selected node must be an AssemblyTreeNode). Execute calls
AssemblyList.ReloadAssembly on each selection's path, snapshots the
pre-reload tree paths so that after the LoadedAssembly instances are swapped
out the model can re-select an equivalent node via FindNodeByPath. The
existing SelectedItem -> ShowSelectedNode plumbing then re-decompiles into
the active tab.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Each tree-expansion, selection change, command execution, async wait and
key/mouse injection inside an [AvaloniaTest] now carries a short imperative
comment on the line above (e.g. "// expand typeNode", "// select methodA",
"// wait for assemblies to load", "// execute aboutCmd"). The comments are
the same scaffolding the manual debug-with-screenshot workflow uses to
follow what's happening at each breakpoint, surfaced into the committed
source so the tests are readable without the breakpoint markers attached.
Assisted-by: Claude:claude-opus-4-7:Claude Code
AssemblyTreeNode now overrides CanDelete / Delete / DeleteCore (matching
the WPF host: Delete unloads the assembly via AssemblyList.Unload, with
package-nested entries opting out via CanDelete=false) and exports a
nested RemoveAssembly context-menu entry tagged Header="_Remove".
Assisted-by: Claude:claude-opus-4-7:Claude Code
AssemblyListPane attaches a ContextMenu to its TreeGrid at construction
time. The menu's Opening handler builds entries from the
ContextMenuEntryRegistry against a fresh TextViewContext (the live
selection + the grid as OriginalSource), and cancels Opening when no
entry would be visible so an empty popup never flashes up.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds a leading paragraph describing what each test verifies and Arrange /
Act / Assert markers (split into numbered phases for multi-step tests) so
the intent of each fixture is readable from comments alone.
Assisted-by: Claude:claude-opus-4-7:Claude Code