feat(ui): add review keyboard shortcuts

This commit is contained in:
Saravana Achu Mac 2026-05-06 13:15:37 -07:00
parent 116c0c982b
commit d63fdd1def

View File

@ -18,6 +18,15 @@ import { approveReviewItem, batchReviewItems, listAgentTimeline, listApprovalQue
import { toast } from "@/lib/toast"; import { toast } from "@/lib/toast";
import type { AgentTimelineItem, ApprovalQueueItem } from "@/lib/types"; import type { AgentTimelineItem, ApprovalQueueItem } from "@/lib/types";
function isKeyboardShortcutSafe(target: EventTarget | null) {
if (!(target instanceof HTMLElement)) {
return true;
}
const tagName = target.tagName.toLowerCase();
return tagName !== "input" && tagName !== "textarea" && tagName !== "select" && !target.isContentEditable;
}
export default function ReviewsPage() { export default function ReviewsPage() {
const [approvalQueue, setApprovalQueue] = useState<ApprovalQueueItem[]>([]); const [approvalQueue, setApprovalQueue] = useState<ApprovalQueueItem[]>([]);
const [timeline, setTimeline] = useState<AgentTimelineItem[]>([]); const [timeline, setTimeline] = useState<AgentTimelineItem[]>([]);
@ -172,6 +181,88 @@ export default function ReviewsPage() {
} }
} }
useEffect(() => {
function selectRelativeQueueItem(offset: number) {
if (approvalQueue.length === 0) {
return;
}
const currentIndex = Math.max(
0,
approvalQueue.findIndex((item) => item.id === featuredProposal?.id),
);
const nextIndex = Math.min(Math.max(currentIndex + offset, 0), approvalQueue.length - 1);
setSelectedApprovalId(approvalQueue[nextIndex]?.id ?? null);
}
function handleKeyDown(event: KeyboardEvent) {
if (
event.defaultPrevented ||
event.metaKey ||
event.ctrlKey ||
event.altKey ||
!isKeyboardShortcutSafe(event.target)
) {
return;
}
const key = event.key.toLowerCase();
if (key === "arrowdown" || key === "j") {
event.preventDefault();
selectRelativeQueueItem(1);
return;
}
if (key === "arrowup" || key === "k") {
event.preventDefault();
selectRelativeQueueItem(-1);
return;
}
if (key === "x") {
event.preventDefault();
selectAllForBatch();
return;
}
if ((key === "escape" || key === "c") && batchMode) {
event.preventDefault();
clearBatch();
return;
}
if (key === "a") {
event.preventDefault();
if (batchMode) {
void handleBatchDecision("approved");
} else {
void handleDecision("approved");
}
return;
}
if (key === "r") {
event.preventDefault();
if (batchMode) {
void handleBatchDecision("rejected");
} else {
void handleDecision("rejected");
}
}
}
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [
approvalQueue,
batchMode,
clearBatch,
featuredProposal?.id,
handleBatchDecision,
handleDecision,
selectAllForBatch,
]);
return ( return (
<AppShell <AppShell
title="Agent review" title="Agent review"