toeverything/AFFiNE · error · BlockSuiteError
ErrorCode.SelectionError
ErrorCode.SelectionError
Error message
Unknown selection type: ${json.type} What it means
The selection manager deserializes each selection record via a registry keyed by json.type (populated from SelectionExtension-wrapped constructors). When a payload carries a type string that no registered selection class declares, _jsonToSelection throws SelectionError 'Unknown selection type'. This is the guard against restoring or receiving selections whose extension isn't present in the current editor.
Source
Thrown at blocksuite/framework/store/src/extension/selection/selection-extension.ts:36
private readonly _remoteSelections = signal<Map<number, BaseSelection[]>>(
new Map()
);
private readonly _itemAdded = (event: { stackItem: StackItem }) => {
event.stackItem.meta.set('selection-state', this._selections.value);
};
private readonly _itemPopped = (event: { stackItem: StackItem }) => {
const selection = event.stackItem.meta.get('selection-state');
if (selection) {
this.set(selection as BaseSelection[]);
}
};
private readonly _jsonToSelection = (json: Record<string, unknown>) => {
const ctor = this._selectionConstructors[json.type as string];
if (!ctor) {
throw new BlockSuiteError(
ErrorCode.SelectionError,
`Unknown selection type: ${json.type}`
);
}
return ctor.fromJSON(json);
};
slots = {
changed: new Subject<BaseSelection[]>(),
remoteChanged: new Subject<Map<number, BaseSelection[]>>(),
};
override loaded() {
this.store.provider.getAll(SelectionIdentifier).forEach(ctor => {
[ctor].flat().forEach(ctor => {
this._selectionConstructors[ctor.type] = ctor;
});
});View on GitHub (pinned to b4c8548c09)
Solutions
- Register the selection class in the store/editor extensions: SelectionExtension(MySelection) for every type that can appear in serialized selection state.
- Keep static type strings stable across versions; if renamed, migrate old values before set().
- Filter unknown types before restoring: check the type against registered constructors and drop unmatched entries.
- Ensure all collaborative peers run a build that registers the same selection types.
Example fix
// before selectionManager.set(json as BaseSelection[]); // unknown type throws // after const known = new Set(['block', 'text', 'surface', 'my-selection']); const safe = (Array.isArray(json) ? json : []).filter(s => known.has(s.type)); selectionManager.set(safe.map(selectionManager.fromJSON.bind(selectionManager)));
Defensive patterns
Strategy: validation
Validate before calling
// Whitelist types you actually register before restoring selection state:
const registeredTypes = new Set(['block', 'text', 'surface', 'surfaceSelection']);
const items = (json.selections ?? []).filter(
(s: { type?: string }) => typeof s.type === 'string' && registeredTypes.has(s.type)
);
selectionManager.fromJSON(items); Type guard
function isKnownSelectionType(
type: unknown,
known: ReadonlySet<string>
): type is string {
return typeof type === 'string' && known.has(type);
} Try / catch
try {
selectionManager.set(rawSelections);
} catch (e) {
if (e instanceof BlockSuiteError && e.code === ErrorCode.SelectionError && /Unknown selection type/.test(e.message)) {
// drop the unknown entries and set the rest
selectionManager.set(rawSelections.filter(s => knownTypes.has(s.type)));
} else throw e;
} Prevention
- Register SelectionExtension(...) for every custom selection type on all peers.
- Keep static type strings stable; migrate renamed types before deserializing.
- Treat unknown-type errors as non-fatal during cross-version collaboration: filter and continue.
When it happens
Trigger: Undo/redo restoring 'selection-state' meta recorded by a build that had a selection type the current build doesn't register; collaborative peers with different extensions broadcasting selections; hand-crafted or migrated selection JSON with a type string that doesn't match any class's static type.
Common situations: Version skew between client builds (new custom selection shipped to some peers only); forgetting to register the custom selection's SelectionExtension in the editor's extensions array; renaming a selection's static type while old undo stacks/persisted data still use the old name.
Related errors
- ErrorCode.InlineEditorError
- ErrorCode.SelectionError
- internal_server_error
- ErrorCode.ValueNotExists
- a should be smaller than b
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/ee2e68f2221afe59.
Report an issue: GitHub.