{"record":{"id":"3bdc7953bb01d858","repo":"toeverything/AFFiNE","slug":"none-root-block-must-have-parent","errorCode":null,"errorMessage":"None root block must have parent.","messagePattern":"None root block must have parent\\.","errorType":"validation","errorClass":"SchemaValidateError","httpStatus":null,"severity":"error","filePath":"blocksuite/framework/store/src/schema/schema.ts","lineNumber":91,"sourceCode":"        }\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) {\n      throw new SchemaValidateError(parentFlavour, SCHEMA_NOT_FOUND_MESSAGE);\n    }\n    this.validateSchema(schema, parentSchema);\n    validateChildren();\n  };\n\n  /**\n   * Returns an object mapping each registered flavour to its version number.\n   */\n  get versions() {\n    return Object.fromEntries(","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/blocksuite/framework/store/src/schema/schema.ts#L73-L109","documentation":"The counterpart of the root rule: Schema.validate throws when a block whose model.role is not 'root' is validated without a parentFlavour. Every non-root block must live under a parent, so validating or adding one standalone is an invalid hierarchy.","triggerScenarios":"doc.addBlock('affine:paragraph') with no parent id passed; validating a child flavour with schema.validate(flavour) and no second argument; snapshot import where the parent block was dropped.","commonSituations":"Forgetting the parent argument in addBlock during prototyping; docs loaded from partial snapshots that lost the root; constructing test hierarchies by hand.","solutions":["Pass the parent block id: doc.addBlock('affine:paragraph', {}, noteModel.id).","Ensure the doc has a root and an intermediate container before adding children (usually via doc.load()).","If the block is intentionally top-level, its schema must declare model.role = 'root'."],"exampleFix":"// before\nawait doc.addBlock('affine:paragraph'); // no parent\n\n// after\nconst noteId = doc.root?.children[0]?.id;\nawait doc.addBlock('affine:paragraph', {}, noteId);","handlingStrategy":"validation","validationCode":"const role = doc.schema.get(flavour)?.model.role;\nif (role && role !== 'root' && !parentId) {\n  throw new Error(`flavour ${flavour} requires a parent block id`);\n}\nawait doc.addBlock(flavour, props, parentId);","typeGuard":"const needsParent = (schema: Schema, flavour: string): boolean =>\n  schema.get(flavour)?.model.role !== 'root';","tryCatchPattern":"try {\n  await doc.addBlock(flavour);\n} catch (e) {\n  if (e instanceof SchemaValidateError && e.message.includes('must have parent')) {\n    await doc.addBlock(flavour, props, resolvedParentId);\n  } else throw e;\n}","preventionTips":["Ensure the doc has a root and container hierarchy (doc.load()) before adding children.","Make parent id a required parameter in your own wrappers around addBlock for non-root flavours."],"tags":["schema","hierarchy","parent-block","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"}