toeverything/AFFiNE · error · BlockSuiteError

ErrorCode.InlineEditorError

ErrorCode.InlineEditorError

Error message

failed to find vElement for a text note in an embed element

What it means

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.

Source

Thrown at blocksuite/framework/std/src/inline/utils/range-conversion.ts:307

      if (startText && endText) {
        break;
      }

      index += textLength;
    }

    // the one because of the line break
    index += 1;
  }

  if (!startText || !endText) {
    return null;
  }

  if (isInEmbedElement(startText)) {
    const anchorVElement = startText.parentElement?.closest('v-element');
    if (!anchorVElement) {
      throw new BlockSuiteError(
        ErrorCode.InlineEditorError,
        'failed to find vElement for a text note in an embed element'
      );
    }
    const nextSibling = anchorVElement.nextElementSibling;
    if (!nextSibling) {
      throw new BlockSuiteError(
        ErrorCode.InlineEditorError,
        'failed to find nextSibling sibling of an embed element'
      );
    }

    const texts = getTextNodesFromElement(nextSibling);
    if (texts.length === 0) {
      throw new BlockSuiteError(
        ErrorCode.InlineEditorError,
        'text node in v-text not found'
      );

View on GitHub (pinned to 26c515e050)

Solutions

  1. Render embed nodes through BlockSuite's `VElement` (`v-element`) wrappers rather than bare spans.
  2. Before triggering range conversion, log `rootElement.querySelectorAll('[data-v-embed="true"]')` and verify each has an ancestor `v-element`.
  3. Gate range/sync calls on `editor.mounted` and the host's `hasUpdated`; skip them during teardown.
  4. If you ship a custom inline render hook, audit it against `@blocksuite/std` `inline-v-element` / `inline-embed` components.

Example fix

// before: embed text rendered without the v-element wrapper
html`<span data-v-embed="true">${node.text}</span>`

// after: wrap embed content in a VElement so the invariant holds
html`<v-element><span data-v-embed="true">${node.text}</span></v-element>`
Defensive patterns

Strategy: validation

Validate before calling

// run before inlineRangeToDomRange to confirm the embed DOM invariant
function isEmbedDomIntact(root: HTMLElement): boolean {
  const embeds = Array.from(root.querySelectorAll('[data-v-embed="true"]'));
  return embeds.every(el => !!el.closest('v-element'));
}

if (!isEmbedDomIntact(rootEl)) {
  // skip range conversion instead of throwing
  return null;
}

Type guard

function hasVElementWrapper(node: Text): boolean {
  return !!node.parentElement?.closest('v-element');
}

Try / catch

import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';

try {
  return inlineRangeToDomRange(root, range);
} catch (e) {
  if (e instanceof BlockSuiteError && e.code === ErrorCode.InlineEditorError) {
    // DOM invariant broken: degrade gracefully rather than crash selection sync
    console.warn('inlineRangeToDomRange skipped: embed DOM invariant violated', e);
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/dcbb1defdd4ea93b. Report an issue: GitHub.