From 7d81e4cb2a86c591cd7aa129246b8ebec6dd7285 Mon Sep 17 00:00:00 2001 From: dotta Date: Sat, 28 Mar 2026 16:24:23 -0500 Subject: [PATCH] Fix mine inbox keyboard selection Co-Authored-By: Paperclip --- ui/src/components/SwipeToArchive.test.tsx | 21 +++++++++++++++++++++ ui/src/components/SwipeToArchive.tsx | 5 +++++ ui/src/lib/inbox.test.ts | 8 ++++++++ ui/src/lib/inbox.ts | 10 ++++++++++ ui/src/pages/Inbox.tsx | 21 +++++++++------------ 5 files changed, 53 insertions(+), 12 deletions(-) diff --git a/ui/src/components/SwipeToArchive.test.tsx b/ui/src/components/SwipeToArchive.test.tsx index 8995793c..7adb2d92 100644 --- a/ui/src/components/SwipeToArchive.test.tsx +++ b/ui/src/components/SwipeToArchive.test.tsx @@ -122,4 +122,25 @@ describe("SwipeToArchive", () => { root.unmount(); }); }); + + it("renders the selected inbox treatment on the swipe surface", () => { + const root = createRoot(container); + + act(() => { + root.render( + {}} selected> + + , + ); + }); + + const surface = container.querySelector("[data-inbox-row-surface]") as HTMLDivElement | null; + expect(surface).not.toBeNull(); + expect(surface?.style.backgroundColor).toBe("hsl(var(--primary) / 0.06)"); + expect(surface?.style.boxShadow).toBe("inset 3px 0 0 hsl(var(--primary))"); + + act(() => { + root.unmount(); + }); + }); }); diff --git a/ui/src/components/SwipeToArchive.tsx b/ui/src/components/SwipeToArchive.tsx index 9a07ca96..be9ca964 100644 --- a/ui/src/components/SwipeToArchive.tsx +++ b/ui/src/components/SwipeToArchive.tsx @@ -6,6 +6,7 @@ interface SwipeToArchiveProps { children: ReactNode; onArchive: () => void; disabled?: boolean; + selected?: boolean; className?: string; } @@ -17,6 +18,7 @@ export function SwipeToArchive({ children, onArchive, disabled = false, + selected = false, className, }: SwipeToArchiveProps) { const containerRef = useRef(null); @@ -148,10 +150,13 @@ export function SwipeToArchive({
{children} diff --git a/ui/src/lib/inbox.test.ts b/ui/src/lib/inbox.test.ts index 317249c9..c7851afb 100644 --- a/ui/src/lib/inbox.test.ts +++ b/ui/src/lib/inbox.test.ts @@ -11,6 +11,7 @@ import { isMineInboxTab, loadLastInboxTab, RECENT_ISSUES_LIMIT, + resolveInboxSelectionIndex, saveLastInboxTab, shouldShowInboxSection, } from "./inbox"; @@ -408,4 +409,11 @@ describe("inbox helpers", () => { expect(isMineInboxTab("unread")).toBe(false); expect(isMineInboxTab("all")).toBe(false); }); + + it("anchors Mine selection to the first available inbox row", () => { + expect(resolveInboxSelectionIndex(-1, 3, true)).toBe(0); + expect(resolveInboxSelectionIndex(-1, 3, false)).toBe(-1); + expect(resolveInboxSelectionIndex(5, 3, true)).toBe(2); + expect(resolveInboxSelectionIndex(1, 0, true)).toBe(-1); + }); }); diff --git a/ui/src/lib/inbox.ts b/ui/src/lib/inbox.ts index 997993c7..9031ccc0 100644 --- a/ui/src/lib/inbox.ts +++ b/ui/src/lib/inbox.ts @@ -102,6 +102,16 @@ export function isMineInboxTab(tab: InboxTab): boolean { return tab === "mine"; } +export function resolveInboxSelectionIndex( + previousIndex: number, + itemCount: number, + canSelectItems: boolean, +): number { + if (itemCount === 0) return -1; + if (previousIndex < 0) return canSelectItems ? 0 : -1; + return Math.min(previousIndex, itemCount - 1); +} + export function getLatestFailedRunsByAgent(runs: HeartbeatRun[]): HeartbeatRun[] { const sorted = [...runs].sort( (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), diff --git a/ui/src/pages/Inbox.tsx b/ui/src/pages/Inbox.tsx index 24c42dad..7ed6e97d 100644 --- a/ui/src/pages/Inbox.tsx +++ b/ui/src/pages/Inbox.tsx @@ -50,6 +50,7 @@ import { getLatestFailedRunsByAgent, getRecentTouchedIssues, isMineInboxTab, + resolveInboxSelectionIndex, InboxApprovalFilter, saveLastInboxTab, shouldShowInboxSection, @@ -928,12 +929,10 @@ export function Inbox() { return `join:${item.joinRequest.id}`; }, []); - // Reset selection when the list changes + // Keep Mine anchored to a real row so keyboard navigation always lands on an item. useEffect(() => { - setSelectedIndex((prev) => - prev >= workItemsToRender.length ? workItemsToRender.length - 1 : prev, - ); - }, [workItemsToRender.length]); + setSelectedIndex((prev) => resolveInboxSelectionIndex(prev, workItemsToRender.length, canArchiveFromTab)); + }, [canArchiveFromTab, workItemsToRender.length]); // Use refs for keyboard handler to avoid stale closures const kbStateRef = useRef({ @@ -1233,15 +1232,9 @@ export function Inbox() {
*]:bg-transparent", - )} + className="relative" onClick={() => setSelectedIndex(index)} > - {isSelected && ( -
- )} {child}
); @@ -1289,6 +1282,7 @@ export function Inbox() { elements.push(wrapItem(approvalKey, isSelected, canArchiveFromTab ? ( handleArchiveNonIssue(approvalKey)} > @@ -1325,6 +1319,7 @@ export function Inbox() { elements.push(wrapItem(runKey, isSelected, canArchiveFromTab ? ( handleArchiveNonIssue(runKey)} > @@ -1358,6 +1353,7 @@ export function Inbox() { elements.push(wrapItem(joinKey, isSelected, canArchiveFromTab ? ( handleArchiveNonIssue(joinKey)} > @@ -1428,6 +1424,7 @@ export function Inbox() { elements.push(wrapItem(`issue:${issue.id}`, isSelected, canArchiveFromTab ? ( archiveIssueMutation.mutate(issue.id)} >