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}