From ae451f73df14e3d6085aca8f58ae68d647a5e053 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 26 Mar 2026 21:59:28 +0000 Subject: [PATCH] fix: reveal mode was destroying chat input text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reveal mode's text replacement was walking ALL text nodes on the page including contenteditable elements (the chat input box on Claude.ai, ChatGPT, etc.), overwriting the user's typed text with garbled revealed content. Fixed by adding contenteditable checks to: - revealInElement() — returns early if element is contenteditable - unrevealInElement() — same - highlightMatches() — TreeWalker filter rejects contenteditable nodes - MutationObserver — skips addedNodes and characterData mutations inside contenteditable elements All four code paths now use parent.closest('[contenteditable="true"]') to detect and skip chat input areas. https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn --- src/content/content.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/content/content.js b/src/content/content.js index b8c7cd4..5b4fc7b 100644 --- a/src/content/content.js +++ b/src/content/content.js @@ -1318,12 +1318,16 @@ function revealInElement(el) { if (SKIP_REVEAL_TAGS.has(el.tagName)) return; if (el.classList?.contains('ss-reveal-badge')) return; + // Never touch contenteditable elements (chat input boxes) + if (el.isContentEditable) return; const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { acceptNode(node) { const parent = node.parentElement; if (parent && SKIP_REVEAL_TAGS.has(parent.tagName)) return NodeFilter.FILTER_REJECT; if (parent?.closest?.('.ss-autodetect-warning, .ss-presend-warning, .ss-reveal-badge')) return NodeFilter.FILTER_REJECT; + // Skip contenteditable areas (chat input) + if (parent?.closest?.('[contenteditable="true"]')) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); @@ -1346,8 +1350,15 @@ function unrevealInElement(el) { if (SKIP_REVEAL_TAGS.has(el.tagName)) return; + if (el.isContentEditable) return; - const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT); + const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { + acceptNode(node) { + const parent = node.parentElement; + if (parent?.closest?.('[contenteditable="true"]')) return NodeFilter.FILTER_REJECT; + return NodeFilter.FILTER_ACCEPT; + } + }); let textNode; while ((textNode = walker.nextNode())) { const original = originalTexts.get(textNode); @@ -1374,6 +1385,7 @@ const parent = node.parentElement; if (parent && SKIP_REVEAL_TAGS.has(parent.tagName)) return NodeFilter.FILTER_REJECT; if (parent?.closest?.('.ss-autodetect-warning, .ss-presend-warning, .ss-reveal-badge')) return NodeFilter.FILTER_REJECT; + if (parent?.closest?.('[contenteditable="true"]')) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); @@ -1454,10 +1466,16 @@ // Only do text replacement in reveal mode if (settings.revealMode) { if (node.nodeType === Node.ELEMENT_NODE) { - if (!SKIP_REVEAL_TAGS.has(node.tagName)) { + // Skip contenteditable (chat input) and skipped tags + if (!SKIP_REVEAL_TAGS.has(node.tagName) && + !node.isContentEditable && + !node.closest?.('[contenteditable="true"]')) { revealInElement(node); } } else if (node.nodeType === Node.TEXT_NODE) { + // Skip text nodes inside contenteditable + const parent = node.parentElement; + if (parent?.closest?.('[contenteditable="true"]')) continue; const text = node.textContent; if (text && text.length >= MIN_STRING_LENGTH) { if (!originalTexts.has(node)) { @@ -1479,6 +1497,8 @@ const text = mutation.target.textContent; if (text && text.length >= MIN_STRING_LENGTH) { const parent = mutation.target.parentElement; + // Skip contenteditable (chat input) + if (parent?.closest?.('[contenteditable="true"]')) continue; if (parent && !SKIP_REVEAL_TAGS.has(parent.tagName)) { if (!originalTexts.has(mutation.target)) { originalTexts.set(mutation.target, text);