siyuan-note/siyuan · error
table cell rich text projection structure changed
Error message
table cell rich text projection structure changed
What it means
During projection of a table cell's rich text from an inline source (e.g. code block or math block content), the kernel re-syncs the projected inline nodes back onto the origin nodes. This error means the projected node sequence no longer matches the expected structure: a node at the same position has a different ast.Node type, so the walker cannot map projection nodes back to their origin.
Solutions
- Reload the document in the current SiYuan version so the cell is re-parsed and re-projected from source
- Inspect the table cell's .sy JSON and fix the projected inline nodes so their types match the expected projection
- If caused by a recent Lute/parser upgrade, roll back or update to a version where the projection format is unchanged
- Restore the document from sync history or a snapshot
Example fix
// before (hand-edited cell projection)
{"Type":"NodeText"} // where a NodeCodeBlockCode projection was expected
// after
re-parse the cell so the projection matches:
expected, sources := parse.ProjectTableCellRichWithSources(fragment) Defensive patterns
Strategy: try-catch
Validate before calling
// Regenerate the projection immediately before syncing
expected, sources := parse.ProjectTableCellRichWithSources(fragment)
if expected.ChildCount() != cell.ChildCount() { reparse() } Type guard
func isSyncable(expected, actual *ast.Node) bool {
for w, a := expected.FirstChild, actual.FirstChild; w != nil && a != nil; w, a = w.Next, a.Next {
if w.Type != a.Type { return false }
}
return true
} Try / catch
if err := syncTableProjection(cell); err != nil {
if strings.Contains(err.Error(), "projection structure") {
reparseCellAndRetry(cell)
}
} Prevention
- Never mutate projected table cell AST nodes directly; edit the source block
- Regenerate the projection and sources map after every fragment edit
- Avoid editing .sy JSON with external tools
- Keep all clients on the same SiYuan/Lute version
When it happens
Trigger: Calling the table-cell rich text sync path (parse.ProjectTableCellRichWithSources + recursive sync) when the cell's inline projection was edited in a way that inserted, removed, or retyped nodes relative to what the projection generated — e.g. a plugin or direct .sy edit changed node types inside a table cell that projects code/math content.
Common situations: Editing exported/projected table content with an external tool or older client; a Lute parser change altering how inline markdown inside cells is tokenized; corrupted or hand-edited .sy JSON.
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
- table cell rich text projection length changed
- table cell rich text projection marker changed
- 376
- 377
- attribute view not found
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/6b0c2e2ded755e98.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/treenode/table_cell_rich.go:155
// SyncTableCellRichInlineChanges 将内核对资源、引用和行内内容的改写同步到源,再刷新派生内容。
func SyncTableCellRichInlineChanges(root *ast.Node) (err error) {
updates := map[*ast.Node]string{}
ast.Walk(root, func(cell *ast.Node, entering bool) ast.WalkStatus {
if !entering || nil == cell.TableCellRich {
return ast.WalkContinue
}
var fragment *parse.Tree
if fragment, err = av.ParseTableCellRich(cell.TableCellRich); nil != err {
return ast.WalkStop
}
expected, sources := parse.ProjectTableCellRichWithSources(fragment)
changed := false
var sync func(*ast.Node, *ast.Node) error
sync = func(want, actual *ast.Node) error {
for ; want != nil && actual != nil; want, actual = want.Next, actual.Next {
if want.Type != actual.Type {
return fmt.Errorf("table cell rich text projection structure changed")
}
if !bytes.Equal(tableCellInlineSignature(want), tableCellInlineSignature(actual)) {
origin := sources[want]
if nil == origin {
return fmt.Errorf("table cell rich text projection marker changed")
}
switch origin.Type {
case ast.NodeCodeBlockCode:
origin.Tokens = []byte(html.UnescapeString(actual.TextMarkTextContent) + "\n")
case ast.NodeMathBlockContent:
origin.Tokens = []byte(html.UnescapeString(actual.TextMarkInlineMathContent))
default:
parent, previous, next, first, last := origin.Parent, origin.Previous, origin.Next, origin.FirstChild, origin.LastChild
*origin = *parse.CloneTableCellInline(actual)
origin.Parent, origin.Previous, origin.Next, origin.FirstChild, origin.LastChild = parent, previous, next, first, last
}
changed = true
}View on GitHub (pinned to 9f775e8a12)