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

  1. Register the selection class in the store/editor extensions: SelectionExtension(MySelection) for every type that can appear in serialized selection state.
  2. Keep static type strings stable across versions; if renamed, migrate old values before set().
  3. Filter unknown types before restoring: check the type against registered constructors and drop unmatched entries.
  4. 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

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


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/ee2e68f2221afe59. Report an issue: GitHub.