toeverything/AFFiNE · error · BlockSuiteError

NoSurfaceModelError

NoSurfaceModelError

Error message

This doc is missing surface block in edgeless.

What it means

FrameworkStackProvider.getRaw probes each stacked provider with optional:true and returns the first truthy result; if none of them can build the component it throws ComponentNotFoundError for the stack as a whole. Because the probe uses truthiness, a factory that legitimately returns a falsy value (0, '', false, null) is also treated as 'not found' and the scan continues past it.

Solutions

  1. Register the identifier in at least one of the stacked frameworks (usually the base one) before resolving through the stack
  2. If factories may return falsy values, wrap them so they return objects (e.g. { value } or a boxed instance) — the truthiness probe cannot distinguish 'found but falsy'
  3. If absence is expected, resolve with getOptional through the stack to receive undefined instead of the throw

Example fix

// before
const x = stackProvider.get(PluginService); // no stacked framework registered it

// after
baseFramework.impl(PluginService, PluginServiceImpl); // register in one stacked framework
const x = stackProvider.get(PluginService);
// or: const maybe = stackProvider.getOptional(PluginService);
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

import { ComponentNotFoundError } from '.../framework/core/error';

try {
  const svc = stackProvider.get(PluginService);
} catch (err) {
  if (err instanceof ComponentNotFoundError) {
    // none of the stacked frameworks provide it; register it or degrade
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Resolving an identifier that none of the stacked frameworks registered (e.g. the plugin framework that owns it was not composed in); a registered factory returning a falsy value that the stack scan skips; version skew where the framework expected to register the service is older.

Common situations: Plugin systems where a service lives in an optional framework that was not loaded; mocks replacing a factory with () => null; conditional registration behind flags leaving no stacked provider with the binding.

Related errors


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

Appendix: source

Thrown at blocksuite/affine/blocks/root/src/edgeless/edgeless-root-service.ts:119

  }

  get viewport() {
    return this.std.get(GfxControllerIdentifier).viewport;
  }

  get zoom() {
    return this.viewport.zoom;
  }

  get crud() {
    return this.std.get(EdgelessCRUDIdentifier);
  }

  constructor(std: BlockStdScope, flavourProvider: { flavour: string }) {
    super(std, flavourProvider);
    const surface = getSurfaceBlock(this.doc);
    if (!surface) {
      throw new BlockSuiteError(
        ErrorCode.NoSurfaceModelError,
        'This doc is missing surface block in edgeless.'
      );
    }
    this._surface = surface;
  }

  private _initReadonlyListener() {
    const doc = this.doc;

    const slots = this.std.get(EdgelessLegacySlotIdentifier);

    let readonly = doc.readonly;
    this.disposables.add(
      effect(() => {
        if (readonly !== doc.readonly) {
          readonly = doc.readonly;
          slots.readonlyUpdated.next(readonly);

View on GitHub (pinned to b4c8548c09)