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
- Register the identifier in at least one of the stacked frameworks (usually the base one) before resolving through the stack
- 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'
- 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
- Use getOptional() through the stack when a service may not be composed in
- Register shared services in the base framework so the stack always finds them
- Keep factories from returning falsy values (0, '', null, false) — the stack probe treats them as not-found
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
- EdgelessExportError
- GfxBlockElementError
- Unable to export content to canvas
- DatabaseBlockError
- Failed to read image size
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)