{"record":{"id":"f1f4d70156db871c","repo":"siyuan-note/siyuan","slug":"attribute-s-is-only-supported-on-tab-containers","errorCode":null,"errorMessage":"attribute [%s] is only supported on tab containers","messagePattern":"attribute \\[(.+?)\\] is only supported on tab containers","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/treenode/tabs.go","lineNumber":49,"sourceCode":"\t\tif nil == ret {\n\t\t\tret = item\n\t\t}\n\t\tif item.ID == activeID {\n\t\t\treturn item\n\t\t}\n\t}\n\treturn\n}\n\n// ValidateTabsAttrs 在修改属性前校验页签属性，避免部分属性已写入后才发现错误。\nfunc ValidateTabsAttrs(node *ast.Node, attrs map[string]string) error {\n\tfor name, value := range attrs {\n\t\tname = strings.ToLower(name)\n\t\tif TabsActiveIDAttr != name && TabsPositionAttr != name {\n\t\t\tcontinue\n\t\t}\n\t\tif nil == node || ast.NodeTabs != node.Type {\n\t\t\treturn fmt.Errorf(\"attribute [%s] is only supported on tab containers\", name)\n\t\t}\n\t\tvalue = strings.TrimSpace(util.RemoveInvalidRetainCtrl(value))\n\t\tif \"\" == value {\n\t\t\tcontinue\n\t\t}\n\t\tif TabsPositionAttr == name {\n\t\t\tif \"top\" != value && \"left\" != value {\n\t\t\t\treturn fmt.Errorf(\"invalid tabs position [%s]\", value)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tfound := false\n\t\tfor item := node.FirstChild; nil != item; item = item.Next {\n\t\t\tif ast.NodeTabItem == item.Type && item.ID == value {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/treenode/tabs.go#L31-L67","documentation":"ValidateTabsAttrs enforces that the tab-specific attributes (TabsActiveIDAttr and TabsPositionAttr) are only set on nodes whose AST type is NodeTabs. Setting either attribute on any other node type — or on a nil node — aborts with this error, leaving attributes unchanged.","triggerScenarios":"Calling setNodeAttrs0 (the set node attributes API) with an attribute named like the tabs active-id or tabs-position attribute on a non-tab block, or on a block whose node could not be resolved (nil).","commonSituations":"Plugins bulk-applying attributes without checking node type; typos in attribute names that collide with the reserved tabs attribute names; scripts written against tabs UI copying attributes onto ordinary containers.","solutions":["Verify the target node type is a tab container (NodeTabs) before setting these attributes, e.g. via the block's type field","Remove or rename the attribute if it was not intended as a tabs directive","If the node cannot be resolved (nil), re-fetch the correct block ID first"],"exampleFix":"// before\nsetNodeAttrs(blockID, { \"tabs-position\": \"left\" }); // block is a paragraph\n// after: only set on tab containers\nconst attrs = await getBlockAttrs(blockID);\nif (block.type === \"tabs\") {\n  setNodeAttrs(blockID, { \"tabs-position\": \"left\" });\n}","handlingStrategy":"type-guard","validationCode":"const block = await getBlockByID(blockID);\nif (block.type !== \"tabs\" && (\"tabs-position\" in attrs || \"tabs-active-id\" in attrs)) {\n  throw new Error(\"tabs attributes require a tabs container\");\n}","typeGuard":"const isTabsContainer = (b) => b.type === \"tabs\";","tryCatchPattern":"try {\n  await setNodeAttrs(blockID, attrs);\n} catch (e) {\n  if (String(e.message).includes(\"only supported on tab containers\")) {\n    console.warn(\"skipped tabs attr on non-tab node\", blockID);\n  } else { throw e; }\n}","preventionTips":["Check node type before applying tabs-prefixed attributes","Keep tabs attributes isolated to tab-container mutation helpers","Avoid attribute-name typos that collide with reserved tabs names"],"tags":["attributes","tabs","validation"],"backgroundTag":"invalid-argument-value","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"}