Files
twenty/packages/twenty-front/src/modules/blocknote-editor/utils/getBackspaceTopLevelGuardExtension.ts
T
Sonarly Claude Code 8f7a10fb0e fix(twenty-front): guard BlockNote backspace handler against top-level node position
https://sonarly.com/issue/33776?type=bug

Pressing Backspace at the beginning of the first block in a Dashboard rich text widget throws an unhandled RangeError from ProseMirror, crashing the editor.

Fix: Added a tiptap Extension (`backspaceTopLevelGuard`) that intercepts Backspace keypresses at the beginning of the first top-level block in BlockNote editors, preventing BlockNote 0.47.x's internal parent-block traversal from calling `resolvedPos.before(0)` which throws `RangeError: "There is no position before the top-level node"`.

The extension:
1. Only activates on Backspace with an empty (collapsed) selection
2. Checks if the cursor is at offset 0 within a node at depth ≤ 2 (BlockNote's doc → blockGroup → blockContainer structure)
3. Confirms the position is at the document start (`$from.before($from.depth) === 1`)
4. Returns `true` (handled) to swallow the keypress — there is nothing to delete at this position anyway

Applied to both `useCreateBlockNote` call sites:
- `StandaloneRichTextEditorContent.tsx` (Dashboard rich text widget — the crash site from the Sentry event)
- `RichTextFieldEditor.tsx` (Record field rich text editor — same BlockNote version, same potential crash)

Uses `_tiptapOptions.extensions` to inject the guard extension into BlockNote's underlying tiptap editor.
2026-05-03 22:38:54 +00:00

39 lines
1.2 KiB
TypeScript

import { Extension } from '@tiptap/core';
// Guards against a BlockNote 0.47.x bug where pressing Backspace at the
// beginning of the first top-level block throws:
// RangeError: "There is no position before the top-level node"
// BlockNote's internal parent-block traversal calls
// resolvedPos.before(depth - 1) without checking that depth > 1.
export const getBackspaceTopLevelGuardExtension = () =>
Extension.create({
name: 'backspaceTopLevelGuard',
priority: 1000,
addKeyboardShortcuts() {
return {
Backspace: ({ editor }) => {
const { $from, empty } = editor.state.selection;
if (!empty) {
return false;
}
// When the cursor is at the very beginning of the first
// top-level block (offset 0 and depth ≤ 2 in BlockNote's
// doc → blockGroup → blockContainer structure), swallow the
// Backspace to prevent the crash. There is nothing to delete
// in this position anyway.
if ($from.parentOffset === 0 && $from.depth <= 2) {
const atDocStart = $from.before($from.depth) === 1;
if (atDocStart) {
return true;
}
}
return false;
},
};
},
});