{"record":{"id":"7e60dff773a591da","repo":"siyuan-note/siyuan","slug":"block-swap-insertion-position-has-changed","errorCode":null,"errorMessage":"block swap insertion position has changed","messagePattern":"block swap insertion position has changed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/transaction_block_swap.go","lineNumber":262,"sourceCode":"\t\t\t}\n\t\t}\n\t\tif previousID != fragment.previousID || nextID != fragment.nextID {\n\t\t\treturn fmt.Errorf(\"block position changed since conversion: %s\", fragment.node.ID)\n\t\t}\n\t\tfor _, id := range node.BlockIDs() {\n\t\t\tremoving[id] = true\n\t\t}\n\t}\n\tavailable := map[string]bool{}\n\tfor _, fragment := range to {\n\t\ttree := trees[fragment.rootID]\n\t\tif tree == nil || tree.Box != fragment.boxID {\n\t\t\treturn errors.New(\"block swap notebook has changed\")\n\t\t}\n\t\tif fragment.previousID != \"\" && !available[fragment.previousID] {\n\t\t\tprevious := treenode.GetNodeInTree(tree, fragment.previousID)\n\t\t\tif previous == nil || previous.Parent != tree.Root || removing[previous.ID] {\n\t\t\t\treturn errors.New(\"block swap insertion position has changed\")\n\t\t\t}\n\t\t}\n\t\tfor _, id := range fragment.node.BlockIDs() {\n\t\t\tif existing := treenode.GetBlockTreeInBox(id, fragment.boxID); existing != nil && !removing[id] {\n\t\t\t\treturn fmt.Errorf(\"block swap ID already exists: %s\", id)\n\t\t\t}\n\t\t\tfor _, currentTree := range trees {\n\t\t\t\tif treenode.GetNodeInTree(currentTree, id) != nil && !removing[id] {\n\t\t\t\t\treturn fmt.Errorf(\"block swap ID already exists: %s\", id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tavailable[fragment.node.ID] = true\n\t}\n\tfor _, fragment := range from {\n\t\ttreenode.GetNodeInTree(trees[fragment.rootID], fragment.node.ID).Unlink()\n\t}\n\tfor _, fragment := range to {","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/transaction_block_swap.go#L244-L280","documentation":"For each 'to' fragment, the replay verifies the recorded insertion anchor (previousID) still exists as a direct child of the document root and is not scheduled for removal. If the anchor block is gone, was reparented deeper, or is itself being swapped away, the intended insertion position no longer exists, so the fragment cannot be restored safely and the transaction is aborted.","triggerScenarios":"replayBlockSwap -> restoreBlockSwapFragments: fragment.previousID is set, not yet marked available, and treenode.GetNodeInTree returns nil, the node's parent is not tree.Root, or the node is in the removing set — i.e. the anchor sibling was deleted, moved, or is being removed by the same swap.","commonSituations":"Concurrent deletion of the block next to the insertion point; an import/undo that restructured the document; ordering conflicts after multi-device sync; replaying an old swap whose anchor was since moved into a nested container.","solutions":["Check the anchor block (fragment.previousID) still exists as a top-level child and undo changes that removed or nested it","Sync the notebook to converge both sides, then re-capture and retry the swap","Re-prepare the block swap against the current document structure","Discard the stale transaction if the intended position no longer makes sense"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Go: ensure the insertion anchor is still a root-level child and not being removed\nprevious := treenode.GetNodeInTree(tree, fragment.previousID)\nif previous == nil || previous.Parent != tree.Root || removing[previous.ID] {\n    return errors.New(\"block swap insertion position has changed\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not delete or nest the anchor block while a swap is pending","Re-capture swap state after any structural edit to the document","Converge sync before replaying multi-device transactions"],"tags":["go","transaction","block-swap","positioning"],"backgroundTag":"invalid-state-transition","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}