siyuan-note/siyuan · error
attribute view rich text normalization did not converge
Error message
attribute view rich text normalization did not converge
What it means
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.
Source
Thrown at kernel/av/value.go:2127
for iteration := 0; iteration < 4; iteration++ {
if err = normalizeValueTextRichTreeStyles(normalizedTree); nil != err {
return "", nil, err
}
luteEngine := newValueTextRichLute()
blockDOM := luteEngine.Tree2BlockDOM(normalizedTree, luteEngine.RenderOptions, luteEngine.ParseOptions)
content = valueTextRichBlockDOM2Kramdown(luteEngine, blockDOM)
if 0 < iteration && previous == content {
return content, normalizedTree, nil
}
previous = content
candidate := &ValueTextRich{
Spec: ValueTextRichSpec, Format: ValueTextRichFormatKramdown, Content: content,
}
if _, normalizedTree, err = parseValueTextRich(candidate); nil != err {
return "", nil, err
}
}
return "", nil, fmt.Errorf("attribute view rich text normalization did not converge")
}
// NormalizeRichContent 校验富文本载荷,并根据富文本源刷新纯文本投影。
func (value *ValueText) NormalizeRichContent() (err error) {
if !value.IsRich() {
return
}
tree, err := NormalizeValueTextRich(value.Rich)
if nil != err {
return err
}
value.Content = valueTextRichPlainContent(tree)
return
}
func valueTextRichPlainContent(tree *parse.Tree) string {
if nil == tree || nil == tree.Root {
return ""View on GitHub (pinned to 8641553a1f)
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
Defensive patterns
Strategy: retry
Validate before calling
// detect oscillation before submitting: render twice and compare
const a = lute.BlockDOM2Kramdown(lute.Kramdown2BlockDOM(content))
const b = lute.BlockDOM2Kramdown(lute.Kramdown2BlockDOM(a))
if (a !== b) throw new Error("content does not round-trip stably") Try / catch
try {
await api.normalizeAvValueText(payload)
} catch (e) {
if (String(e).includes("normalization did not converge")) {
payload = { ...payload, rich: { ...payload.rich, content: simplifyStyles(payload.rich.content) } }
await api.normalizeAvValueText(payload)
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- ErrRichTextSpecMismatch
- unsupported attribute view rich text spec [%d]
- unsupported attribute view rich text format [%s]
- parse attribute view rich text failed
- attribute view rich text tree is missing
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/8579d8f51078ab8f.
Report an issue: GitHub.