{"record":{"id":"6df5a8d6e2cc622c","repo":"siyuan-note/siyuan","slug":"cannot-swap-a-block-with-itself-or-its-ancestor","errorCode":null,"errorMessage":"cannot swap a block with itself or its ancestor","messagePattern":"cannot swap a block with itself or its ancestor","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/transaction_block_swap.go","lineNumber":107,"sourceCode":"\tif ref == nil || def == nil || ref.Parent == nil || def.Parent == nil {\n\t\treturn errors.New(\"block swap requires two non-document blocks\")\n\t}\n\tif ref.Parent.Type == ast.NodeListItem {\n\t\tref = ref.Parent\n\t}\n\tif def.Parent.Type == ast.NodeListItem {\n\t\tdef = def.Parent\n\t}\n\tcontains := func(parent, node *ast.Node) bool {\n\t\tfor ; node != nil; node = node.Parent {\n\t\t\tif node == parent {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\tif contains(ref, def) || contains(def, ref) {\n\t\treturn errors.New(\"cannot swap a block with itself or its ancestor\")\n\t}\n\tif includeChildren && def.Type == ast.NodeHeading {\n\t\tfor _, child := range treenode.HeadingChildren(def) {\n\t\t\tif contains(child, ref) {\n\t\t\t\treturn errors.New(\"cannot swap a heading with a block in its section\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc captureBlockSwapFragments(trees []*parse.Tree) (ret []blockSwapFragment) {\n\tfor _, tree := range trees {\n\t\tvar previousID string\n\t\tfor node := tree.Root.FirstChild; node != nil; node = node.Next {\n\t\t\tif !node.IsBlock() || node.ID == \"\" {\n\t\t\t\tcontinue\n\t\t\t}","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/transaction_block_swap.go#L89-L125","documentation":"The two blocks to swap have an ancestor/descendant relationship (or are the same block). Swapping a block with itself or with something that contains it would create a cycle in the tree, so validateBlockSwap refuses it via the contains() helper that walks each node's ancestors.","triggerScenarios":"Calling swap-block-ref with operation.ID equal to operation.BlockID; or one block ID being a heading/parent block that contains the other (e.g. swapping a container/parent block with one of its children).","commonSituations":"Selecting a whole outline/heading section and asking to swap with a block inside it; recursive or scripted swaps where IDs were computed from the same node; UI drag logic passing the drop target's ancestor.","solutions":["Pick two sibling-level blocks that do not contain each other","If a parent-child swap is intended, restructure as a move operation instead of a swap","Check that the ref ID and def ID differ and neither is an ancestor of the other before submitting"],"exampleFix":"// before\n// def block is a child of ref block -> rejected\n// after\n// choose two blocks with no containment, e.g. two sibling paragraphs\n{\"action\":\"swap-block-ref\",\"id\":\"<paragraphA>\",\"blockID\":\"<paragraphB>\"}","handlingStrategy":"validation","validationCode":"if (op.id === op.blockID) throw new Error('cannot swap a block with itself');\nif (isAncestor(op.id, op.blockID) || isAncestor(op.blockID, op.id)) throw new Error('ancestor/descendant swap not allowed');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Walk each node's parent chain to confirm no containment before swapping","For parent-child restructuring use move operations, not swaps"],"tags":["validation","block-swap","tree-structure"],"backgroundTag":"invalid-argument-value","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"}