{"record":{"id":"8579d8f51078ab8f","repo":"siyuan-note/siyuan","slug":"attribute-view-rich-text-normalization-did-not-con","errorCode":null,"errorMessage":"attribute view rich text normalization did not converge","messagePattern":"attribute view rich text normalization did not converge","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/av/value.go","lineNumber":2127,"sourceCode":"\tfor iteration := 0; iteration < 4; iteration++ {\n\t\tif err = normalizeValueTextRichTreeStyles(normalizedTree); nil != err {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t\tluteEngine := newValueTextRichLute()\n\t\tblockDOM := luteEngine.Tree2BlockDOM(normalizedTree, luteEngine.RenderOptions, luteEngine.ParseOptions)\n\t\tcontent = valueTextRichBlockDOM2Kramdown(luteEngine, blockDOM)\n\t\tif 0 < iteration && previous == content {\n\t\t\treturn content, normalizedTree, nil\n\t\t}\n\t\tprevious = content\n\t\tcandidate := &ValueTextRich{\n\t\t\tSpec: ValueTextRichSpec, Format: ValueTextRichFormatKramdown, Content: content,\n\t\t}\n\t\tif _, normalizedTree, err = parseValueTextRich(candidate); nil != err {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t}\n\treturn \"\", nil, fmt.Errorf(\"attribute view rich text normalization did not converge\")\n}\n\n// NormalizeRichContent 校验富文本载荷，并根据富文本源刷新纯文本投影。\nfunc (value *ValueText) NormalizeRichContent() (err error) {\n\tif !value.IsRich() {\n\t\treturn\n\t}\n\ttree, err := NormalizeValueTextRich(value.Rich)\n\tif nil != err {\n\t\treturn err\n\t}\n\tvalue.Content = valueTextRichPlainContent(tree)\n\treturn\n}\n\nfunc valueTextRichPlainContent(tree *parse.Tree) string {\n\tif nil == tree || nil == tree.Root {\n\t\treturn \"\"","sourceCodeStart":2109,"sourceCodeEnd":2145,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/av/value.go#L2109-L2145","documentation":"normalizeValueTextRichTreeSource repeatedly re-parses and re-renders the rich text tree, expecting the Kramdown serialization to reach a fixed point. It allows 4 iterations; if the content keeps changing between renders (style normalization and Lute rendering never stabilize), it gives up with this error rather than persisting non-idempotent content. This indicates a parser/renderer oscillation rather than a single malformed token.","triggerScenarios":"Calling av.NormalizeValueTextRich (or saving a ValueText rich payload via NormalizeRichContent) with Kramdown whose styles or markup are rewritten differently on each parse/render cycle — e.g. style attributes that Lute re-orders or re-escapes differently every pass.","commonSituations":"Exotic or adversarial style attribute values (nested entities, mixed quoting) that round-trip unstably; a Lute engine version that serializes some construct differently than it parses it; plugin-authored rich text using constructs outside the supported subset.","solutions":["Simplify the rich text content — remove unusual style attributes or nested markup until normalization succeeds","Identify the oscillating construct by rendering the content twice and diffing the output, then reauthor that portion","Update Lute (rebuild lute.min.js) if the oscillation is caused by a parser/serializer mismatch in the bundled engine"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// detect oscillation before submitting: render twice and compare\nconst a = lute.BlockDOM2Kramdown(lute.Kramdown2BlockDOM(content))\nconst b = lute.BlockDOM2Kramdown(lute.Kramdown2BlockDOM(a))\nif (a !== b) throw new Error(\"content does not round-trip stably\")","typeGuard":null,"tryCatchPattern":"try {\n  await api.normalizeAvValueText(payload)\n} catch (e) {\n  if (String(e).includes(\"normalization did not converge\")) {\n    payload = { ...payload, rich: { ...payload.rich, content: simplifyStyles(payload.rich.content) } }\n    await api.normalizeAvValueText(payload)\n  }\n}","preventionTips":["Pre-check round-trip stability of complex rich text before saving","Avoid exotic style attribute values (nested entities, mixed quoting)","Keep the bundled Lute engine up to date to avoid parser/serializer drift"],"tags":["attribute-view","rich-text","normalization","idempotency"],"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"}