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
- 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).
- 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(...).
- 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
- Mutate block text only after the block is mounted into the doc tree.
- In tests, always construct a Y.Doc and an attached Y.Text before wrapping in Text.
- Defer edits until the host collection reports the doc is ready.
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
- ReactiveProxyError
- ReactiveProxyError
- Blob engine is not initialized
- DatabaseBlockError
- ErrorCode.ValueNotExists
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/e093b460ad6084b6.
Report an issue: GitHub.