toeverything/AFFiNE · error · BlockSuiteError

ReactiveProxyError

ReactiveProxyError

Error message

Failed to transact text! yText is not attached to a doc

What it means

Thrown by Text._transact (text.ts:96) when the wrapped Y.Text has no doc. Every mutating Text method (clear, delete, format, insert, replace, applyDelta, join, split) routes through _transact, which needs a Y.Doc to run doc.transact(callback, clientID). Without a doc the operation cannot be persisted or synced, so it throws.

Source

Thrown at blocksuite/framework/store/src/reactive/text/text.ts:99

    this._deltas$ = signal(this._yText.doc ? this._yText.toDelta() : []);
    this._yText.observe(event => {
      const isLocal =
        !event.transaction.origin ||
        !this._yText.doc ||
        event.transaction.origin instanceof Y.UndoManager ||
        event.transaction.origin.proxy
          ? true
          : event.transaction.origin === this._yText.doc.clientID;
      this._length$.value = this._yText.length;
      this._deltas$.value = this._yText.toDelta();
      this._onChange?.(this._yText, isLocal);
    });
  }

  private _transact(callback: () => void) {
    const doc = this._yText.doc;
    if (!doc) {
      throw new BlockSuiteError(
        ErrorCode.ReactiveProxyError,
        'Failed to transact text! yText is not attached to a doc'
      );
    }
    doc.transact(() => {
      callback();
    }, doc.clientID);
  }

  /**
   * Apply a delta to the text.
   *
   * @param delta - The delta to apply.
   *
   * @example
   * ```ts
   * const text = new Text('Hello, world!');
   * text.applyDelta([{insert: ' blocksuite', attributes: { bold: true }}]);

View on GitHub (pinned to 26c515e050)

Solutions

  1. Ensure the Text's Y.Text is part of a Y.Doc before mutating (e.g. insert it into a Y.Map owned by a doc, or obtain the Text from an attached block model).
  2. For ad-hoc manipulation, wrap: const doc = new Y.Doc(); const yt = doc.getText('t'); yt.insert(0, 'hi'); const t = new Text(yt); t.insert(...).
  3. Defer text edits until after the host block/collection reports the doc is ready.

Example fix

// before
const t = new Text('hello');
t.delete(0, 1); // throws: yText not attached

// after
const doc = new Y.Doc();
const yt = doc.getText('t');
yt.insert(0, 'hello');
const t = new Text(yt);
t.delete(0, 1); // ok
Defensive patterns

Strategy: validation

Validate before calling

import * as Y from 'yjs';
import { Text } from '@blocksuite/framework/store';

function assertTextAttached(text: Text) {
  if (!text.yText.doc) {
    throw new Error('Text.yText is not attached to a Y.Doc');
  }
}

// usage: build an attached Text
const doc = new Y.Doc();
const yt = doc.getText('body');
yt.insert(0, 'hello');
const text = new Text(yt);
assertTextAttached(text);
text.insert(' world', 5);

Type guard

function isTextAttached(text: Text): boolean {
  return text.yText.doc != null;
}

Prevention

When it happens

Trigger: Calling any mutating Text method on a Text constructed from a detached Y.Text (new Text(new Y.Text('x')) where the Y.Text was never inserted into a doc). Also reached when a Text built from a plain string/delta is mutated before being attached, because string/delta constructors create a detached Y.Text (text.ts:60,73).

Common situations: Editor code that mutates block text before the block is mounted into the doc tree; tests using new Text('hello').delete(...) with no Y.Doc; cloning/detaching text for transform and then editing the clone; SSR rendering paths that call format/insert.

Related errors


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