diff --git a/entrypoints/content.ts b/entrypoints/content.ts index 10b8216..f7c7d5d 100644 --- a/entrypoints/content.ts +++ b/entrypoints/content.ts @@ -496,19 +496,35 @@ export default defineContentScript({ // contenteditable / DOM range path console.log('[LexAI Replace] using DOM range'); try { - snapRange.deleteContents(); - const textNode = document.createTextNode(resultText); - snapRange.insertNode(textNode); + // Strategy: restore the selection from the cloned range, then use + // execCommand('insertText') which works reliably on contenteditable. + // insertNode() on stale ranges often fails after async operations. const sel = window.getSelection(); if (sel) { sel.removeAllRanges(); - const newRange = document.createRange(); - newRange.setStartAfter(textNode); - newRange.collapse(true); - sel.addRange(newRange); + sel.addRange(snapRange); // restore original selection + } + + // Try execCommand first (works on most contenteditable editors) + const inserted = document.execCommand('insertText', false, resultText); + + if (!inserted) { + // Fallback: direct DOM manipulation + snapRange.deleteContents(); + const textNode = document.createTextNode(resultText); + snapRange.insertNode(textNode); + if (sel) { + sel.removeAllRanges(); + const newRange = document.createRange(); + newRange.setStartAfter(textNode); + newRange.collapse(true); + sel.addRange(newRange); + } } } catch (err) { console.warn('[LexAI Replace] range replace failed:', err); + // Last resort: execCommand + try { document.execCommand('insertText', false, resultText); } catch (_) {} } } else { console.warn('[LexAI Replace] NO VALID SNAP STATE — snapStart:', snapStart, 'snapEnd:', snapEnd, 'snapElement:', snapElement, 'snapRange:', snapRange); diff --git a/lexai-chrome-mv3.zip b/lexai-chrome-mv3.zip index 17be447..f408808 100644 Binary files a/lexai-chrome-mv3.zip and b/lexai-chrome-mv3.zip differ