{"record":{"id":"ef4f1686f6eae863","repo":"siyuan-note/siyuan","slug":"attribute-view-rich-text-style-entity-sentinel-was","errorCode":null,"errorMessage":"attribute view rich text style entity sentinel was not removed from node [%s]","messagePattern":"attribute view rich text style entity sentinel was not removed from node \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/av/value.go","lineNumber":2006,"sourceCode":"\t\t\t}\n\t\t\tnode.Tokens = []byte(literalReplacer.Replace(tokens))\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"invalid encoded style entity in attribute view rich text node [%s]\", node.Type.String())\n\t\t\treturn ast.WalkStop\n\t\t}\n\t\treturn ast.WalkContinue\n\t})\n\tif nil != err {\n\t\treturn\n\t}\n\tfor _, found := range restored {\n\t\tif !found {\n\t\t\treturn fmt.Errorf(\"attribute view rich text style entity was not restored\")\n\t\t}\n\t}\n\tast.Walk(tree.Root, func(node *ast.Node, entering bool) ast.WalkStatus {\n\t\tif entering && valueTextRichNodeContainsSentinel(node, sentinel) {\n\t\t\terr = fmt.Errorf(\"attribute view rich text style entity sentinel was not removed from node [%s]\",\n\t\t\t\tnode.Type.String())\n\t\t\treturn ast.WalkStop\n\t\t}\n\t\treturn ast.WalkContinue\n\t})\n\treturn\n}\n\nfunc valueTextRichNodeContainsSentinel(node *ast.Node, sentinel string) bool {\n\tvalues := []string{\n\t\tnode.ID, node.Spec, node.Data, string(node.Tokens), string(node.CodeBlockOpenFence),\n\t\tstring(node.CodeBlockInfo), string(node.CodeBlockCloseFence), string(node.LinkRefLabel),\n\t\tstring(node.FootnotesRefLabel), node.FootnotesRefId, string(node.HtmlEntityTokens),\n\t\tnode.TextMarkType, node.TextMarkAHref, node.TextMarkATitle, node.TextMarkInlineMathContent,\n\t\tnode.TextMarkInlineMemoContent, node.TextMarkBlockRefID, node.TextMarkBlockRefSubtype,\n\t\tnode.TextMarkFileAnnotationRefID, node.TextMarkFlashcardOcclusionID, node.TextMarkTextContent,\n\t}\n\tfor _, value := range values {","sourceCodeStart":1988,"sourceCodeEnd":2024,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/av/value.go#L1988-L2024","documentation":"After all protection tokens were restored, a final sweep (valueTextRichNodeContainsSentinel) still found the sentinel string somewhere in a node — its tokens, text mark fields, IAL attributes, or properties. Residual sentinel text means the cleanup pass missed a location, and leaving the private-use sentinel characters in the saved value would corrupt future normalization runs.","triggerScenarios":"Normalizing rich text where sentinel characters appear in a node field not covered by the replacement replacers (e.g. link href/title, memo, block ref fields, node properties) — typically because user content itself contained the sentinel sequence or a token was copied into an unhandled field.","commonSituations":"Pasting content that already contains \\ue000/\\ue002 private-use characters; a plugin writing sentinel-like strings into attributes such as memo or title; a restoration pass where one replacer variant did not cover a field the token ended up in.","solutions":["Locate the node type reported in the message and remove the residual sentinel characters from that field","Strip \\ue000-\\ue002 private-use characters from user-supplied content before feeding it to the attribute view API","Re-run normalization on the cleaned value to confirm the sentinel sweep passes"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// strip private-use sentinel characters from all string fields before sending\nfunction scrub(obj) {\n  for (const k in obj) {\n    if (typeof obj[k] === \"string\") obj[k] = obj[k].replace(/[\\ue000-\\ue002]/g, \"\")\n    else if (obj[k] && typeof obj[k] === \"object\") scrub(obj[k])\n  }\n  return obj\n}\npayload = scrub(payload)","typeGuard":"const hasSentinel = (s) => typeof s === \"string\" && /[\\ue000-\\ue002]/.test(s)","tryCatchPattern":"try {\n  await api.normalizeAvValueText(payload)\n} catch (e) {\n  if (String(e).includes(\"sentinel was not removed\")) {\n    payload = scrub(payload)\n    await api.normalizeAvValueText(payload)\n  }\n}","preventionTips":["Strip \\ue000-\\ue002 private-use characters from all user-supplied and pasted content","Never write sentinel sequences into attributes like memo, title, or href","Run the sentinel sweep check on content before submitting it to the API"],"tags":["attribute-view","rich-text","sentinel","cleanup"],"backgroundTag":"internal-invariant-violation","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}