diff --git a/ui/src/components/IssueRow.test.tsx b/ui/src/components/IssueRow.test.tsx index d087885a2e..5f85bba4f8 100644 --- a/ui/src/components/IssueRow.test.tsx +++ b/ui/src/components/IssueRow.test.tsx @@ -218,7 +218,7 @@ describe("IssueRow", () => { }); }); - it("puts the unread dot in the reserved far-left slot on desktop and in flow on mobile", () => { + it("puts the unread dot in the reserved desktop slot and overlays it on mobile", () => { const root = createRoot(container); act(() => { root.render(); @@ -230,12 +230,16 @@ describe("IssueRow", () => { expect(slot).not.toBeNull(); expect(slot?.querySelector('button[aria-label="Mark as read"]')).not.toBeNull(); - // Mobile: a separate in-flow, order-first dot (mobile has no reserved slot). + // Mobile: all inbox rows reserve a gutter and the dot stays fully inside + // it, so the control cannot be clipped or indent the status/title. const mobileDot = container .querySelector('button[aria-label="Mark as read"].sm\\:hidden, span.sm\\:hidden button[aria-label="Mark as read"]') ?.closest("span.sm\\:hidden"); expect(mobileDot).not.toBeNull(); - expect(mobileDot?.className).toContain("order-first"); + expect(mobileDot?.className).toContain("absolute"); + expect(mobileDot?.className).toContain("left-0"); + expect(mobileDot?.className).not.toContain("order-first"); + expect(container.firstElementChild?.className).toContain("pl-4"); act(() => { root.unmount(); diff --git a/ui/src/components/IssueRow.tsx b/ui/src/components/IssueRow.tsx index a175417c43..bcede8f1dd 100644 --- a/ui/src/components/IssueRow.tsx +++ b/ui/src/components/IssueRow.tsx @@ -168,7 +168,8 @@ export function IssueRow({ // No color transition on the row band: hover/selection must snap // instantly. A fade (transition-colors) leaves a trail of fading bands // when scrubbing the mouse fast across the list. - "group relative flex items-start gap-2 rounded-lg py-2.5 pl-2 pr-3 text-sm no-underline text-inherit sm:items-center sm:py-2 sm:pl-1", + "group relative flex items-start gap-2 rounded-lg py-2.5 pr-3 text-sm no-underline text-inherit sm:items-center sm:py-2 sm:pl-1", + showUnreadSlot ? "pl-4" : "pl-2", "[&_button]:relative [&_button]:z-10", // Divider + hover/selected/checklist wash live on the ROOT row band so // the tint paints BEHIND the content and `last:border-b-0` matches the @@ -323,10 +324,10 @@ export function IssueRow({ ) : null} {showUnreadDot ? ( - // Mobile keeps the dot in flow as the leading item (mobile has no - // reserved desktop dot gutter). Desktop renders the dot in the reserved - // leading slot above instead, so this is mobile-only. - + // Inbox rows reserve a mobile gutter on both read and unread rows. The + // full control stays inside overflow-clipping row containers while its + // absolute position avoids shifting or covering the leading control. + {unreadDotButton} ) : null}