From 7d868caae168d3a06cd78d15c86719971ddce5d4 Mon Sep 17 00:00:00 2001 From: lucletoffe <15689941+lucletoffe@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:26:49 +0200 Subject: [PATCH] fix(list): align the unread dot with the first line, not the row midpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dot is absolutely positioned with `top-1/2`, so it centres on the whole row. In the default multi-line layout (sender / subject / preview) that puts it a full line below the checkbox and the avatar it reads as a column with — visibly out of line, and worse the taller the row. Anchor it on the first line instead: top padding + half an avatar, per density. Same fix for the messages of an expanded thread. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FTRyrFCR2N9qkpKK37GgWb --- components/email/thread-email-item.tsx | 9 ++++++-- components/email/thread-list-item.tsx | 29 ++++++++++++++++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) 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' && (