{"record":{"id":"ed05423fb1cd868a","repo":"toeverything/AFFiNE","slug":"root-block-cannot-have-parent","errorCode":null,"errorMessage":"Root block cannot have parent.","messagePattern":"Root block cannot have parent\\.","errorType":"validation","errorClass":"SchemaValidateError","httpStatus":null,"severity":"error","filePath":"blocksuite/framework/store/src/schema/schema.ts","lineNumber":80,"sourceCode":"  ): void => {\n    const schema = this.flavourSchemaMap.get(flavour);\n    if (!schema) {\n      throw new SchemaValidateError(flavour, SCHEMA_NOT_FOUND_MESSAGE);\n    }\n\n    const validateChildren = () => {\n      childFlavours?.forEach(childFlavour => {\n        const childSchema = this.flavourSchemaMap.get(childFlavour);\n        if (!childSchema) {\n          throw new SchemaValidateError(childFlavour, SCHEMA_NOT_FOUND_MESSAGE);\n        }\n        this.validateSchema(childSchema, schema);\n      });\n    };\n\n    if (schema.model.role === 'root') {\n      if (parentFlavour) {\n        throw new SchemaValidateError(\n          schema.model.flavour,\n          'Root block cannot have parent.'\n        );\n      }\n\n      validateChildren();\n      return;\n    }\n\n    if (!parentFlavour) {\n      throw new SchemaValidateError(\n        schema.model.flavour,\n        'None root block must have parent.'\n      );\n    }\n\n    const parentSchema = this.flavourSchemaMap.get(parentFlavour);\n    if (!parentSchema) {","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/blocksuite/framework/store/src/schema/schema.ts#L62-L98","documentation":"Schema.validate refuses any schema whose model.role === 'root' when a parentFlavour is supplied. Root blocks (for example a doc root like affine:page) are top-level by definition, so nesting one under another block is an invalid hierarchy and validation stops immediately.","triggerScenarios":"doc.addBlock('affine:page', {}, someParentId) — creating a root-flavoured block under another block; moving a root model into a container; snapshot import that places a root block under a parent.","commonSituations":"Treating the page/root flavour as a generic container; hand-built snapshots with wrong parent links; copy-paste logic that re-parents whatever block was copied, including roots.","solutions":["Create root blocks without a parent: doc.addBlock(rootFlavour).","Use a non-root container flavour when you need nesting under other blocks.","When importing snapshots, drop parent ids attached to root-role blocks."],"exampleFix":"// before\nawait doc.addBlock('affine:page', {}, parentId); // root under a parent\n\n// after\nawait doc.addBlock('affine:page'); // root blocks are added without a parent","handlingStrategy":"validation","validationCode":"if (doc.schema.get(flavour)?.model.role === 'root') {\n  await doc.addBlock(flavour); // root: never pass a parent\n} else {\n  await doc.addBlock(flavour, props, parentId);\n}","typeGuard":"const isRootFlavour = (schema: Schema, flavour: string): boolean =>\n  schema.get(flavour)?.model.role === 'root';","tryCatchPattern":"try {\n  await doc.addBlock(flavour, props, parentId);\n} catch (e) {\n  if (e instanceof SchemaValidateError && e.message.includes('Root block cannot have parent')) {\n    // retry without parent\n    await doc.addBlock(flavour);\n  } else throw e;\n}","preventionTips":["Treat role === 'root' blocks as strictly top-level in insertion and re-parenting code.","Drop parent ids from snapshot nodes whose flavour has root role before import."],"tags":["schema","root-block","hierarchy","blocksuite"],"backgroundTag":"invalid-block-hierarchy","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}