From 5bb3fe05508a209bf75ca2b5a57ef27bdfab44f1 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Sat, 23 May 2026 15:28:35 +0200 Subject: [PATCH] fix: recover stale table editor transforms --- ...torTransformErrorRecoveryExtension.test.ts | 22 +++++++++++++ ...chEditorTransformErrorRecoveryExtension.ts | 31 ++++++++++++++----- .../smoke/table-cell-paste-regression.spec.ts | 1 + tests/smoke/table-resize-crash.spec.ts | 1 + 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/components/richEditorTransformErrorRecoveryExtension.test.ts b/src/components/richEditorTransformErrorRecoveryExtension.test.ts index d32ab8a9..ff23ab51 100644 --- a/src/components/richEditorTransformErrorRecoveryExtension.test.ts +++ b/src/components/richEditorTransformErrorRecoveryExtension.test.ts @@ -48,6 +48,12 @@ describe('isRecoverableEditorTransformError', () => { expect(isRecoverableEditorTransformError(new RangeError( 'Invalid content for node blockContainer: ', ))).toBe(true) + expect(isRecoverableEditorTransformError(new RangeError( + 'Index 1 out of range for ', + ))).toBe(true) + expect(isRecoverableEditorTransformError(new RangeError( + 'Index 1 out of range for ', + ))).toBe(false) expect(isRecoverableEditorTransformError(new Error('unrelated'))).toBe(false) }) }) @@ -93,6 +99,22 @@ describe('installRichEditorTransformErrorRecovery', () => { }) }) + it('recovers table selection transactions whose target row changed underneath BlockNote', () => { + const tableError = new RangeError( + 'Index 1 out of range for ', + ) + const { currentDoc, view } = createView(tableError) + const recoverDocument = vi.fn() + + installRichEditorTransformErrorRecovery(view, { recoverDocument }) + + expect(() => view.dispatch({ before: currentDoc })).not.toThrow() + expect(recoverDocument).toHaveBeenCalledTimes(1) + expect(trackEvent).toHaveBeenCalledWith('rich_editor_transform_error_recovered', { + reason: 'table_position_out_of_range', + }) + }) + it('keeps non-ProseMirror dispatch failures visible', () => { const { view } = createView(new Error('plugin failed')) diff --git a/src/components/richEditorTransformErrorRecoveryExtension.ts b/src/components/richEditorTransformErrorRecoveryExtension.ts index 6a3c3954..a9069f1b 100644 --- a/src/components/richEditorTransformErrorRecoveryExtension.ts +++ b/src/components/richEditorTransformErrorRecoveryExtension.ts @@ -35,7 +35,11 @@ interface RepairableBlockNoteEditor { replaceBlocks?: (currentBlocks: unknown[], nextBlocks: unknown[]) => unknown } -type RecoveryReason = 'mismatched_transaction' | 'stale_transaction' | 'transform_error' +type RecoveryReason = + | 'mismatched_transaction' + | 'stale_transaction' + | 'table_position_out_of_range' + | 'transform_error' function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null @@ -67,12 +71,20 @@ function isInvalidContentTransactionError(error: unknown): boolean { return error instanceof RangeError && error.message.startsWith('Invalid content for node ') } +function isTablePositionOutOfRangeError(error: unknown): boolean { + return error instanceof RangeError && /^Index \d+ out of range for