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' && (