From 6659185faca127d20c8430fe3831eb92106697d3 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 7 Apr 2026 18:46:22 -0700 Subject: [PATCH] fix(FN-1082): harden mailbox modal mobile layout - Add flex-column base layout for .mailbox-modal and make .mailbox-content grow/scroll correctly - Introduce a dedicated <=768px mailbox mobile CSS section for header, tabs, detail view, and FAB spacing - Add MailboxModal regression tests that assert required CSS rules and detail-view structural hooks - Increase batch GitHub issue import route test timeout to 30s for slower retry paths in CI --- .../__tests__/MailboxModal.test.tsx | 82 +++++++++++++++++++ packages/dashboard/app/styles.css | 75 +++++++++++++++++ packages/dashboard/src/routes.test.ts | 2 +- 3 files changed, 158 insertions(+), 1 deletion(-) diff --git a/packages/dashboard/app/components/__tests__/MailboxModal.test.tsx b/packages/dashboard/app/components/__tests__/MailboxModal.test.tsx index 64d613db1..0560e579f 100644 --- a/packages/dashboard/app/components/__tests__/MailboxModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/MailboxModal.test.tsx @@ -376,4 +376,86 @@ describe("MailboxModal", () => { expect(mockFetchInbox).toHaveBeenCalledWith({ limit: 50 }, "proj-1"); }); }); + + describe("mobile layout CSS regressions", () => { + it("defines mailbox base flex layout for modal and content containers", async () => { + const fs = await import("fs"); + const path = await import("path"); + const cssPath = path.resolve(__dirname, "../../styles.css"); + const css = fs.readFileSync(cssPath, "utf-8"); + + const modalBlockMatch = css.match(/\.mailbox-modal\s*\{([^}]*)\}/); + expect(modalBlockMatch).toBeTruthy(); + const modalBlock = modalBlockMatch![1]; + expect(modalBlock).toContain("display: flex;"); + expect(modalBlock).toContain("flex-direction: column;"); + + const contentBlockMatch = css.match(/\.mailbox-content\s*\{([^}]*)\}/); + expect(contentBlockMatch).toBeTruthy(); + const contentBlock = contentBlockMatch![1]; + expect(contentBlock).toContain("flex: 1;"); + expect(contentBlock).toContain("min-height: 0;"); + }); + + it("keeps mobile mailbox overrides in the dedicated media-query section", async () => { + const fs = await import("fs"); + const path = await import("path"); + const cssPath = path.resolve(__dirname, "../../styles.css"); + const css = fs.readFileSync(cssPath, "utf-8"); + + const sectionStart = css.indexOf("/* ── Mailbox — Mobile"); + expect(sectionStart).toBeGreaterThan(-1); + + const sectionEnd = css.indexOf("/* ── Message Composer", sectionStart); + expect(sectionEnd).toBeGreaterThan(sectionStart); + + const mailboxMobileSection = css.slice(sectionStart, sectionEnd); + + expect(mailboxMobileSection).toContain("@media (max-width: 768px)"); + expect(mailboxMobileSection).toContain(".mailbox-header"); + expect(mailboxMobileSection).toContain("flex-wrap: wrap;"); + expect(mailboxMobileSection).toContain(".mailbox-title"); + expect(mailboxMobileSection).toContain("flex-shrink: 0;"); + expect(mailboxMobileSection).toContain(".mailbox-header-actions"); + expect(mailboxMobileSection).toContain("overflow-x: auto;"); + expect(mailboxMobileSection).toContain("-webkit-overflow-scrolling: touch;"); + expect(mailboxMobileSection).toContain("scrollbar-width: none;"); + expect(mailboxMobileSection).toContain(".mailbox-tabs::-webkit-scrollbar"); + expect(mailboxMobileSection).toContain("display: none;"); + expect(mailboxMobileSection).toContain(".mailbox-tab"); + expect(mailboxMobileSection).toContain("padding: 8px 12px;"); + expect(mailboxMobileSection).toContain("max-height: calc(100dvh - 120px);"); + expect(mailboxMobileSection).toContain(".mailbox-message-detail-header"); + expect(mailboxMobileSection).toContain("flex-direction: column;"); + expect(mailboxMobileSection).toContain("align-items: flex-start;"); + expect(mailboxMobileSection).toContain(".mailbox-message-detail-actions"); + expect(mailboxMobileSection).toContain(".mailbox-message-participants"); + expect(mailboxMobileSection).toContain("gap: 8px;"); + expect(mailboxMobileSection).toContain(".mailbox-conversation-msg"); + expect(mailboxMobileSection).toContain("padding: 8px 10px;"); + expect(mailboxMobileSection).toContain(".mailbox-agent-select"); + expect(mailboxMobileSection).toContain("max-width: 100%;"); + expect(mailboxMobileSection).toContain(".mailbox-compose-fab"); + expect(mailboxMobileSection).toContain("bottom: 16px;"); + expect(mailboxMobileSection).toContain("right: 16px;"); + expect(mailboxMobileSection).toContain(".mailbox-empty"); + expect(mailboxMobileSection).toContain("padding: 32px 12px;"); + }); + + it("renders detail-view structural hooks targeted by mobile overrides", async () => { + const { container } = render(); + + await waitFor(() => { + expect(screen.getByTestId("mailbox-item-msg-001")).toBeDefined(); + }); + + fireEvent.click(screen.getByTestId("mailbox-item-msg-001")); + + await waitFor(() => { + expect(container.querySelector(".mailbox-message-detail-header")).toBeTruthy(); + expect(container.querySelector(".mailbox-message-detail-actions")).toBeTruthy(); + expect(container.querySelector(".mailbox-message-participants")).toBeTruthy(); + }); + }); + }); }); diff --git a/packages/dashboard/app/styles.css b/packages/dashboard/app/styles.css index 3e94fb3dd..1aaaddcb1 100644 --- a/packages/dashboard/app/styles.css +++ b/packages/dashboard/app/styles.css @@ -21154,6 +21154,8 @@ html .column.drag-over * { .mailbox-modal { max-height: 80vh; + display: flex; + flex-direction: column; } .mailbox-header { @@ -21237,6 +21239,8 @@ html .column.drag-over * { } .mailbox-content { + flex: 1; + min-height: 0; padding: 16px; overflow-y: auto; max-height: calc(80vh - 140px); @@ -21555,6 +21559,77 @@ html .column.drag-over * { width: 80%; } +/* ── Mailbox — Mobile (≤ 768px) ───────────────────────────────────── */ + +@media (max-width: 768px) { + .mailbox-header { + flex-wrap: wrap; + gap: 8px; + } + + .mailbox-title { + flex-shrink: 0; + } + + .mailbox-header-actions { + flex-wrap: wrap; + gap: 6px; + justify-content: flex-end; + } + + .mailbox-tabs { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding: 0 12px; + } + + .mailbox-tabs::-webkit-scrollbar { + display: none; + } + + .mailbox-tab { + flex-shrink: 0; + padding: 8px 12px; + } + + .mailbox-content { + padding: 12px; + max-height: calc(100dvh - 120px); + } + + .mailbox-message-detail-header { + flex-direction: column; + align-items: flex-start; + } + + .mailbox-message-detail-actions { + flex-wrap: wrap; + } + + .mailbox-message-participants { + flex-direction: column; + gap: 8px; + } + + .mailbox-conversation-msg { + padding: 8px 10px; + } + + .mailbox-agent-select { + max-width: 100%; + } + + .mailbox-compose-fab { + bottom: 16px; + right: 16px; + } + + .mailbox-empty { + padding: 32px 12px; + } +} + /* ── Message Composer ──────────────────────────────────────────────── */ .message-composer { diff --git a/packages/dashboard/src/routes.test.ts b/packages/dashboard/src/routes.test.ts index 86b7564bc..0eb9226fd 100644 --- a/packages/dashboard/src/routes.test.ts +++ b/packages/dashboard/src/routes.test.ts @@ -3903,7 +3903,7 @@ describe("POST /github/issues/batch-import", () => { expect(res.body.results[0].error).toContain("rate limit"); expect(res.body.results[0].retryAfter).toBe(1); expect(fetchSpy.mock.calls.length).toBeGreaterThanOrEqual(4); - }, 15000); // Increase timeout for multiple retries + }, 30000); // Retry path can exceed 15s in CI/load-constrained environments it("processes issues sequentially (not parallel)", async () => { vi.useFakeTimers();