{"record":{"id":"265e9529e3b94c58","repo":"siyuan-note/siyuan","slug":"block-swap-must-be-submitted-as-a-separate-transaction","errorCode":null,"errorMessage":"block swap must be submitted as a separate transaction","messagePattern":"block swap must be submitted as a separate transaction","errorType":"exception","errorClass":"TxErr","httpStatus":null,"severity":"error","filePath":"kernel/model/transaction_block_swap.go","lineNumber":36,"sourceCode":"}\n\n// 撤销快照仅保存在内核内存中，客户端不能提交或替换快照。\ntype blockSwapState struct {\n\tbefore, after []blockSwapFragment\n\trootIDs       []string\n}\n\ntype blockSwapFragment struct {\n\trootID, boxID, previousID, nextID string\n\tnode                              *ast.Node\n}\n\nfunc (tx *Transaction) doSwapBlockRef(operation *Operation) *TxErr {\n\tfail := func(err error) *TxErr {\n\t\treturn &TxErr{code: TxErrCodePushMsg, id: operation.ID, msg: err.Error()}\n\t}\n\tif len(tx.DoOperations) != 1 {\n\t\treturn fail(errors.New(\"block swap must be submitted as a separate transaction\"))\n\t}\n\tif tx.isReplay {\n\t\tif operation.blockSwapState == nil {\n\t\t\treturn fail(errors.New(\"block swap undo state is unavailable\"))\n\t\t}\n\t\tif err := tx.replayBlockSwap(operation); err != nil {\n\t\t\treturn fail(err)\n\t\t}\n\t\treturn nil\n\t}\n\tdata, err := json.Marshal(operation.Data)\n\tif err != nil {\n\t\treturn fail(err)\n\t}\n\tvar options blockSwapOptions\n\tif err = json.Unmarshal(data, &options); err != nil || options.IncludeChildren == nil || options.OriginalToEmbed == nil {\n\t\treturn fail(errors.New(\"invalid block swap options\"))\n\t}","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/transaction_block_swap.go#L18-L54","documentation":"This error means a block swap operation was submitted in a transaction together with other operations. The kernel requires that a 'swap-block-ref' operation be the sole operation in its transaction, because the swap manipulates two whole trees and stores an undo state snapshot that only makes sense atomically. doSwapBlockRef rejects any transaction whose DoOperations slice length is not exactly 1.","triggerScenarios":"Calling the transactions API with a 'swap-block-ref' operation bundled alongside other do/undo operations in the same transaction payload; performing a multi-operation batch that includes a block swap.","commonSituations":"Plugins or scripts batching several editor mutations into one transaction for efficiency; frontend code refactoring that merges a swap into an existing transaction array; replay/undo pipelines that concatenate queued operations.","solutions":["Submit the swap-block-ref operation alone in its own transaction with exactly one entry in the do array","Move any other operations into separate transaction calls before or after the swap","If batching is required, perform the swap last, in a dedicated transaction"],"exampleFix":"// before\nPOST /api/transactions\n{\"transactions\":[{\"doOperations\":[{\"action\":\"insert\",\"data\":\"...\"},{\"action\":\"swap-block-ref\",...}],\"undoOperations\":[]}]}\n// after\nPOST /api/transactions\n{\"transactions\":[{\"doOperations\":[{\"action\":\"swap-block-ref\",...}],\"undoOperations\":[]}]}\n// then a second call for the insert","handlingStrategy":"validation","validationCode":"const isSwapOnly = tx.doOperations.length === 1 && tx.doOperations[0].action === 'swap-block-ref';\nif (!isSwapOnly) throw new Error('swap-block-ref must be submitted as the only operation in its transaction');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always submit swap-block-ref in a dedicated single-operation transaction","Wrap swap submission in a helper that asserts doOperations.length === 1","Never merge swap operations into batched transaction payloads"],"tags":["transactions","block-swap","api"],"backgroundTag":"invalid-argument-value","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"}