From 85eb4c35fdeeec34eda09de66c9ea40c28bb686b Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 14 Oct 2026 09:18:10 +0200 Subject: [PATCH] fix: keep the above-quote frame off the quote toggle and the footer, and give email-footer the message id --- .../email/__tests__/reader-plugin-surfaces.test.tsx | 13 +++++++++++++ components/email/email-viewer.tsx | 3 ++- components/email/reader-plugin-surfaces.tsx | 5 ++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/components/email/__tests__/reader-plugin-surfaces.test.tsx b/components/email/__tests__/reader-plugin-surfaces.test.tsx index a5586710b..301257065 100644 --- a/components/email/__tests__/reader-plugin-surfaces.test.tsx +++ b/components/email/__tests__/reader-plugin-surfaces.test.tsx @@ -159,6 +159,14 @@ describe('reader-header and reader-below-subject', () => { }); }); +describe('email-footer', () => { + it('hands the frame the id of the open message', () => { + offerSlots('p1', ['email-footer']); + viewer(textMail('Hi')); + expect(slotProps(screen.getByTestId('slot-email-footer'))).toEqual({ emailId: 'e1' }); + }); +}); + describe('reader-above-quote', () => { it('sits between the new text of a plain-text reply and its folded quote', () => { offerSlots('p1', ['reader-above-quote']); @@ -170,6 +178,11 @@ describe('reader-above-quote', () => { expect(follows(frame, quote)).toBe(true); expect(follows(screen.getByText(/Sounds good/), frame)).toBe(true); expect((slotProps(frame).email as { id: string }).id).toBe('e1'); + // The frame renders inside the anchor, so the anchor must grow with it + // instead of letting the frame spill over the quote toggle and the footer. + const anchor = container.querySelector(`[${QUOTE_ANCHOR_ATTR}]`)!; + expect(anchor.contains(frame)).toBe(true); + expect(anchor.style.height).toBe(''); }); it('is not mounted for a message without a quote, or without a plugin', () => { diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 0fb8dfe83..a43e1c776 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -1437,6 +1437,7 @@ export function EmailViewer({ ); const pluginEmailProps = useMemo(() => ({ email: pluginEmailView }), [pluginEmailView]); const readerActionContext = useMemo(() => ({ emailId: email?.id }), [email?.id]); + const pluginFooterProps = useMemo(() => ({ emailId: email?.id }), [email?.id]); const [detailSidebarCollapsed, setDetailSidebarCollapsed] = useState(() => { if (typeof window === 'undefined') return false; try { return localStorage.getItem('emailDetailSidebarCollapsed') === '1'; } catch { return false; } @@ -5952,7 +5953,7 @@ export function EmailViewer({ )} - + {/* Mountain View: the conversation's later messages, folded. */} {mvAfter.length > 0 && ( diff --git a/components/email/reader-plugin-surfaces.tsx b/components/email/reader-plugin-surfaces.tsx index dbba3688d..7a7d87f8b 100644 --- a/components/email/reader-plugin-surfaces.tsx +++ b/components/email/reader-plugin-surfaces.tsx @@ -145,7 +145,10 @@ export function insertQuoteAnchor(root: HTMLElement): HTMLElement | null { if (!toggle?.parentNode) return null; const anchor = root.ownerDocument.createElement("div"); anchor.setAttribute(QUOTE_ANCHOR_ATTR, ""); - anchor.style.cssText = "display:block;margin:0;padding:0;border:0;height:0;white-space:normal;"; + // No fixed height: in a plain-text body the frame renders inside the anchor + // and must push the quote toggle down, not lie over it. QuoteOverlay sets + // the height itself for an HTML body. + anchor.style.cssText = "display:block;margin:0;padding:0;border:0;white-space:normal;"; toggle.parentNode.insertBefore(anchor, toggle); return anchor; }