{"record":{"id":"ba0c6ed7508ce685","repo":"siyuan-note/siyuan","slug":"encrypted-notebooks-do-not-support-this-operation-ba0c6e","errorCode":null,"errorMessage":"Encrypted notebooks do not support this operation","messagePattern":"Encrypted notebooks do not support this operation","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/flashcard.go","lineNumber":47,"sourceCode":"\n\t\"github.com/88250/gulu\"\n\t\"github.com/88250/lute/ast\"\n\t\"github.com/88250/lute/parse\"\n\t\"github.com/open-spaced-repetition/go-fsrs/v3\"\n\t\"github.com/siyuan-note/filelock\"\n\t\"github.com/siyuan-note/logging\"\n\t\"github.com/siyuan-note/riff\"\n\t\"github.com/siyuan-note/siyuan/kernel/cache\"\n\t\"github.com/siyuan-note/siyuan/kernel/sql\"\n\t\"github.com/siyuan-note/siyuan/kernel/treenode\"\n\t\"github.com/siyuan-note/siyuan/kernel/util\"\n)\n\n// ValidateFlashcardBlockIDs 只允许普通笔记本中可确认存在的块进入全局闪卡存储。\nfunc ValidateFlashcardBlockIDs(blockIDs []string) error {\n\tfor _, blockID := range blockIDs {\n\t\tif !isSupportedFlashcardBlock(blockID) {\n\t\t\treturn errors.New(Conf.Language(313))\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateFlashcardTree(rootID string) error {\n\treturn ValidateFlashcardBlockIDs([]string{rootID})\n}\n\nfunc isSupportedFlashcardBlock(blockID string) bool {\n\tbt := treenode.GetBlockTreeInExactBox(blockID, \"\")\n\treturn bt != nil && !IsEncryptedBox(bt.BoxID)\n}\n\nfunc filterSupportedFlashcards(cards []riff.Card) (ret []riff.Card) {\n\tfor _, card := range cards {\n\t\tif card == nil || !isSupportedFlashcardBlock(card.BlockID()) {\n\t\t\tcontinue","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/flashcard.go#L29-L65","documentation":"Thrown by ValidateFlashcardBlockIDs when at least one block ID fails the isSupportedFlashcardBlock check. That function returns false if treenode.GetBlockTreeInExactBox(blockID, \"\") returns nil (the block does not exist) OR if the block lives in an encrypted notebook (IsEncryptedBox(bt.BoxID) is true). The error message uses Conf.Language(313) = \"Encrypted notebooks do not support this operation\". This is a gatekeeper function that prevents encrypted content from entering the global flashcard deck store.","triggerScenarios":"Calling any flashcard API that routes through ValidateFlashcardBlockIDs (such as adding blocks to a deck or resetting flashcards) with a block ID that either does not exist in the block tree or resides in an encrypted notebook. The isSupportedFlashcardBlock check uses GetBlockTreeInExactBox with an empty box filter, so it looks up the block across all boxes.","commonSituations":"User selects blocks from an encrypted notebook and tries to add them as flashcards; a block was deleted after the UI rendered it but before the flashcard API call; an API client passes a stale or fabricated block ID.","solutions":["If the block is in an encrypted notebook, move it to a non-encrypted notebook first, or decrypt the notebook — encrypted content is fundamentally excluded from flashcard decks by design.","If the block should exist, verify it is still present via the block lookup endpoint — it may have been deleted.","As an API client, filter block IDs through isSupportedFlashcardBlock equivalent logic (exists + non-encrypted) before calling flashcard APIs."],"exampleFix":"// before\nawait post('/api/riff/addRiffCards', { deckID, blockIDs: [...allSelectedBlocks] })\n\n// after — exclude blocks from encrypted notebooks and non-existent blocks\nconst validBlockIDs = allSelectedBlocks.filter(id => {\n  const bt = getBlockInfo(id)\n  return bt && !bt.encrypted\n})\nawait post('/api/riff/addRiffCards', { deckID, blockIDs: validBlockIDs })","handlingStrategy":"validation","validationCode":"// Filter out blocks from encrypted notebooks before flashcard operations\nfunction isFlashcardSupported(bt) {\n  return bt != null && !bt.encrypted\n}\nconst supportedBlockIDs = blockIDs.filter(id => isFlashcardSupported(blockTreeCache[id]))","typeGuard":"// Type guard: check if a block can be used as a flashcard\nfunction isFlashcardEligible(bt: { boxID: string; encrypted: boolean } | null): bt is { boxID: string; encrypted: false } {\n  return bt != null && !bt.encrypted\n}","tryCatchPattern":null,"preventionTips":["Never add blocks from encrypted notebooks to flashcard decks — this is a fundamental design restriction.","Verify block existence before adding to flashcard operations.","Filter block lists through an encrypted-notebook check before any riff API call."],"tags":["flashcard","encryption","validation","encrypted-notebook"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}