From a2484f350852e8603b8df1af4fd31379bdb449cd Mon Sep 17 00:00:00 2001 From: lucletoffe <15689941+lucletoffe@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:30:19 +0200 Subject: [PATCH] fix(list): anchor the unread dot on the first line, not the row midpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dot added in #27 is positioned with `top: '50%'`, so it centres on the whole row. Rows are three lines tall (sender / subject / preview), so the dot lands a full line below the checkbox and the avatar it reads as a column with — the taller the row, the worse it looks. Anchor it on the first line instead: row padding + half an avatar, following the density (and the text line height when the avatar is hidden in extra-compact). The webmail's list carries the same `top-1/2` and the same drift. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FTRyrFCR2N9qkpKK37GgWb --- src/screens/EmailListScreen.tsx | 17 ++++++++++++++--- src/screens/UnifiedInboxScreen.tsx | 7 ++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/screens/EmailListScreen.tsx b/src/screens/EmailListScreen.tsx index 3c95066d..68de068d 100644 --- a/src/screens/EmailListScreen.tsx +++ b/src/screens/EmailListScreen.tsx @@ -46,6 +46,9 @@ function isPinned(email: Email): boolean { return !!email.keywords?.$important; } +// Height of the sender line when the avatar is hidden (extra-compact density), +// used to anchor the unread dot on that first line. +const UNREAD_DOT_TEXT_LINE = 20; const EmailRow = React.memo(function EmailRow({ item, @@ -92,7 +95,14 @@ const EmailRow = React.memo(function EmailRow({ onLongPress={handleLongPress} delayLongPress={300} > - {unread && } + {unread && ( + + )} {selectionMode && ( {selected ? ( @@ -1369,11 +1379,12 @@ function makeStyles(c: ThemePalette) { // Mirrors the webmail's unread indicator: an 8px filled circle at the row's // start edge, vertically centered (email-list-item.tsx, fill-unread). The // weight/color change alone is too subtle on some device fonts (#27). + // `top` is set per row: the dot is anchored on the row's *first* line (top + // padding + half an avatar), not on the row's midpoint, which would drop it + // a full line below the checkbox and avatar it reads as a column with. unreadDot: { position: 'absolute', left: 4, - top: '50%', - marginTop: -4, width: 8, height: 8, borderRadius: 4, diff --git a/src/screens/UnifiedInboxScreen.tsx b/src/screens/UnifiedInboxScreen.tsx index b5796730..a6081c37 100644 --- a/src/screens/UnifiedInboxScreen.tsx +++ b/src/screens/UnifiedInboxScreen.tsx @@ -221,12 +221,13 @@ function makeStyles(c: ThemePalette) { }, rowPressed: { backgroundColor: c.surfaceHover }, // Same unread indicator as the mailbox list (and the webmail): an 8px - // filled circle in the start gutter, vertically centered (#27). + // filled circle in the start gutter (#27). Anchored on the row's *first* + // line — top padding + half an avatar — because centring it on the row + // drops it a full line below the avatar it reads as a column with. unreadDot: { position: 'absolute', left: 4, - top: '50%', - marginTop: -4, + top: spacing.md + componentSizes.avatarMd / 2 - 4, width: 8, height: 8, borderRadius: 4,