diff --git a/src/app/smoke-tests/thread/[threadId]/page.tsx b/src/app/smoke-tests/thread/[threadId]/page.tsx
index 5211d54..de7be1e 100644
--- a/src/app/smoke-tests/thread/[threadId]/page.tsx
+++ b/src/app/smoke-tests/thread/[threadId]/page.tsx
@@ -1,4 +1,5 @@
import { notFound } from "next/navigation";
+import LiveUnreadCountBadge from "@/components/LiveUnreadCountBadge";
interface Props {
params: Promise<{ threadId: string }>;
@@ -12,10 +13,17 @@ export default async function SmokeThreadPage({ params }: Props) {
const { threadId } = await params;
return (
-
+
Opened conversation {threadId}
+
+ Shared unread count
+
+
);
}
diff --git a/src/components/BrowserTabIndicator.tsx b/src/components/BrowserTabIndicator.tsx
index b8e372e..483d1c5 100644
--- a/src/components/BrowserTabIndicator.tsx
+++ b/src/components/BrowserTabIndicator.tsx
@@ -1,5 +1,6 @@
"use client";
+import { usePathname } from "next/navigation";
import { useEffect, useRef } from "react";
import { useUnreadCount } from "@/components/UnreadCountProvider";
import {
@@ -12,45 +13,49 @@ import {
const MANAGED_FAVICON_SELECTOR = 'link[data-mail-unread-favicon="true"]';
export default function BrowserTabIndicator() {
+ const pathname = usePathname();
const unreadCount = useUnreadCount();
const faviconRef = useRef
(null);
useEffect(() => {
- let favicon = document.head.querySelector(
- MANAGED_FAVICON_SELECTOR,
- );
- const createdFavicon = !favicon;
-
- if (!favicon) {
- favicon = document.createElement("link");
- favicon.rel = "icon";
- favicon.type = "image/svg+xml";
- favicon.sizes = "any";
- favicon.dataset.mailUnreadFavicon = "true";
- document.head.append(favicon);
- }
-
- faviconRef.current = favicon;
-
return () => {
faviconRef.current = null;
- if (createdFavicon) favicon.remove();
+ document.head
+ .querySelectorAll(MANAGED_FAVICON_SELECTOR)
+ .forEach((favicon) => favicon.remove());
};
}, []);
useEffect(() => {
const unreadLabel = formatBrowserTabUnreadCount(unreadCount);
- const favicon = faviconRef.current;
+ const connectedFavicon = faviconRef.current?.isConnected
+ ? faviconRef.current
+ : document.head.querySelector(
+ MANAGED_FAVICON_SELECTOR,
+ );
+ const favicon = connectedFavicon ?? document.createElement("link");
+
+ if (!connectedFavicon) {
+ favicon.setAttribute("rel", "icon");
+ favicon.setAttribute("type", "image/svg+xml");
+ favicon.setAttribute("sizes", "any");
+ favicon.setAttribute("data-mail-unread-favicon", "true");
+ }
document.title = getBrowserTabTitle(unreadCount);
-
- if (favicon) {
- favicon.href = unreadLabel
+ favicon.setAttribute(
+ "href",
+ unreadLabel
? getUnreadFaviconDataUrl(unreadCount)
- : DEFAULT_FAVICON_HREF;
- favicon.dataset.unreadCount = unreadLabel;
- }
- }, [unreadCount]);
+ : DEFAULT_FAVICON_HREF,
+ );
+ favicon.setAttribute("data-unread-count", unreadLabel);
+ faviconRef.current = favicon;
+
+ // Next may rewrite static metadata while navigating. Keep this managed
+ // icon last so the browser continues to prefer the live unread state.
+ document.head.append(favicon);
+ }, [pathname, unreadCount]);
return null;
}
diff --git a/tests/smoke/mail.spec.ts b/tests/smoke/mail.spec.ts
index 3c550f6..1dadcea 100644
--- a/tests/smoke/mail.spec.ts
+++ b/tests/smoke/mail.spec.ts
@@ -41,6 +41,23 @@ test("keeps the browser tab in sync with live unread mail", async ({
await expect(page).toHaveTitle("Mail");
await expect(favicon).toHaveAttribute("href", /\/icon\.svg$/);
await expect(favicon).toHaveAttribute("data-unread-count", "");
+
+ await page.getByRole("button", { name: "Set 99 unread" }).click();
+ await page.getByRole("link", { name: "Inbox" }).click();
+ await page
+ .locator('a[href="/smoke-tests/thread/thread-release"]')
+ .click();
+
+ await expect(page).toHaveURL("/smoke-tests/thread/thread-release");
+ await expect(page.getByTestId("shared-unread-count")).toContainText("99");
+ await expect(page).toHaveTitle("(99+) Mail");
+ await expect(favicon).toHaveAttribute("data-unread-count", "99+");
+ expect(
+ await page.evaluate(() => {
+ const icons = [...document.querySelectorAll('link[rel~="icon"]')];
+ return icons.at(-1)?.getAttribute("data-mail-unread-favicon");
+ }),
+ ).toBe("true");
});
test("loads sender artwork and falls back cleanly when it is unavailable", async ({