{"record":{"id":"9ce35f119265810e","repo":"siyuan-note/siyuan","slug":"source-ids-must-not-be-empty","errorCode":null,"errorMessage":"source IDs must not be empty","messagePattern":"source IDs must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/file.go","lineNumber":2719,"sourceCode":"\t}\n\tmaps.Copy(fullSortIDs, sortIDs)\n\tif writeErr := writeSortConfMap(confPath, fullSortIDs); nil != writeErr {\n\t\tfileTreeSortLock.Unlock()\n\t\treturn ret, writeErr\n\t}\n\tfileTreeSortLock.Unlock()\n\n\tret.Changed = true\n\tret.Notebook = box.ID\n\tret.ParentPath = parentPath\n\tIncSync()\n\tpushFiletreeSortChanged(sortIDs)\n\treturn\n}\n\nfunc validateReorderArgs(sourceIDs []string, targetID, position string) error {\n\tif 1 > len(sourceIDs) {\n\t\treturn errors.New(\"source IDs must not be empty\")\n\t}\n\tif \"before\" != position && \"after\" != position {\n\t\treturn fmt.Errorf(\"invalid reorder position [%s]\", position)\n\t}\n\tseen := map[string]struct{}{}\n\tfor _, sourceID := range sourceIDs {\n\t\tif sourceID == targetID {\n\t\t\treturn fmt.Errorf(\"target ID [%s] must not be included in source IDs\", targetID)\n\t\t}\n\t\tif _, ok := seen[sourceID]; ok {\n\t\t\treturn fmt.Errorf(\"duplicate source ID [%s]\", sourceID)\n\t\t}\n\t\tseen[sourceID] = struct{}{}\n\t}\n\treturn nil\n}\n\nfunc isSortableDocument(tree *treenode.BlockTree) bool {","sourceCodeStart":2701,"sourceCodeEnd":2737,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/file.go#L2701-L2737","documentation":"ReorderDocs / ReorderDocTree validate their inputs through validateReorderArgs before touching any files. This error means the sourceIDs slice passed to the reorder operation was empty (nil or zero length). The reorder operation is defined only as 'move these one or more sibling docs before/after a target doc', so an empty source list is meaningless and rejected immediately.","triggerScenarios":"Calling the kernel API ReorderDocs([]string{}, \"target-id\", \"before\") / ReorderDocTree with an empty sources array; a frontend drag handler that computes the dragged block IDs after the DOM data was already cleared; mapping an empty selection into the API call.","commonSituations":"Plugin or client code building the request from a multi-select that was empty; a race where documents were removed from selection before the reorder request fired; scripts automating SiYuan that pass an unpopulated array variable.","solutions":["Ensure the sources/sourceIDs array contains at least one document ID before calling ReorderDocs/ReorderDocTree","Guard the caller: return early or no-op when the selection is empty instead of issuing the API call","Check the frontend/plugin drag pipeline for losing the selection data before the request is sent"],"exampleFix":"// before\nresult, err := model.ReorderDocs(sourceIDs, targetID, \"before\")\n// after\nif len(sourceIDs) == 0 {\n    return nil // nothing to reorder; skip the call\n}\nresult, err := model.ReorderDocs(sourceIDs, targetID, \"before\")","handlingStrategy":"validation","validationCode":"if len(sourceIDs) == 0 {\n    return errors.New(\"reorder requires at least one source document ID\")\n}","typeGuard":"func hasSources(sourceIDs []string) bool { return len(sourceIDs) > 0 }","tryCatchPattern":"if err := validateSources(sourceIDs); err != nil {\n    // handle locally; do not call ReorderDocs\n    return err\n}","preventionTips":["Always derive sourceIDs from a checked non-empty selection before the API call","No-op early when nothing is selected in drag/selection handlers","Add a caller-side assertion or unit test for empty-input rejection"],"tags":["validation","argument-validation","go"],"backgroundTag":"empty-required-field","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}