diff --git a/components/email/thread-email-item.tsx b/components/email/thread-email-item.tsx
index a2859e666..24c97d7e7 100644
--- a/components/email/thread-email-item.tsx
+++ b/components/email/thread-email-item.tsx
@@ -137,9 +137,14 @@ export function ThreadEmailItem({
)}
- {/* Unread indicator */}
+ {/* Unread indicator — anchored on the first line (top padding + half a
+ small avatar), not on the row's midpoint, so it stays in line with
+ the checkbox and avatar when the row wraps to a second line. */}
{isUnread && (
-
+
)}
diff --git a/components/email/thread-list-item.tsx b/components/email/thread-list-item.tsx
index b54733418..6a0f3ed4c 100644
--- a/components/email/thread-list-item.tsx
+++ b/components/email/thread-list-item.tsx
@@ -20,6 +20,27 @@ import { ThreadEmailItem } from "./thread-email-item";
import { EmailHoverActions } from "./email-hover-actions";
import { useTranslations } from "next-intl";
+/**
+ * Unread bullet in the row gutter.
+ *
+ * It is absolutely positioned so it never widens the row, which means it has
+ * to be told where the row's *first* line sits: centring it on the row (the
+ * old `top-1/2`) drifts down as soon as the row grows a second and third line,
+ * leaving it visibly out of line with the checkbox and the avatar it reads as
+ * a column with. Anchor = top padding + half an avatar.
+ */
+function UnreadDot({ density, compactAvatar }: { density: string; compactAvatar: boolean }) {
+ const halfFirstLine = density === 'extra-compact' ? '0.625rem' : compactAvatar ? '1rem' : '1.25rem';
+ return (
+
+
+
+ );
+}
+
/**
* Small chip showing the originating folder of a message, rendered in the
* aggregate "All …" views (All Mail / unified / cross-account) where rows come
@@ -244,9 +265,7 @@ const SingleEmailItem = React.forwardRef
(
)}
{isUnread && (
-
-
-
+
)}
{density !== 'extra-compact' && (
@@ -683,9 +702,7 @@ export const ThreadListItem = React.forwardRef
-
-
+
)}
{density !== 'extra-compact' && (