Skip to content

fix(list): align the unread dot with the first line, not the row midpoint - #715

Open
lucletoffe wants to merge 1 commit into
bulwarkmail:mainfrom
lucletoffe:fix/unread-dot-alignment
Open

fix(list): align the unread dot with the first line, not the row midpoint#715
lucletoffe wants to merge 1 commit into
bulwarkmail:mainfrom
lucletoffe:fix/unread-dot-alignment

Conversation

@lucletoffe

Copy link
Copy Markdown

The problem

The unread dot is positioned with top-1/2 -translate-y-1/2, so it centres on the whole
row
. At the default density a row is three lines tall (sender / subject / preview) while the
checkbox and the avatar sit at the top — the dot therefore lands roughly a line below them,
level with the subject or the preview. It reads as a column with those two, so the drift is
what the eye catches first, and it gets worse as rows grow (larger font, wrapped subject,
tag lozenges).

before (main) after (this PR)

Screenshots taken with DEMO_MODE=true npm run dev, dark theme, default density.

The fix

Anchor the dot on the row's first line instead of its midpoint: var(--density-item-py)
plus half an avatar, following the density (extra-compact has no avatar, so it uses the
sender line height). Extracted into a small UnreadDot component since the markup was
duplicated for the single-email and thread rows.

  • thread-list-item.tsx — both occurrences.
  • thread-email-item.tsx — same top-1/2 on the messages of an expanded thread, same fix
    (its avatar is sm).

No layout change: the dot stays absolutely positioned, so it still costs no width.

Checks

npm run typecheck clean · npm run lint clean (8 pre-existing warnings on main, none
in the touched files) · npx vitest run → 146 files, 2331 tests passed.

Note for the mobile app

bulwarkmail/native grew the same indicator in #27 with the same top: '50%', and the same
drift — bulwarkmail/native#36 fixes it there.

…oint

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTRyrFCR2N9qkpKK37GgWb

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts the unread indicator (“dot”) positioning in the email/thread list UI so it aligns with the first line of content (checkbox/avatar row) rather than the vertical midpoint of multi-line rows, preventing visible drift as rows grow in height.

Changes:

  • Repositions the unread dot in list rows by anchoring it to var(--density-item-py) plus half of the first-line avatar/line-height.
  • Extracts the duplicated unread-dot markup in thread-list-item.tsx into a small UnreadDot helper component.
  • Applies the same “anchor to first line” approach to unread dots for messages in expanded threads (thread-email-item.tsx).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
components/email/thread-list-item.tsx Introduces UnreadDot and replaces midpoint-centered unread-dot positioning in single/thread rows.
components/email/thread-email-item.tsx Updates unread-dot positioning in expanded thread email rows to anchor on the first line.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to +33
function UnreadDot({ density, compactAvatar }: { density: string; compactAvatar: boolean }) {
const halfFirstLine = density === 'extra-compact' ? '0.625rem' : compactAvatar ? '1rem' : '1.25rem';
@Almost-Senseless-Coder

Copy link
Copy Markdown

While the unread dot is getting reworked, I'd appreciate if you could make it appear to screen readers, e.g., by using aria-label="Unread" (of course using the existing i18n string). Ensure aria-hidden="true" isn't present.

If you'd prefer that, I could create an issue for this and attempt submitting a PR for the newly introduced component once this PR is merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants