siyuan-note/siyuan · error

cannot swap a heading with a block in its section

Error message

cannot swap a heading with a block in its section

What it means

When IncludeChildren is enabled and the definition block is a heading, the swap is refused if the reference block lies anywhere inside that heading's section (the heading plus its following child blocks until the next heading). Swapping would tear the section apart, so HeadingChildren is scanned with contains() to reject it.

Solutions

  1. Disable includeChildren in the swap options if the section grouping should not apply
  2. Swap the ref block with a block outside the heading's section
  3. Move the ref block out of the heading's section first, then perform the swap

Example fix

// before
data: {"includeChildren":true,"originalToEmbed":false} // ref lies inside def heading's section
// after
data: {"includeChildren":false,"originalToEmbed":false} // or choose a ref outside the section
Defensive patterns

Strategy: validation

Validate before calling

if (options.includeChildren && isHeading(defBlock)) {
  const section = headingChildrenIds(defBlock); // blocks under heading until next heading
  if (section.includes(op.id)) throw new Error('ref block lies inside the heading section');
}

Type guard

function isHeading(node) { return node != null && node.type === 'h'; }

Prevention

When it happens

Trigger: Calling swap-block-ref with originalToEmbed/includeChildren semantics where def is a NodeHeading and ref (operation.ID) is one of the blocks under that heading's section.

Common situations: Swapping a paragraph that sits below a heading with that heading itself while 'include children' is on; outline-level operations that treat a heading and its section as one unit.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/0b0558dcd4593ef1. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/transaction_block_swap.go:112

	}
	if def.Parent.Type == ast.NodeListItem {
		def = def.Parent
	}
	contains := func(parent, node *ast.Node) bool {
		for ; node != nil; node = node.Parent {
			if node == parent {
				return true
			}
		}
		return false
	}
	if contains(ref, def) || contains(def, ref) {
		return errors.New("cannot swap a block with itself or its ancestor")
	}
	if includeChildren && def.Type == ast.NodeHeading {
		for _, child := range treenode.HeadingChildren(def) {
			if contains(child, ref) {
				return errors.New("cannot swap a heading with a block in its section")
			}
		}
	}
	return nil
}

func captureBlockSwapFragments(trees []*parse.Tree) (ret []blockSwapFragment) {
	for _, tree := range trees {
		var previousID string
		for node := tree.Root.FirstChild; node != nil; node = node.Next {
			if !node.IsBlock() || node.ID == "" {
				continue
			}
			fragment := blockSwapFragment{rootID: tree.ID, boxID: tree.Box, previousID: previousID, node: cloneBlockSwapNode(node)}
			for next := node.Next; next != nil; next = next.Next {
				if next.IsBlock() && next.ID != "" {
					fragment.nextID = next.ID
					break

View on GitHub (pinned to 9f775e8a12)