{"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":543,"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":525,"sourceCodeEnd":561,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/treenode/blocktree.go#L525-L561","documentation":"CheckListItemNesting rejects a move/insert that would make a list item ('i') the direct child of another list item. The correct nesting in the AST is ListItem > List > ListItem: an inner list must sit between the two list items. It is only checked in move scenarios where both source and target types are known.","triggerScenarios":"Calling blockMove (or validateBlockMove) with parentID pointing to a list item and the moved block itself being a list item, without an intermediate list node.","commonSituations":"Dragging or scripting a list item under another list item expecting plain parent-child nesting; tools that flatten a list and re-parent items; programmatic outline restructuring that skips creating the intermediate NodeList.","solutions":["Create a child list (NodeList) under the outer list item first, then move the inner list item into that list.","Alternatively move the item so it becomes a sibling via previousID=<another list item id> instead of nesting.","Before moving, check both blocks' types with GetBlockTree and refuse/adjust when both are type 'i'."],"exampleFix":"// before\nmoveBlock({id: innerItemID, parentID: outerItemID})\n// after\ncreateBlock({dataType: \"markdown\", data: \"*\", parentID: outerItemID}) // child list\nmoveBlock({id: innerItemID, parentID: newChildListID})","handlingStrategy":"validation","validationCode":"const parent = (await fetchPost('/api/block/getBlockInfo', {id: parentID})).data;\nconst child = (await fetchPost('/api/block/getBlockInfo', {id: childID})).data;\nif (parent.subType === 'o' || parent.type === 'NodeListItem') { /* ensure a child list exists or use previousID */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never move a list item directly under another list item; always insert an intermediate list.","Check both block types before a programmatic move.","Prefer sibling placement (previousID) for list reordering."],"tags":["lists","block-move","ast-structure"],"backgroundTag":"incompatible-source-type","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"}