{"record":{"id":"4d52619bd5038ff4","repo":"siyuan-note/siyuan","slug":"block-s-is-not-a-sortable-document","errorCode":null,"errorMessage":"block [%s] is not a sortable document","messagePattern":"block \\[(.+?)\\] is not a sortable document","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/file.go","lineNumber":2615,"sourceCode":"\t}\n\n\tdocPlans := make([]*docSortPlan, 0, len(docSorts))\n\tdocIDs := map[string]struct{}{}\n\tfor _, item := range docSorts {\n\t\tif nil == item {\n\t\t\treturn ret, errors.New(\"document sort item must not be nil\")\n\t\t}\n\t\tif _, ok := docIDs[item.ID]; ok {\n\t\t\treturn ret, fmt.Errorf(\"duplicate document ID [%s]\", item.ID)\n\t\t}\n\t\tdocIDs[item.ID] = struct{}{}\n\n\t\tbt := treenode.GetBlockTree(item.ID)\n\t\tif nil == bt || nil == openedBoxes[bt.BoxID] {\n\t\t\treturn ret, fmt.Errorf(\"document [%s] not found in opened and unlocked notebooks\", item.ID)\n\t\t}\n\t\tif bt.ID != bt.RootID || \"d\" != bt.Type || IsBoxDoc(bt.BoxID, bt.RootID) {\n\t\t\treturn ret, fmt.Errorf(\"block [%s] is not a sortable document\", item.ID)\n\t\t}\n\t\tif nil == boxes[bt.BoxID] {\n\t\t\treturn ret, fmt.Errorf(\"notebook [%s] not found for document [%s]\", bt.BoxID, item.ID)\n\t\t}\n\t\tdocPlans = append(docPlans, &docSortPlan{item: item, boxID: bt.BoxID, parentPath: path.Dir(bt.Path)})\n\t}\n\n\tdocGroups := map[string]*docSortGroup{}\n\tfor _, plan := range docPlans {\n\t\tgroup := docGroups[plan.boxID]\n\t\tif nil == group {\n\t\t\tconfPath := filepath.Join(util.DataDir, plan.boxID, \".siyuan\", \"sort.json\")\n\t\t\tfullSortIDs, readErr := readSortConfMap(confPath)\n\t\t\tif readErr != nil {\n\t\t\t\treturn ret, readErr\n\t\t\t}\n\t\t\tgroup = &docSortGroup{\n\t\t\t\tfullSortIDs: fullSortIDs,","sourceCodeStart":2597,"sourceCodeEnd":2633,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/file.go#L2597-L2633","documentation":"Thrown by SetFileTreeSort when a docSorts entry passes the existence check but the block is not a root document node. Three conditions are checked: bt.ID must equal bt.RootID (the block must be a document root, not a child block within a document), bt.Type must be \"d\" (document node type), and IsBoxDoc(bt.BoxID, bt.RootID) must be false (the block must not be a notebook's top-level box document). Only true child documents within a notebook can be reordered via the sort API.","triggerScenarios":"Calling POST /api/filetree/setFileTreeSort with a docSorts entry whose ID is: (a) a child block within a document (e.g. a paragraph or heading block ID rather than a document root ID), (b) a non-document node, or (c) the top-level box document of a notebook. The box document is the notebook's root .sy file and its sort position is fixed.","commonSituations":"An API client passes a block ID that is a heading or paragraph rather than a document; the UI incorrectly includes a notebook's top-level document in a sub-document sort operation; an external tool fabricates block IDs that happen to be child blocks.","solutions":["Ensure every ID in docSorts is a document root block ID (where block ID == root ID), not a child block within a document.","Exclude notebook top-level documents (box docs) from docSorts — their position is determined by notebookSorts, not document sorting.","If building an API client, verify each ID via the block info endpoint and confirm type is document (\"d\") and ID == rootID before submitting."],"exampleFix":"// before\nawait post('/api/filetree/setFileTreeSort', {\n  notebookSorts: [],\n  docSorts: [{ id: someChildBlockId, sort: 1 }]\n})\n\n// after — only sort document root blocks, never child blocks or box docs\nawait post('/api/filetree/setFileTreeSort', {\n  notebookSorts: [],\n  docSorts: [{ id: docRootId, sort: 1 }]\n})","handlingStrategy":"validation","validationCode":"// Verify each docSorts ID is a document root (not a child block or box doc)\nfunction isSortableDocument(bt) {\n  return bt && bt.id === bt.rootID && bt.type === 'd' && !bt.isBoxDoc\n}\nconst sortableDocSorts = docSorts.filter(item => isSortableDocument(blockInfo[item.id]))","typeGuard":"// Type guard: check if a block is a sortable document root\nfunction isSortableDocumentRoot(bt: { id: string; rootID: string; type: string; isBoxDoc?: boolean }): boolean {\n  return bt.id === bt.rootID && bt.type === 'd' && !bt.isBoxDoc\n}","tryCatchPattern":null,"preventionTips":["Only include document root block IDs (where block ID == root ID) in docSorts.","Never include notebook top-level documents (box docs) in document-level sort operations.","Verify block type is 'd' before including in sort payloads."],"tags":["validation","filetree","sort","block-type"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}