{"record":{"id":"3ec823985cb016de","repo":"Foundry376/Mailspring","slug":"composereditor-reason-inserting-empty-paragra","errorCode":null,"errorMessage":"ComposerEditor: ${reason}, inserting empty paragraph to recover.","messagePattern":"ComposerEditor: (.+?), inserting empty paragraph to recover\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/components/composer-editor/composer-editor.tsx","lineNumber":349,"sourceCode":"  };\n\n  onChange = (change: { operations: Immutable.List<Operation>; value: Value }) => {\n    // This needs to be here because some composer plugins defer their calls to onChange\n    // (like spellcheck and the context menu).\n    if (!this._mounted) return;\n\n    // If the incoming value is broken, apply recovery operations directly to `value`\n    // before forwarding to the parent. This emits a single onChange with the already-\n    // corrected value rather than emitting the broken value and then a second onChange\n    // from a deferred microtask (which is what would happen if we called editor commands\n    // here). The parent therefore never sees the broken state, and any recovery operation\n    // that changes the document is included in the operations list so the parent knows\n    // the document changed and doesn't skip saving.\n    let { operations, value } = change;\n\n    const reason = getDocumentBrokenReason(value);\n    if (reason) {\n      console.warn(`ComposerEditor: ${reason}, inserting empty paragraph to recover.`);\n      const op = require('slate').Operation.create({\n        type: 'insert_node',\n        path: Immutable.List([0]),\n        node: Block.create({ type: 'div', nodes: Immutable.List([Text.create('')]) }),\n      });\n      operations = operations.push(op);\n      value = op.apply(value);\n    }\n\n    // Same idea as above, but for a broken selection rather than a broken document: move\n    // the dangling anchor/focus back to the start of the document so the next keystroke\n    // doesn't crash inside Slate. This check must run on `value` (which may have just been\n    // repaired above), not the original `change.value` — removing the last text-carrying\n    // node from the document unsets the selection *and* triggers the document-broken repair\n    // in the same change, and inserting the recovery paragraph does not re-anchor a\n    // previously-unset selection point, so the selection would still be broken afterwards.\n    if (isSelectionBroken(value, change.operations)) {\n      console.warn(","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/Foundry376/Mailspring/blob/648c685d602ece6bb00c22534b8734de6ac644b3/app/src/components/composer-editor/composer-editor.tsx#L331-L367","documentation":"Self-healing warning in ComposerEditor.onChange: the incoming Slate value was structurally broken (reason is filled in by the check that fired), so a recovery operation — inserting an empty div paragraph at path [0] — is applied directly to the value before it reaches the parent, producing one clean onChange instead of a broken one plus a deferred fix.","triggerScenarios":"Thrown at app/src/components/composer-editor/composer-editor.tsx:349 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["No manual fix needed — the editor repairs the document automatically","If it recurs, identify the plugin/plugin-paste producing invalid Slate nodes and fix its serializer"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"648c685d602ece6bb00c22534b8734de6ac644b3","analyzedAt":"2026-09-03T02:00:24.311Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}