diff --git a/static/js/emailLibrary.js b/static/js/emailLibrary.js
index 458a7471e..a8380bba6 100644
--- a/static/js/emailLibrary.js
+++ b/static/js/emailLibrary.js
@@ -2559,17 +2559,16 @@ async function _toggleCardPreview(card, em) {
-
- ${_hasMultipleRecipients(data) ? `
` : ''}
-
-
+
+
+ ${_hasMultipleRecipients(data) ? `
` : ''}
+
-
@@ -4409,17 +4408,16 @@ async function _openEmailWindow(em, folder) {
-
- ${_hasMultipleRecipients(data) ? `
` : ''}
-
-
+
+
+ ${_hasMultipleRecipients(data) ? `
` : ''}
+
-
@@ -5359,7 +5357,7 @@ function _summaryIcon(data) {
return ``;
}
-async function _runAiReplyFromButton(btn, em, data, mode) {
+async function _runAiReplyFromButton(btn, em, data, mode, noteHint = '') {
_snapEmailModalToLeftSidebar(btn.closest('.modal'));
btn.disabled = true;
const orig = btn.innerHTML;
@@ -5371,7 +5369,7 @@ async function _runAiReplyFromButton(btn, em, data, mode) {
btn.appendChild(wp.element);
} catch (_) {}
try {
- if (state._onEmailClick) await state._onEmailClick({ email: em, emailData: data, mode });
+ if (state._onEmailClick) await state._onEmailClick({ email: em, emailData: data, mode, noteHint });
} finally {
try { wp && wp.stop(); } catch (_) {}
btn.disabled = false;
@@ -5406,16 +5404,47 @@ function _showAiReplyChoice(btn, em, data) {
// Full = layered concentric circles to suggest "more, deeper" — not a fully
// filled circle so it reads as a complement to the lightning, not as a "stop".
menu.innerHTML = `
-
-
+
+
+
+
+
+
+
+
+
+
+
`;
menu.addEventListener('click', async (ev) => {
+ const noteToggle = ev.target.closest('[data-act="note-toggle"]');
+ if (noteToggle) {
+ ev.preventDefault(); ev.stopPropagation();
+ const panel = menu.querySelector('.email-ai-reply-note');
+ const wasHidden = panel.hidden;
+ panel.hidden = !wasHidden;
+ if (wasHidden) panel.querySelector('textarea')?.focus();
+ return;
+ }
+ const noteSend = ev.target.closest('[data-act="note-send"]');
+ if (noteSend) {
+ ev.preventDefault(); ev.stopPropagation();
+ const note = (menu.querySelector('.email-ai-reply-note-text')?.value || '').trim();
+ _closeAiReplyChoice();
+ await _runAiReplyFromButton(btn, em, data, 'ai-reply-full', note);
+ return;
+ }
const choice = ev.target.closest('[data-mode]');
if (!choice) return;
ev.preventDefault();
@@ -5424,6 +5453,9 @@ function _showAiReplyChoice(btn, em, data) {
_closeAiReplyChoice();
await _runAiReplyFromButton(btn, em, data, mode);
});
+ // Esc closes the popover; ignore plain clicks inside the menu so the
+ // textarea stays focused.
+ menu.addEventListener('mousedown', (ev) => ev.stopPropagation());
document.body.appendChild(menu);
setTimeout(() => document.addEventListener('click', _closeAiReplyChoice, true), 0);
}
diff --git a/static/style.css b/static/style.css
index 94b79b599..ce23aa6d7 100644
--- a/static/style.css
+++ b/static/style.css
@@ -28078,23 +28078,19 @@ button .spinner-whirlpool {
.recipient-chip { flex-shrink: 0; }
}
.email-reader-actions {
- display: flex; gap: 4px; flex-wrap: wrap; align-items: center;
+ display: flex; flex-direction: column; gap: 4px; align-items: flex-end;
flex-shrink: 0;
- justify-content: flex-end;
margin-top: -4px;
}
-/* The HTML wraps the buttons in two .email-reader-actions-row divs (primary
- + secondary). On mobile those flatten via `display: contents` inside the
- max-width:768px block; apply the same here so the whole row stays on one
- line on desktop too. */
+/* Two stacked rows inside .email-reader-actions:
+ primary — Summary + More (top)
+ secondary — Reply / Reply all / Forward / AI reply (bottom) */
.email-reader-actions-row {
- display: contents;
-}
-/* Pin the More button to the far right of the flattened flex row — it
- sits at position 5 in the primary row's source order, so without an
- explicit order the secondary AI/Summary buttons land after it. */
-.email-reader-actions .email-reader-more-wrap {
- order: 99;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 4px;
}
.email-reader-atts {
display: flex; flex-wrap: wrap; gap: 6px;