{"record":{"id":"7b9f0aeae1bcc671","repo":"toeverything/AFFiNE","slug":"missingviewmodelerror","errorCode":"MissingViewModelError","errorMessage":"can not find element(id:${referenceElement})","messagePattern":"can not find element\\(id:(.+?)\\)","errorType":"exception","errorClass":"BlockSuiteError","httpStatus":null,"severity":"error","filePath":"blocksuite/affine/blocks/surface-ref/src/surface-ref-block.ts","lineNumber":308,"sourceCode":"    };\n    this.disposables.add(effect(refreshViewport));\n\n    const referenceId = this.model.props.reference;\n    const referenceXYWH$ = this._referenceXYWH$;\n    class SurfaceRefViewportWatcher extends LifeCycleWatcher {\n      static override readonly key = 'surface-ref-viewport-watcher';\n\n      private readonly _disposable = new DisposableGroup();\n\n      override mounted() {\n        const crud = this.std.get(EdgelessCRUDIdentifier);\n        const gfx = this.std.get(GfxControllerIdentifier);\n        const { surface, viewport } = gfx;\n        if (!surface) return;\n\n        const referenceElement = crud.getElementById(referenceId);\n        if (!referenceElement) {\n          throw new BlockSuiteError(\n            ErrorCode.MissingViewModelError,\n            `can not find element(id:${referenceElement})`\n          );\n        }\n        referenceXYWH$.value = referenceElement.xywh;\n\n        const { _disposable } = this;\n        refreshViewport();\n        _disposable.add(viewport.sizeUpdated.subscribe(refreshViewport));\n\n        if (referenceElement instanceof GfxBlockElementModel) {\n          _disposable.add(\n            referenceElement.xywh$.subscribe(xywh => {\n              referenceXYWH$.value = xywh;\n            })\n          );\n        } else if (referenceElement instanceof GfxPrimitiveElementModel) {\n          _disposable.add(","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/blocksuite/affine/blocks/surface-ref/src/surface-ref-block.ts#L290-L326","documentation":"The op consumer dispatches incoming call/subscribe messages by looking up op.name in registeredOpHandlers, which is populated only via consumer.register()/registerAll(). A message arriving for an unregistered name throws inside a defer()-wrapped observable, so the error travels back to the producer through the channel's serialized error path instead of crashing the consumer loop.","triggerScenarios":"Producer calls an op the consumer never register()ed; handler registration is asynchronous and the producer sends before it completes; op name typo or rename on one side only; version mismatch between host and plugin op sets; registerAll object missing a key the protocol requires.","commonSituations":"Worker/port-based RPC where the host races ahead of worker setup; plugin upgrades adding ops the old consumer does not handle; op name strings drifting between packages instead of being derived from a shared Ops type.","solutions":["Register handlers before wiring the producer: consumer.register('opName', handler) or consumer.registerAll({...}) prior to any call/subscribe","Derive op names from a single shared Ops interface on both sides so producer and consumer cannot drift","If registration is async, buffer outgoing messages until a ready signal instead of firing immediately"],"exampleFix":"// before\nproducer.call('sync', payload); // consumer never registered 'sync'\n\n// after\nconsumer.register('sync', async (payload, { signal }) => {\n  /* ... */\n});\nconsumer.registerAll({ load, save }); // register everything before the producer connects","handlingStrategy":"validation","validationCode":"const registeredOps = new Set<string>();\nfunction safeRegister<T>(name: string, handler: T) {\n  consumer.register(name as any, handler as any);\n  registeredOps.add(name);\n}\n\n// producer side: check before sending\nfunction canCall(op: string) {\n  return registeredOps.has(op);\n}\nif (!canCall('sync')) throw new Error(`op 'sync' not registered yet`);","typeGuard":"null","tryCatchPattern":null,"preventionTips":["Register all handlers before connecting the producer to the port/channel","Derive op names from one shared Ops type so both sides stay in sync","Track registered op names in a Set and gate outgoing calls/queued sends on it","Treat op handler registration as part of startup; delay producer wiring until it completes"],"tags":["rpc","message-passing","worker","handler-registration"],"backgroundTag":"unregistered-rpc-handler","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}