{"record":{"id":"7950447655b9b0b5","repo":"siyuan-note/siyuan","slug":"field-notebook-must-not-be-empty","errorCode":null,"errorMessage":"Field [notebook] must not be empty","messagePattern":"Field \\[notebook\\] must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/block_remaining.go","lineNumber":146,"sourceCode":"\t\t\t\tif !bytes.Equal(entry, []byte(\"null\")) && json.Unmarshal(entry, &id) == nil {\n\t\t\t\t\trequest.IDs = append(request.IDs, id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn request, errors.New(\"invalid block ref check scope\")\n\t}\n\tif raw := fields[\"notebook\"]; len(raw) != 0 && !bytes.Equal(raw, []byte(\"null\")) {\n\t\tif json.Unmarshal(raw, &request.Notebook) != nil {\n\t\t\treturn request, errors.New(\"Field [notebook] should be of type [String]\")\n\t\t}\n\t} else if request.Scope == \"notebook\" {\n\t\treturn request, errors.New(\"Field [notebook] is required\")\n\t}\n\tif request.Scope == \"notebook\" {\n\t\trequest.Notebook = strings.TrimSpace(request.Notebook)\n\t\tif request.Notebook == \"\" {\n\t\t\treturn request, errors.New(\"Field [notebook] must not be empty\")\n\t\t}\n\t}\n\t_ = json.Unmarshal(fields[\"id\"], &request.ID)\n\treturn\n}\n\nfunc decodeHeadingLevel(reader io.Reader) (request HeadingLevelRequest, err error) {\n\tfields, err := blockRequestFields(reader, \"/api/block/getHeadingLevelTransaction\")\n\tif err != nil {\n\t\treturn request, err\n\t}\n\tif len(fields[\"level\"]) == 0 || bytes.Equal(fields[\"level\"], []byte(\"null\")) || json.Unmarshal(fields[\"level\"], &request.Level) != nil {\n\t\treturn request, errors.New(\"Field [level] should be of type [Number]\")\n\t}\n\tvar entries []json.RawMessage\n\tif raw := fields[\"ids\"]; len(raw) > 0 && !bytes.Equal(raw, []byte(\"null\")) && json.Unmarshal(raw, &entries) == nil {\n\t\trequest.IDs = []string{}\n\t\tseen := map[string]bool{}","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/block_remaining.go#L128-L164","documentation":"For /api/block/checkBlockRef with scope \"notebook\", the notebook field must be non-empty after trimming whitespace. This error fires when the provided notebook value consists only of spaces or trims to an empty string, distinguishing it from [215] which covers a fully absent/null notebook. It enforces that a real notebook ID is supplied.","triggerScenarios":"POST /api/block/checkBlockRef with {\"scope\":\"notebook\",\"notebook\":\"   \"}; a notebook variable that was whitespace because an input field was never filled with an actual ID.","commonSituations":"Copy-paste of an ID that lost its content; form input bound to a whitespace default; whitespace-only values passing a naive truthiness check (\" \" is truthy in JS) before the request is built.","solutions":["Supply the actual notebook ID (format 20240101120000-xxxxxxx) rather than placeholder whitespace","Validate with notebook.trim().length > 0 before sending when scope is \"notebook\"","Read the ID from the opened notebook list (kernel /api/notebook/lsNotebooks) instead of user free-text"],"exampleFix":"// before\nconst notebook = input.value; // \"   \"\nawait fetchPost(\"/api/block/checkBlockRef\", { scope: \"notebook\", notebook, ids });\n// after\nif (notebook.trim()) {\n  await fetchPost(\"/api/block/checkBlockRef\", { scope: \"notebook\", notebook: notebook.trim(), ids });\n}","handlingStrategy":"validation","validationCode":"if (payload.scope === 'notebook' && !payload.notebook?.trim()) throw new Error('notebook must be a non-empty ID');","typeGuard":"const hasRealNotebook = (p) => p.scope !== 'notebook' || (typeof p.notebook === 'string' && p.notebook.trim().length > 0);","tryCatchPattern":"try { await fetchPost('/api/block/checkBlockRef', payload); } catch (e) { if (/Field \\[notebook\\] must not be empty/.test(e?.message)) { /* re-fetch notebook ID from /api/notebook/lsNotebooks */ } }","preventionTips":["Trim notebook values before sending","Reject whitespace-only IDs at input boundaries with trim() checks, not truthiness","Select notebook IDs from kernel listings instead of free-text input"],"tags":["api","validation","empty-value"],"backgroundTag":"empty-required-field","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"}