{"record":{"id":"3ff11e0c3328f6c0","repo":"BookStackApp/BookStack","slug":"cannot-call-clear-on-a-frozen-lexical-node-map","errorCode":null,"errorMessage":"Cannot call clear() on a frozen Lexical node map","messagePattern":"Cannot call clear\\(\\) on a frozen Lexical node map","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"resources/js/wysiwyg/lexical/core/LexicalUpdates.ts","lineNumber":469,"sourceCode":"    activeEditorState = previousActiveEditorState;\n    isReadOnlyMode = previousReadOnlyMode;\n    activeEditor = previousActiveEditor;\n  }\n}\n\nfunction handleDEVOnlyPendingUpdateGuarantees(\n  pendingEditorState: EditorState,\n): void {\n  // Given we can't Object.freeze the nodeMap as it's a Map,\n  // we instead replace its set, clear and delete methods.\n  const nodeMap = pendingEditorState._nodeMap;\n\n  nodeMap.set = () => {\n    throw new Error('Cannot call set() on a frozen Lexical node map');\n  };\n\n  nodeMap.clear = () => {\n    throw new Error('Cannot call clear() on a frozen Lexical node map');\n  };\n\n  nodeMap.delete = () => {\n    throw new Error('Cannot call delete() on a frozen Lexical node map');\n  };\n}\n\nexport function $commitPendingUpdates(\n  editor: LexicalEditor,\n  recoveryEditorState?: EditorState,\n): void {\n  const pendingEditorState = editor._pendingEditorState;\n  const rootElement = editor._rootElement;\n  const shouldSkipDOM = editor._headless || rootElement === null;\n\n  if (pendingEditorState === null) {\n    return;\n  }","sourceCodeStart":451,"sourceCodeEnd":487,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/resources/js/wysiwyg/lexical/core/LexicalUpdates.ts#L451-L487","documentation":"Same frozen node-map dev guarantee as [147]: after pending state is frozen, calling .clear() on it throws 'Cannot call clear() on a frozen Lexical node map'. It exists to catch code clearing an editor state's node map that should be immutable once pending/committed.","triggerScenarios":"Any dev-mode path calling nodeMap.clear() on a pendingEditorState._nodeMap after parseEditorState/$commitPendingUpdates froze it — internal misuse or plugin code holding the map reference.","commonSituations":"Custom code attempting to reset editor state by clearing its node map; plugins copying state via internal APIs; dev-only invariant failures.","solutions":["Never clear an EditorState's _nodeMap; create a new state via parseEditorState/editor.setEditorState instead","Refactor plugins to use public Lexical APIs for state changes","Search codebase for direct _nodeMap usage and remove it","If seen from a dependency, pin/update that dependency"],"exampleFix":"// before\neditorState._nodeMap.clear();\n// after\neditor.setEditorState(editor.parseEditorState(serializedState));","handlingStrategy":"type-guard","validationCode":"// To reset state, create a new one — never clear the map\nif (needsReset) {\n  editor.setEditorState(editor.parseEditorState(serialized));\n}","typeGuard":"function canMutate(map: Map<string, unknown>): boolean {\n  try { map.set('__t', 0); map.delete('__t'); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  resetEditorState(editor);\n} catch (e) {\n  if (/frozen Lexical node map/.test(e.message)) {\n    editor.setEditorState(editor.parseEditorState(emptyDoc));\n  }\n}","preventionTips":["Replace 'clear the state' logic with setEditorState(new state)","Avoid storing long-lived references to old EditorStates","Audit code for direct _nodeMap access","Treat editor states as read-only, always"],"tags":["javascript","lexical","rich-text","dev-only","immutability"],"backgroundTag":"lexical-immutable-state-violation","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}