{"record":{"id":"dcbb1defdd4ea93b","repo":"toeverything/AFFiNE","slug":"errorcode-inlineeditorerror-dcbb1d","errorCode":"ErrorCode.InlineEditorError","errorMessage":"failed to find vElement for a text note in an embed element","messagePattern":"failed to find vElement for a text note in an embed element","errorType":"exception","errorClass":"BlockSuiteError","httpStatus":null,"severity":"error","filePath":"blocksuite/framework/std/src/inline/utils/range-conversion.ts","lineNumber":307,"sourceCode":"      if (startText && endText) {\n        break;\n      }\n\n      index += textLength;\n    }\n\n    // the one because of the line break\n    index += 1;\n  }\n\n  if (!startText || !endText) {\n    return null;\n  }\n\n  if (isInEmbedElement(startText)) {\n    const anchorVElement = startText.parentElement?.closest('v-element');\n    if (!anchorVElement) {\n      throw new BlockSuiteError(\n        ErrorCode.InlineEditorError,\n        'failed to find vElement for a text note in an embed element'\n      );\n    }\n    const nextSibling = anchorVElement.nextElementSibling;\n    if (!nextSibling) {\n      throw new BlockSuiteError(\n        ErrorCode.InlineEditorError,\n        'failed to find nextSibling sibling of an embed element'\n      );\n    }\n\n    const texts = getTextNodesFromElement(nextSibling);\n    if (texts.length === 0) {\n      throw new BlockSuiteError(\n        ErrorCode.InlineEditorError,\n        'text node in v-text not found'\n      );","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/blocksuite/framework/std/src/inline/utils/range-conversion.ts#L289-L325","documentation":"Thrown by `inlineRangeToDomRange(rootElement, inlineRange)` in range-conversion.ts while translating an editor `InlineRange` into a native DOM `Range`. The resolved start (anchor) `Text` node was detected inside an embed (`isInEmbedElement` returned true via the `data-v-embed=\"true\"` marker), but `parentElement?.closest('v-element')` returned null. BlockSuite's inline DOM invariant requires every embed text node to live inside a `<v-element>` custom element; a missing wrapper means the rendered tree is inconsistent with the delta model.","triggerScenarios":"Calling `InlineEditor`/`RangeManager` APIs that invoke `inlineRangeToDomRange` (e.g. selection sync, `toDomRange`, programmatic range restore) when a custom inline embed renderer produced text marked `data-v-embed=\"true\"` without a wrapping `<v-element>`, or while the inline DOM is being concurrently mutated/torn down so the wrapper has been removed but the embed span remains.","commonSituations":"Forked or custom embed attributes components that render a plain `<span data-v-embed=\"true\">` instead of going through `VElement`; SSR/hydration mismatch; selection being restored during `disconnectedCallback`; an embed delta inserted before its component was registered with the inline renderer.","solutions":["Render embed nodes through BlockSuite's `VElement` (`v-element`) wrappers rather than bare spans.","Before triggering range conversion, log `rootElement.querySelectorAll('[data-v-embed=\"true\"]')` and verify each has an ancestor `v-element`.","Gate range/sync calls on `editor.mounted` and the host's `hasUpdated`; skip them during teardown.","If you ship a custom inline render hook, audit it against `@blocksuite/std` `inline-v-element` / `inline-embed` components."],"exampleFix":"// before: embed text rendered without the v-element wrapper\nhtml`<span data-v-embed=\"true\">${node.text}</span>`\n\n// after: wrap embed content in a VElement so the invariant holds\nhtml`<v-element><span data-v-embed=\"true\">${node.text}</span></v-element>`","handlingStrategy":"validation","validationCode":"// run before inlineRangeToDomRange to confirm the embed DOM invariant\nfunction isEmbedDomIntact(root: HTMLElement): boolean {\n  const embeds = Array.from(root.querySelectorAll('[data-v-embed=\"true\"]'));\n  return embeds.every(el => !!el.closest('v-element'));\n}\n\nif (!isEmbedDomIntact(rootEl)) {\n  // skip range conversion instead of throwing\n  return null;\n}","typeGuard":"function hasVElementWrapper(node: Text): boolean {\n  return !!node.parentElement?.closest('v-element');\n}","tryCatchPattern":"import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';\n\ntry {\n  return inlineRangeToDomRange(root, range);\n} catch (e) {\n  if (e instanceof BlockSuiteError && e.code === ErrorCode.InlineEditorError) {\n    // DOM invariant broken: degrade gracefully rather than crash selection sync\n    console.warn('inlineRangeToDomRange skipped: embed DOM invariant violated', e);\n    return null;\n  }\n  throw e;\n}","preventionTips":["Render embed nodes exclusively through BlockSuite's VElement helpers.","Never hand-edit the inline DOM; mutate deltas through the editor API.","Skip range sync while the editor is unmounting.","Gate range conversion on `editor.mounted` and `host.hasUpdated`."],"tags":["inline-editor","dom","embed","selection","range"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}