{"record":{"id":"d6a59d71b8a80ff1","repo":"siyuan-note/siyuan","slug":"invalid-attribute-view-source-d-id-must-be-a-string","errorCode":null,"errorMessage":"invalid attribute view source [%d]: id must be a string","messagePattern":"invalid attribute view source \\[(.+?)\\]: id must be a string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view.go","lineNumber":6084,"sourceCode":"\t// 整批校验完成后再写入，避免后续条目越界时已修改前面的绑定。\n\tboundTrees := map[string]*parse.Tree{}\n\tdetachedSources := make([]bool, len(srcs))\n\tfor index, src := range srcs {\n\t\tif nil == src {\n\t\t\treturn result, fmt.Errorf(\"invalid attribute view source [%d]\", index)\n\t\t}\n\t\t// 绑定条目的序列化数据可能省略值为 false 的字段，撤销重放时按绑定状态读取。\n\t\tisDetached, ok := src[\"isDetached\"].(bool)\n\t\tif !ok && nil != src[\"isDetached\"] {\n\t\t\treturn result, fmt.Errorf(\"invalid attribute view source [%d]: isDetached must be a boolean\", index)\n\t\t}\n\t\tdetachedSources[index] = isDetached\n\t\tif isDetached {\n\t\t\tcontinue\n\t\t}\n\t\tid, ok := src[\"id\"].(string)\n\t\tif !ok {\n\t\t\treturn result, fmt.Errorf(\"invalid attribute view source [%d]: id must be a string\", index)\n\t\t}\n\t\tif !ast.IsNodeIDPattern(id) {\n\t\t\tcontinue\n\t\t}\n\t\tvar tree *parse.Tree\n\t\tif tx != nil {\n\t\t\ttree, err = tx.loadTree(id)\n\t\t} else {\n\t\t\ttree, err = LoadTreeByBlockID(id)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif err = validateAttributeViewBinding(avID, tree); err != nil {\n\t\t\treturn\n\t\t}\n\t\tboundTrees[id] = tree\n\t}","sourceCodeStart":6066,"sourceCodeEnd":6102,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view.go#L6066-L6102","documentation":"For non-detached entries in the batch, the id field must be a string block ID. If src[i].id is missing, null, or of another type, the whole batch is rejected with this error. (A string ID that merely fails the node-ID pattern is skipped, not an error.)","triggerScenarios":"Calling the batch bind API where a non-detached entry lacks a string id — e.g. {isDetached: false} with no id, or id as a number from a numeric row key.","commonSituations":"Clients building payloads from table rows where IDs were coerced to numbers; entries whose id was stripped by a partial serializer; mixing detached and bound entries with the wrong shape.","solutions":["Ensure every non-detached entry has id as a string (String(id) if needed)","Skip detached entries — they must not carry/require an id — and validate the payload shape before sending","Fix the data source so block IDs are not coerced to numbers"],"exampleFix":"// before\n{ id: 12345, isDetached: false }\n// after\n{ id: \"20240101120000-abcd123\", isDetached: false }","handlingStrategy":"validation","validationCode":"if (!src.isDetached && typeof src.id !== 'string') {\n  throw new Error(`entry ${i}: id must be a string`);\n}","typeGuard":"function hasStringId(s) { return s.isDetached === true || typeof s.id === 'string'; }","tryCatchPattern":"try {\n  await api.bindSources(srcs);\n} catch (e) {\n  if (/id must be a string/.test(e.message)) {\n    srcs = srcs.map(s => s.isDetached ? s : { ...s, id: String(s.id) });\n  } else { throw e; }\n}","preventionTips":["Keep block IDs as strings end-to-end; watch for numeric coercion from row keys","Validate payload shape (detached vs bound entries) before sending","Only include id on bound (non-detached) entries"],"tags":["attribute-view","validation","type-mismatch"],"backgroundTag":"type-mismatch","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"}