toeverything/AFFiNE · error · BlockSuiteError
ReactiveProxyError
ReactiveProxyError
Error message
Failed to transact text! yText is not attached to a doc
What it means
Every mutating Text method (insert, delete, format, replace, clear, split, join) funnels through `_transact`, which requires `yText.doc`. The Text constructor deliberately accepts unattached input — `new Text('hi')` creates a standalone `Y.Text` — so calling any mutation before that Y.Text is integrated into a Y.Doc throws this error.
Solutions
- Attach before mutating: `const t = new Text('hi'); ydoc.getMap('texts').set('t', t.yText); t.insert('!', 2);`
- Or construct from an already-attached Y.Text: `new Text(ydoc.getText('t'))`
- Check `text.yText.doc !== null` before calling any mutation method
- For never-synced scratch strings, keep a plain string and build the Text after attachment
Example fix
// before
const title = new Text('draft');
title.insert('!', 6); // throws: yText has no doc
// after
const title = new Text('draft');
ydoc.getMap('blocks').set('title', title.yText); // integrate
title.insert('!', 6); Defensive patterns
Strategy: validation
Validate before calling
if (text.yText.doc === null) {
ydoc.getMap('texts').set('t', text.yText); // integrate before editing
}
text.insert('!', text.length); Type guard
const isEditableText = (t: Text): boolean => t.yText.doc !== null;
Try / catch
try {
text.insert(content, index);
} catch (e) {
if (e instanceof BlockSuiteError && e.code === ErrorCode.ReactiveProxyError && e.message.includes('not attached')) {
ydoc.getMap('texts').set('t', text.yText);
text.insert(content, index); // retry once attached
} else throw e;
} Prevention
- Attach the Y.Text to a doc (or construct Text from an attached Y.Text) before any mutation
- Check text.yText.doc !== null before insert/delete/format/replace/clear
- Keep unattached scratch strings as plain strings; build Text only after integration
When it happens
Trigger: `const t = new Text('hello'); t.insert('!', 5);`; `new Text(deltaArray).format(0, 1, {bold: true})`; `new Text(detachedYText).delete(0, 1)`; any mutation after the owning doc is destroyed.
Common situations: Building a Text from a plain string or delta for later attachment, then editing it too early; block-level text created before the block model is mounted in the doc; post-teardown editing callbacks.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/e093b460ad6084b6.
Report an issue: GitHub.
Appendix: 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 b4c8548c09)