diff --git a/src/app/smoke-tests/SmokeHarness.tsx b/src/app/smoke-tests/SmokeHarness.tsx
index e4753d7..6cda0ac 100644
--- a/src/app/smoke-tests/SmokeHarness.tsx
+++ b/src/app/smoke-tests/SmokeHarness.tsx
@@ -148,6 +148,7 @@ export default function SmokeHarness({ panel }: { panel: SmokePanel }) {
archiveMailboxId="mailbox-archive"
trashMailboxId="mailbox-trash"
threadHrefPrefix="/smoke-tests/thread"
+ autoSyncIntervalMs={0}
/>
)}
diff --git a/src/components/EmailListPanel.tsx b/src/components/EmailListPanel.tsx
index 84266de..0808031 100644
--- a/src/components/EmailListPanel.tsx
+++ b/src/components/EmailListPanel.tsx
@@ -1044,6 +1044,58 @@ export default function EmailListPanel({
const actionBtnCls =
"flex h-10 w-10 items-center justify-center rounded-md hover:bg-blue-100 dark:hover:bg-blue-900/60 text-blue-600 dark:text-blue-400 transition-colors shrink-0";
+ const selectionActions = (
+ <>
+
+
+ {view === "spam" && (
+
+ )}
+
+
+ {view === "inbox" && (
+
+ )}
+ {view === "inbox" && archiveMailboxId && (
+
+ )}
+ {trashMailboxId && (
+
+ )}
+ >
+ );
+
// -------------------------------------------------------------------------
// Render
// -------------------------------------------------------------------------
@@ -1112,7 +1164,20 @@ export default function EmailListPanel({
{/* Search (inbox only) */}
{view === "inbox" && (
-
+
+ {selectionMode ? (
+
+ {selectionActions}
+
+ ) : (
+ <>
)}
+ >
+ )}
)}
@@ -1259,55 +1326,9 @@ export default function EmailListPanel({
)}
{/* Bulk action bar */}
- {(view === "inbox" || view === "spam") && selectionMode && (
+ {view === "spam" && selectionMode && (
-
-
- {view === "spam" && (
-
- )}
-
-
- {view === "inbox" && (
-
- )}
- {view === "inbox" && archiveMailboxId && (
-
- )}
- {trashMailboxId && (
-
- )}
+ {selectionActions}
)}
diff --git a/tests/smoke/mail.spec.ts b/tests/smoke/mail.spec.ts
index b7a7abb..ad95241 100644
--- a/tests/smoke/mail.spec.ts
+++ b/tests/smoke/mail.spec.ts
@@ -71,6 +71,37 @@ test("opens a conversation from a desktop click", async ({ page }) => {
).toBeVisible();
});
+test("replaces search with selection actions without shifting conversations", async ({
+ page,
+}) => {
+ await page.goto("/smoke-tests");
+
+ const conversation = page.locator(
+ 'a[href="/smoke-tests/thread/thread-maya"]',
+ );
+ const beforeTop = await conversation.evaluate(
+ (element) => element.getBoundingClientRect().top,
+ );
+
+ await page
+ .getByRole("button", { name: "Select conversation from GitHub" })
+ .click();
+
+ await expect(page.getByRole("searchbox", { name: "Search all mail" })).toHaveCount(0);
+ await expect(page.getByRole("button", { name: "Cancel selection" })).toBeVisible();
+ const selectedTop = await conversation.evaluate(
+ (element) => element.getBoundingClientRect().top,
+ );
+ expect(selectedTop).toBe(beforeTop);
+
+ await page.getByRole("button", { name: "Cancel selection" }).click();
+ await expect(page.getByRole("searchbox", { name: "Search all mail" })).toBeVisible();
+ const restoredTop = await conversation.evaluate(
+ (element) => element.getBoundingClientRect().top,
+ );
+ expect(restoredTop).toBe(beforeTop);
+});
+
test("fetches new mail in the background and pauses while offline", async ({
page,
}) => {