From 8c9aeb535a4d77612af6d9b15c59aee85c26ce04 Mon Sep 17 00:00:00 2001 From: X Developer Date: Fri, 24 Jul 2026 15:27:18 +0430 Subject: [PATCH 1/2] fix: clamp markdown cursor range offsets --- src/web/utils/cursorUtils.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/web/utils/cursorUtils.ts b/src/web/utils/cursorUtils.ts index 82eebc7f..f35f474d 100644 --- a/src/web/utils/cursorUtils.ts +++ b/src/web/utils/cursorUtils.ts @@ -3,6 +3,14 @@ import {isChildOfMultilineMarkdownElement} from './blockUtils'; import {findHTMLElementInTree, getTreeNodeByIndex} from './treeUtils'; import type {TreeNode} from './treeUtils'; +function getMaxRangeOffset(node: ChildNode): number { + return node.nodeType === Node.TEXT_NODE ? (node.textContent ?? '').length : node.childNodes.length; +} + +function getClampedRangeOffset(node: ChildNode, offset: number): number { + return Math.max(0, Math.min(offset, getMaxRangeOffset(node))); +} + function setCursorPosition(target: MarkdownTextInputElement, startIndex: number, endIndex: number | null = null, shouldScrollIntoView = false) { // We don't want to move the cursor if the target is not focused if (!target.tree || target !== document.activeElement) { @@ -29,14 +37,16 @@ function setCursorPosition(target: MarkdownTextInputElement, startIndex: number, range.setStartBefore(startTreeNode.element); } else { const startElement = startTreeNode.element; - range.setStart((startElement.childNodes[0] || startElement) as ChildNode, start - startTreeNode.start); + const startNode = (startElement.childNodes[0] || startElement) as ChildNode; + range.setStart(startNode, getClampedRangeOffset(startNode, start - startTreeNode.start)); } if (endTreeNode.type === 'br') { range.setEndBefore(endTreeNode.element); } else { const endElement = endTreeNode.element; - range.setEnd((endElement.childNodes[0] || endElement) as ChildNode, (end || start) - endTreeNode.start); + const endNode = (endElement.childNodes[0] || endElement) as ChildNode; + range.setEnd(endNode, getClampedRangeOffset(endNode, (end || start) - endTreeNode.start)); } if (!end) { From a572dff0aca0ac8f1998b31f2911780876c8619d Mon Sep 17 00:00:00 2001 From: X Developer Date: Wed, 29 Jul 2026 08:34:21 +0430 Subject: [PATCH 2/2] fix: update documentation for clamping cursor range offsets --- src/web/utils/cursorUtils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/web/utils/cursorUtils.ts b/src/web/utils/cursorUtils.ts index f35f474d..9f6676c7 100644 --- a/src/web/utils/cursorUtils.ts +++ b/src/web/utils/cursorUtils.ts @@ -7,6 +7,11 @@ function getMaxRangeOffset(node: ChildNode): number { return node.nodeType === Node.TEXT_NODE ? (node.textContent ?? '').length : node.childNodes.length; } +/** + * Range offsets are applied to the real DOM node, not the parsed markdown tree. + * When the tree is temporarily stale, clamp to the DOM node's bounds so cursor + * restoration lands at the nearest valid position instead of throwing. + */ function getClampedRangeOffset(node: ChildNode, offset: number): number { return Math.max(0, Math.min(offset, getMaxRangeOffset(node))); }