{"record":{"id":"d405a82bc960343c","repo":"siyuan-note/siyuan","slug":"block-s-type-q-is-a-leaf-block-and-cannot-have","errorCode":null,"errorMessage":"block [%s] type %q is a leaf block and cannot have children; use previousID to place the block as its sibling instead","messagePattern":"block \\[(.+?)\\] type %q is a leaf block and cannot have children; use previousID to place the block as its sibling instead","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/treenode/blocktree.go","lineNumber":530,"sourceCode":"\n// CheckContainerParent 校验 parentID 指向的块是否允许接收子块。\n// 仅在“通过 parentID 定位插入/移动目标”（即不依赖 previousID/nextID）的场景下调用，\n// 因为一旦带 previousID/nextID，事务层走的是兄弟级 InsertAfter/InsertBefore，天然合法。\n// 返回 nil 表示合法；返回 error 时调用方应拒绝本次操作。\nfunc CheckContainerParent(parentID string) error {\n\tbt := GetBlockTree(parentID)\n\tif nil == bt {\n\t\treturn fmt.Errorf(\"parent block not found: %s\", parentID)\n\t}\n\tif IsContainerType(bt.Type) {\n\t\treturn nil\n\t}\n\tif \"h\" == bt.Type {\n\t\t// 标题是叶子块，其“子内容”在数据结构上实为后续兄弟节点（由 HeadingChildren 按层级推算）。\n\t\t// 把块挂成标题的 AST 子节点属于非法嵌套，应改用 previousID 定位。\n\t\treturn fmt.Errorf(\"heading [%s] is a leaf block and cannot have children; to place a block below this heading, pass previousID=<heading id> or previousID=<last block below the heading> instead of parentID\", parentID)\n\t}\n\treturn fmt.Errorf(\"block [%s] type %q is a leaf block and cannot have children; use previousID to place the block as its sibling instead\", parentID, bt.Type)\n}\n\n// CheckListItemNesting 校验 parentID 和 childID 是否形成“列表项直含列表项”的非法嵌套。\n// 嵌套列表的正确结构是 ListItem > List > ListItem，列表项不能直接作为另一个列表项的子块。\n// 仅在 move 场景调用（源和目标类型均已知）。\nfunc CheckListItemNesting(parentID, childID string) error {\n\tparentBt := GetBlockTree(parentID)\n\tchildBt := GetBlockTree(childID)\n\tif nil == parentBt || nil == childBt {\n\t\treturn nil // 查不到就放行，不阻塞未知场景\n\t}\n\tif \"i\" == parentBt.Type && \"i\" == childBt.Type {\n\t\treturn fmt.Errorf(\"a list-item cannot directly contain another list-item; to nest, first create a list (NodeList) under the outer list-item, then add the inner list-items to that list\")\n\t}\n\treturn nil\n}\n\nfunc SetBlockTreePath(tree *parse.Tree) {","sourceCodeStart":512,"sourceCodeEnd":548,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/treenode/blocktree.go#L512-L548","documentation":"CheckContainerParent validates that a parentID given to insert/append/move operations points to a container block. Some block types (headings 'h' and other leaf blocks) cannot structurally hold children in the AST, so passing one as parentID would create illegal nesting. The kernel rejects the operation and tells you to use previousID instead so the block becomes a sibling positioned after the leaf.","triggerScenarios":"Calling blockInsert/blockAppend/blockPrepend, assetCreateHTML, or validateBlockMove with parentID set to a heading block or any leaf block type (paragraphs, headings, etc.) instead of a container such as a document, blockquote, or list item.","commonSituations":"Scripts or plugins that want content 'under a heading' pass the heading's ID as parentID; in SiYuan's data model that content is a following sibling, not a child. Common when migrating code from other outliners or when a user copies a heading ID from the UI.","solutions":["Replace parentID=<heading id> with previousID=<heading id> so the new block is placed directly below the heading as a sibling.","If the block must go after existing content below the heading, pass previousID=<id of the last block currently below the heading>.","Fetch the target block's type first (GetBlockTree) and only use parentID for container types; otherwise use previousID."],"exampleFix":"// before\ninsertBlock({dataType: \"markdown\", data: \"para\", parentID: headingID})\n// after\ninsertBlock({dataType: \"markdown\", data: \"para\", previousID: headingID})","handlingStrategy":"validation","validationCode":"const bt = await fetchPost('/api/block/getBlockInfo', {id: headingID});\nif (bt.data.type === 'h') { /* use previousID instead of parentID */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat headings, paragraphs, and other leaf blocks as siblings-only targets; reserve parentID for containers.","Check block type via getBlockInfo before choosing parentID vs previousID.","When inserting 'below' anything, default to previousID."],"tags":["blocks","invalid-argument","ast-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"}