diff --git a/src/renderer/components/AnalysisPrioritySection.tsx b/src/renderer/components/AnalysisPrioritySection.tsx index b426bb58..e9487705 100644 --- a/src/renderer/components/AnalysisPrioritySection.tsx +++ b/src/renderer/components/AnalysisPrioritySection.tsx @@ -62,7 +62,10 @@ export function AnalysisPrioritySection({ if (!isEditing) { return ( -
+
+
diff --git a/src/renderer/components/EmailDetail.tsx b/src/renderer/components/EmailDetail.tsx index 58b9c283..14606681 100644 --- a/src/renderer/components/EmailDetail.tsx +++ b/src/renderer/components/EmailDetail.tsx @@ -822,7 +822,7 @@ function ThreadMessage({ // White card for rich HTML emails in dark mode, dark card otherwise return (
+
{/* Thread header */}
@@ -3812,9 +3816,13 @@ function EmailDetailInner({ isFullView = false }: EmailDetailProps) { )} {/* Thread messages - single scroll, no nested scrolls */} -
- {threadEmails.map((email) => ( -
+
+ {threadEmails.map((email, index) => ( +
{ + test.describe.configure({ mode: "serial" }); + + let electronApp: ElectronApplication; + let page: Page; + + test.beforeAll(async ({}, testInfo) => { + const launched = await launchElectronApp({ workerIndex: testInfo.workerIndex }); + electronApp = launched.app; + page = launched.page; + await page.locator("[data-thread-id]").first().waitFor({ timeout: 10000 }); + }); + + test.afterAll(async () => { + if (electronApp) await closeApp(electronApp); + }); + + test("a short email fills the pane and keeps classification at the bottom", async () => { + await openEmail(page, "CI workflow failed on main"); + + const scroller = page.getByTestId("email-detail-scroll"); + const messages = page.getByTestId("email-thread-messages"); + const message = page.locator('[data-email-id="demo-006"] .group\\/msg'); + const analysis = page.getByTestId("analysis-priority-section"); + + await expect(analysis).toBeVisible(); + + const [scrollerBox, messagesBox, messageBox, analysisBox] = await Promise.all([ + scroller.boundingBox(), + messages.boundingBox(), + message.boundingBox(), + analysis.boundingBox(), + ]); + + expect(scrollerBox).not.toBeNull(); + expect(messagesBox).not.toBeNull(); + expect(messageBox).not.toBeNull(); + expect(analysisBox).not.toBeNull(); + + const scrollerBottom = scrollerBox!.y + scrollerBox!.height; + const analysisBottom = analysisBox!.y + analysisBox!.height; + const messageBottom = messageBox!.y + messageBox!.height; + + expect(Math.abs(scrollerBottom - analysisBottom)).toBeLessThanOrEqual(1); + expect(Math.abs(messageBottom - analysisBox!.y)).toBeLessThanOrEqual(1); + expect(messagesBox!.height).toBeGreaterThan(messageBox!.height - 1); + await expect + .poll(() => scroller.evaluate((element) => element.scrollHeight)) + .toBe(await scroller.evaluate((element) => element.clientHeight)); + }); + + test("a long email still scrolls to the classification row", async () => { + await page.getByRole("button", { name: "Back" }).click(); + await openEmail(page, "Weekly Product Update"); + + const scroller = page.getByTestId("email-detail-scroll"); + const analysis = page.getByTestId("analysis-priority-section"); + const iframe = page.locator('iframe[title="Email content"]').first(); + + await expect(iframe).toBeVisible(); + await expect.poll(async () => (await iframe.boundingBox())?.height ?? 0).toBeGreaterThan(400); + + const dimensions = await scroller.evaluate((element) => ({ + clientHeight: element.clientHeight, + scrollHeight: element.scrollHeight, + })); + expect(dimensions.scrollHeight).toBeGreaterThan(dimensions.clientHeight); + + await expect + .poll(() => + scroller.evaluate((element) => { + element.scrollTop = element.scrollHeight; + const analysisSection = element.querySelector( + '[data-testid="analysis-priority-section"]', + ); + if (!analysisSection) return Number.POSITIVE_INFINITY; + const scrollerBox = element.getBoundingClientRect(); + const analysisBox = analysisSection.getBoundingClientRect(); + return Math.abs(scrollerBox.bottom - analysisBox.bottom); + }), + ) + .toBeLessThanOrEqual(1); + await expect(analysis).toBeVisible(); + }); +});