{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)}
>