What's wrong
Three pieces of code combine to lose nodes when the target is a Many slot (a statement body, parameters, arguments or members):
AstSchema.TryAttachAt (Coder.Graph/AstSchema.cs ~L202) always replaces in place when index < Count. It never inserts.
AstGraph.MoveTo (Coder.Graph/AstGraph.cs ~L318–339) detaches the node first, which shifts every later sibling down by one, and then attaches at the original index.
- The editor undoes a move with
Graph.MoveTo(node, from): RecordMove in AstGraphEditor.cs ~L1615, plus the Remove/Detach inverses ~L1542/L1601. Undo therefore overwrites whichever sibling has shifted into the old index.
The overwritten node is not added to detached. It drops out of the graph and the document with no warning. This contradicts MoveTo's own comment that "the node stays in the graph rather than vanishing".
Failure scenarios (reproduced headlessly through AstGraph / AstGraphEditor)
The body is [s0, s1, s2], where each sN is an ExpressionStatement(CallExpression).
Undo destroys a statement:
remove s1: s0, s2
undo: s0, s1 detached=0 <- s2 is gone
disconnect s1: s0, s2
undo: s0, s1 detached=0 <- s2 is gone
The same happens through the new pin context menu's Delete and Disconnect items (RemoveFromPin / DisconnectPin).
Connecting within a list or onto a filled pin:
editor.Connect(s0 -> Body[1]): s1, s0 <- s2 deleted; s1 (the pin's actual occupant) kept
undo: s0 <- s1 deleted too; s0, s1, s2 cannot be recovered
editor.Connect(loose x -> Body[0]) on [s0, s1]: x, s1 <- s0 not detached, just gone
undo: s1, detached=1 <- s0 cannot be recovered
Replacing the occupant when you drop onto a filled pin may be intended. Replacing the wrong sibling after the index shift is not. Neither is losing the displaced node permanently.
Undo is supposed to be the safe way back, and here it silently deletes user code. The existing tests only cover single-operand slots (e.g. Binary.Left) or TryAttachAt on its own, so this path has no coverage.
Suggested fix
- Separate "insert at" from "replace at": add
AstSchema.TryInsertAt, and have MoveTo use insert semantics for Many slots. At minimum, the undo path restoring a node to its original location should insert.
- When the source and target are the same parent and slot, adjust the target index after the detach (decrement it if the source index was lower).
- If a drop onto a filled pin should keep replacing the occupant, move the displaced occupant into
detached and record a composite step that re-attaches it on undo, as Replace already does for the children it moves.
Acceptance criteria
Regression tests, each ending with a body/list identical to the starting one after undo and nothing missing from the graph:
- remove or disconnect a middle statement, then undo
- connect
Body[0] onto Body[1], then undo
- connect a loose node onto a filled sequence pin, then undo
What's wrong
Three pieces of code combine to lose nodes when the target is a
Manyslot (a statement body, parameters, arguments or members):AstSchema.TryAttachAt(Coder.Graph/AstSchema.cs~L202) always replaces in place whenindex < Count. It never inserts.AstGraph.MoveTo(Coder.Graph/AstGraph.cs~L318–339) detaches the node first, which shifts every later sibling down by one, and then attaches at the original index.Graph.MoveTo(node, from):RecordMoveinAstGraphEditor.cs~L1615, plus the Remove/Detach inverses ~L1542/L1601. Undo therefore overwrites whichever sibling has shifted into the old index.The overwritten node is not added to
detached. It drops out of the graph and the document with no warning. This contradictsMoveTo's own comment that "the node stays in the graph rather than vanishing".Failure scenarios (reproduced headlessly through
AstGraph/AstGraphEditor)The body is
[s0, s1, s2], where eachsNis anExpressionStatement(CallExpression).Undo destroys a statement:
The same happens through the new pin context menu's Delete and Disconnect items (
RemoveFromPin/DisconnectPin).Connecting within a list or onto a filled pin:
Replacing the occupant when you drop onto a filled pin may be intended. Replacing the wrong sibling after the index shift is not. Neither is losing the displaced node permanently.
Undo is supposed to be the safe way back, and here it silently deletes user code. The existing tests only cover single-operand slots (e.g.
Binary.Left) orTryAttachAton its own, so this path has no coverage.Suggested fix
AstSchema.TryInsertAt, and haveMoveTouse insert semantics forManyslots. At minimum, the undo path restoring a node to its original location should insert.detachedand record a composite step that re-attaches it on undo, asReplacealready does for the children it moves.Acceptance criteria
Regression tests, each ending with a body/list identical to the starting one after undo and nothing missing from the graph:
Body[0]ontoBody[1], then undo