{"record":{"id":"259402b1f38ab57e","repo":"siyuan-note/siyuan","slug":"a-list-item-cannot-directly-contain-another-list-i","errorCode":null,"errorMessage":"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","messagePattern":"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","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/treenode/blocktree.go","lineNumber":565,"sourceCode":"\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) {\n\tRemoveBlockTreesByRootID(tree.Box, tree.ID)\n\tIndexBlockTree(tree)\n}\n\nfunc RemoveBlockTreesByRootID(boxID, rootID string) {\n\tsqlStmt := \"DELETE FROM blocktrees WHERE root_id = ?\"\n\t_, err := execForBox(boxID, sqlStmt, rootID)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"sql exec [%s] failed: %s\", sqlStmt, err)\n\t\treturn\n\t}\n}\n","sourceCodeStart":547,"sourceCodeEnd":583,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/treenode/blocktree.go#L547-L583","documentation":"Returned by CheckListItemNesting in kernel/treenode/blocktree.go when both the resolved parent and child blocks have Type \"i\" (list item). SiYuan's correct nested-list structure is ListItem > List > ListItem; a list item may not be a direct AST child of another list item. CheckListItemNesting is only called in the move scenario where both source and target types are known.","triggerScenarios":"A move-block operation whose target parent (parentID) is a list item and whose moved block is also a list item. Invoked from api/block_op.go, MCP tools/block.go, and cli/cmd/block.go move handlers.","commonSituations":"Dragging a list item into another list item expecting sub-nesting, without the intermediate List node; programmatic re-parenting of list items.","solutions":["Create or locate the NodeList child under the outer list-item first, then move the inner list-item under that list.","Use the editor's drag/drop (which builds the intermediate List) instead of a raw parentID move.","If no list exists under the outer item, insert one (NodeList) before moving the inner item."],"exampleFix":"// before\nMoveBlock(parentID: outerListItemID, id: innerListItemID) // -> cannot directly contain\n\n// after: nest via an intermediate NodeList\nlistID := createListUnderListItem(outerListItemID)\nMoveBlock(parentID: listID, id: innerListItemID)","handlingStrategy":"validation","validationCode":"// Pre-check list-item-to-list-item moves before calling the move API.\nparent := treenode.GetBlockTree(parentID)\nchild := treenode.GetBlockTree(childID)\nif parent != nil && child != nil && parent.Type == \"i\" && child.Type == \"i\" {\n    return fmt.Errorf(\"nest via an intermediate NodeList under list-item %s\", parentID)\n}\nreturn nil","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember the list nesting rule: ListItem > List > ListItem, never ListItem > ListItem.","Ensure an intermediate NodeList exists (or create one) before moving an item into another item.","Prefer the editor drag/drop path for list nesting; it creates the intermediate List for you."],"tags":["block","list","list-item","nesting","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}