{"record":{"id":"80ce821af9b08642","repo":"siyuan-note/siyuan","slug":"invalid-attribute-view-source-d-isdetached-must-be-a-boolean","errorCode":null,"errorMessage":"invalid attribute view source [%d]: isDetached must be a boolean","messagePattern":"invalid attribute view source \\[(.+?)\\]: isDetached must be a boolean","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view.go","lineNumber":6076,"sourceCode":"\t\treturn\n\t}\n\tslices.Reverse(srcs) // https://github.com/siyuan-note/siyuan/issues/11286\n\tattrView, err := avParseView(avID, dbBlockID)\n\tif err != nil {\n\t\treturn\n\t}\n\n\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}","sourceCodeStart":6058,"sourceCodeEnd":6094,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view.go#L6058-L6094","documentation":"In the same batch validation, if an entry contains an isDetached key whose value is not a boolean (and not absent), the batch is rejected with this error. Serialized bindings may omit isDetached (treated as false), but an explicitly present non-boolean value — e.g. the string \"false\" or the number 0 — is invalid.","triggerScenarios":"Calling the batch bind API with srcs[i].isDetached set to a non-boolean such as \"true\", 1, or null-derived values from a loosely typed client.","commonSituations":"JavaScript clients sending stringified booleans from form inputs; payloads round-tripped through CSV/JSON where booleans became strings; hand-written API calls.","solutions":["Coerce isDetached to a real boolean (Boolean(value) or === comparison) before sending","Omit the key entirely when the value is false, matching the omission-tolerant format","Fix the client serializer to preserve JSON boolean types"],"exampleFix":"// before\n{ id: blockID, isDetached: \"true\" }\n// after\n{ id: blockID, isDetached: true }","handlingStrategy":"validation","validationCode":"if ('isDetached' in src && typeof src.isDetached !== 'boolean') {\n  throw new Error('isDetached must be boolean');\n}","typeGuard":"function hasBooleanIsDetached(s) {\n  return !('isDetached' in s) || typeof s.isDetached === 'boolean';\n}","tryCatchPattern":"try {\n  await api.bindSources(srcs);\n} catch (e) {\n  if (/isDetached must be a boolean/.test(e.message)) {\n    srcs = srcs.map(s => ({ ...s, isDetached: Boolean(s.isDetached) }));\n  } else { throw e; }\n}","preventionTips":["Coerce form/CLI string booleans to real booleans before API calls","Omit isDetached when false — the format tolerates omission","Enable strict JSON typing in your client serializer"],"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"}