{"record":{"id":"b66c8a79b6030392","repo":"toeverything/AFFiNE","slug":"errorcode-modelcruderror-b66c8a","errorCode":"ErrorCode.ModelCRUDError","errorMessage":"cannot modify data in readonly mode","messagePattern":"cannot modify data in readonly mode","errorType":"exception","errorClass":"BlockSuiteError","httpStatus":null,"severity":"error","filePath":"blocksuite/framework/store/src/model/store/store.ts","lineNumber":750,"sourceCode":"  /**\n   * Creates and adds a new block to the store\n   * @param flavour - The block's flavour (type)\n   * @param blockProps - Optional properties for the new block\n   * @param parent - Optional parent block or parent block ID\n   * @param parentIndex - Optional index position in parent's children\n   * @returns The ID of the newly created block\n   * @throws {BlockSuiteError} When store is in readonly mode\n   *\n   * @category Block CRUD\n   */\n  addBlock<T extends BlockModel = BlockModel>(\n    flavour: string,\n    blockProps: Partial<(PropsOfModel<T> & BlockSysProps) | BlockProps> = {},\n    parent?: BlockModel | string | null,\n    parentIndex?: number\n  ): string {\n    if (this.readonly) {\n      throw new BlockSuiteError(\n        ErrorCode.ModelCRUDError,\n        'cannot modify data in readonly mode'\n      );\n    }\n\n    const id = blockProps.id ?? this._doc.workspace.idGenerator();\n\n    this.transact(() => {\n      this._crud.addBlock(\n        id,\n        flavour,\n        { ...blockProps },\n        typeof parent === 'string' ? parent : parent?.id,\n        parentIndex\n      );\n    });\n\n    return id;","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/blocksuite/framework/store/src/model/store/store.ts#L732-L768","documentation":"Thrown by Store.addBlock when this.readonly is true. A readonly store is a doc opened for read-only access (e.g. viewer mode, snapshot inspection); mutating it would desync viewers and could break collaboration guarantees, so every write API refuses up front.","triggerScenarios":"Calling store.addBlock (or any mutating API routed through addBlock) on a doc/collection opened with readonly:true, or after the doc was switched to readonly at runtime.","commonSituations":"Embedding a doc in read-only viewer mode and accidentally firing an edit command; shared/preview links opened readonly but the UI still binds edit handlers; a doc auto-switched to readonly after losing connection or on sync errors.","solutions":["Check store.readonly before issuing mutations and disable/skip the action in the UI.","Open the doc in read-write mode if editing is intended: pass readonly:false when creating the session.","Guard global edit handlers (keyboard, toolbar) with an if (store.readonly) return;.","If readonly was set due to a transient condition (permissions, connection), re-open the doc writable once it clears."],"exampleFix":"// before\nstore.addBlock('affine:paragraph', {}, parent);\n\n// after\nif (store.readonly) {\n  notifyUser('Document is read-only');\n  return;\n}\nstore.addBlock('affine:paragraph', {}, parent);","handlingStrategy":"validation","validationCode":"export function assertWritable(store: Store): void {\n  if (store.readonly) {\n    throw new Error('Store is read-only; mutation rejected');\n  }\n}\n\nassertWritable(store);\nstore.addBlock('affine:paragraph', {}, parent);","typeGuard":"export const isWritable = (store: Store): boolean => !store.readonly;","tryCatchPattern":"try {\n  store.addBlock(flavour, props, parent);\n} catch (e) {\n  if (e instanceof BlockSuiteError && e.code === ErrorCode.ModelCRUDError && /readonly mode/.test(e.message)) {\n    notifyReadonlyMode(); // or re-open the doc writable\n  } else throw e;\n}","preventionTips":["Check store.readonly at the UI layer and disable edit controls.","Bind edit handlers (keyboard, toolbar) behind a writable guard.","Open the doc with readonly:false when editing is intended.","Re-open writable once a transient readonly condition clears."],"tags":["model-crud","readonly","addblock","permissions"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}