{"record":{"id":"6b0c2e2ded755e98","repo":"siyuan-note/siyuan","slug":"table-cell-rich-text-projection-structure-changed","errorCode":null,"errorMessage":"table cell rich text projection structure changed","messagePattern":"table cell rich text projection structure changed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/treenode/table_cell_rich.go","lineNumber":155,"sourceCode":"\n// SyncTableCellRichInlineChanges 将内核对资源、引用和行内内容的改写同步到源，再刷新派生内容。\nfunc SyncTableCellRichInlineChanges(root *ast.Node) (err error) {\n\tupdates := map[*ast.Node]string{}\n\tast.Walk(root, func(cell *ast.Node, entering bool) ast.WalkStatus {\n\t\tif !entering || nil == cell.TableCellRich {\n\t\t\treturn ast.WalkContinue\n\t\t}\n\t\tvar fragment *parse.Tree\n\t\tif fragment, err = av.ParseTableCellRich(cell.TableCellRich); nil != err {\n\t\t\treturn ast.WalkStop\n\t\t}\n\t\texpected, sources := parse.ProjectTableCellRichWithSources(fragment)\n\t\tchanged := false\n\t\tvar sync func(*ast.Node, *ast.Node) error\n\t\tsync = func(want, actual *ast.Node) error {\n\t\t\tfor ; want != nil && actual != nil; want, actual = want.Next, actual.Next {\n\t\t\t\tif want.Type != actual.Type {\n\t\t\t\t\treturn fmt.Errorf(\"table cell rich text projection structure changed\")\n\t\t\t\t}\n\t\t\t\tif !bytes.Equal(tableCellInlineSignature(want), tableCellInlineSignature(actual)) {\n\t\t\t\t\torigin := sources[want]\n\t\t\t\t\tif nil == origin {\n\t\t\t\t\t\treturn fmt.Errorf(\"table cell rich text projection marker changed\")\n\t\t\t\t\t}\n\t\t\t\t\tswitch origin.Type {\n\t\t\t\t\tcase ast.NodeCodeBlockCode:\n\t\t\t\t\t\torigin.Tokens = []byte(html.UnescapeString(actual.TextMarkTextContent) + \"\\n\")\n\t\t\t\t\tcase ast.NodeMathBlockContent:\n\t\t\t\t\t\torigin.Tokens = []byte(html.UnescapeString(actual.TextMarkInlineMathContent))\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tparent, previous, next, first, last := origin.Parent, origin.Previous, origin.Next, origin.FirstChild, origin.LastChild\n\t\t\t\t\t\t*origin = *parse.CloneTableCellInline(actual)\n\t\t\t\t\t\torigin.Parent, origin.Previous, origin.Next, origin.FirstChild, origin.LastChild = parent, previous, next, first, last\n\t\t\t\t\t}\n\t\t\t\t\tchanged = true\n\t\t\t\t}","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/treenode/table_cell_rich.go#L137-L173","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (hand-edited cell projection)\n{\"Type\":\"NodeText\"}   // where a NodeCodeBlockCode projection was expected\n// after\nre-parse the cell so the projection matches:\nexpected, sources := parse.ProjectTableCellRichWithSources(fragment)","handlingStrategy":"try-catch","validationCode":"// Regenerate the projection immediately before syncing\nexpected, sources := parse.ProjectTableCellRichWithSources(fragment)\nif expected.ChildCount() != cell.ChildCount() { reparse() }","typeGuard":"func isSyncable(expected, actual *ast.Node) bool {\n  for w, a := expected.FirstChild, actual.FirstChild; w != nil && a != nil; w, a = w.Next, a.Next {\n    if w.Type != a.Type { return false }\n  }\n  return true\n}","tryCatchPattern":"if err := syncTableProjection(cell); err != nil {\n  if strings.Contains(err.Error(), \"projection structure\") {\n    reparseCellAndRetry(cell)\n  }\n}","preventionTips":["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"],"tags":["ast","table","sync"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}