diff --git a/static/js/chat.js b/static/js/chat.js index ea2d8c1bb..df4550d8d 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -4917,7 +4917,7 @@ import { wireArrowUpRecall, getUserMessagesFromChatHistory } from './composerArr if (replaceFromHere) { // Regenerate flows intentionally trim history to this point before // resubmitting. The plain "Resend message" action must not do this. - const keepCount = msgIndex; + const keepCount = msgIndex + 1; await fetch(`${API_BASE}/api/session/${sessionId}/truncate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -5031,7 +5031,7 @@ import { wireArrowUpRecall, getUserMessagesFromChatHistory } from './composerArr variants.push({ raw: oldRaw, html: oldHtml, label: 'original' }); } - const keepCount = userIndex; + const keepCount = userIndex + 1; try { await fetch(`${API_BASE}/api/session/${sessionId}/truncate`, { diff --git a/static/js/document.js b/static/js/document.js index e0c7a7632..7891d9b40 100644 --- a/static/js/document.js +++ b/static/js/document.js @@ -5428,7 +5428,10 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js'; const target = rawTarget && rawTarget.nodeType === Node.TEXT_NODE ? rawTarget.parentElement : rawTarget; const sendButtons = Array.from(document.querySelectorAll('#doc-email-send-btn')); const targetBtn = target && target.closest ? target.closest('#doc-email-send-btn') : null; - const rectBtn = sendButtons.find((candidate) => _eventInsideElement(e, candidate)); + const rectBtn = sendButtons.find((candidate) => { + const rect = candidate.getBoundingClientRect(); + return candidate.offsetParent !== null && rect.width > 0 && rect.height > 0 && _eventInsideElement(e, candidate); + }); const btn = targetBtn || rectBtn || null; if (!btn || btn.disabled) return; if (e) { diff --git a/tests/test_document_email_send_intent.py b/tests/test_document_email_send_intent.py new file mode 100644 index 000000000..c283f28cd --- /dev/null +++ b/tests/test_document_email_send_intent.py @@ -0,0 +1,25 @@ +from pathlib import Path + + +SRC = Path("static/js/document.js") + + +def _send_intent_body(): + source = SRC.read_text(encoding="utf-8") + start = source.index("const handleSendIntent = (e) => {") + end = source.index("window.odysseusEmailSendIntent = handleSendIntent;", start) + return source[start:end] + + +def test_email_send_intent_ignores_hidden_zero_rect_buttons(): + body = _send_intent_body() + + rect_idx = body.index("const rect = candidate.getBoundingClientRect();") + visible_idx = body.index("candidate.offsetParent !== null") + width_idx = body.index("rect.width > 0") + height_idx = body.index("rect.height > 0") + hit_idx = body.index("_eventInsideElement(e, candidate)") + + assert rect_idx < visible_idx < hit_idx + assert rect_idx < width_idx < hit_idx + assert rect_idx < height_idx < hit_idx diff --git a/tests/test_resend_message_nondestructive.py b/tests/test_resend_message_nondestructive.py index c107e84fc..426b7d4b2 100644 --- a/tests/test_resend_message_nondestructive.py +++ b/tests/test_resend_message_nondestructive.py @@ -41,3 +41,20 @@ def test_only_regenerate_callers_opt_into_replace_from_here(): assert "window.chatModule.resendUserMessage(msgElement);" in renderer assert "window.chatModule.resendUserMessage(userMsgEl, { replaceFromHere: true });" in renderer + + +def test_replace_and_regenerate_keep_the_original_user_message(): + chat = _CHAT_JS.read_text(encoding="utf-8") + resend = chat[ + chat.index("export async function resendUserMessage("): + chat.index("export async function regenerateFrom(") + ] + regenerate = chat[ + chat.index("export async function regenerateFrom("): + chat.index("// Pending variants from a regeneration", chat.index("export async function regenerateFrom(")) + ] + + assert "const keepCount = msgIndex + 1;" in resend + assert "const keepCount = userIndex + 1;" in regenerate + assert "const keepCount = msgIndex;" not in resend + assert "const keepCount = userIndex;" not in regenerate