Skip to content

Commit 29a5f56

Browse files
committed
signals inbox keyboard name
1 parent f8d7da8 commit 29a5f56

5 files changed

Lines changed: 113 additions & 21 deletions

File tree

apps/code/src/renderer/components/GlobalEventHandlers.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export function GlobalEventHandlers({
3535
(state) => state.navigateToTaskInput,
3636
);
3737
const navigateToTask = useNavigationStore((state) => state.navigateToTask);
38+
const navigateToInbox = useNavigationStore((state) => state.navigateToInbox);
3839
const navigateToFolderSettings = useNavigationStore(
3940
(state) => state.navigateToFolderSettings,
4041
);
@@ -165,6 +166,7 @@ export function GlobalEventHandlers({
165166
useHotkeys(SHORTCUTS.TOGGLE_LEFT_SIDEBAR, toggleLeftSidebar, globalOptions);
166167
useHotkeys(SHORTCUTS.TOGGLE_RIGHT_SIDEBAR, toggleRightSidebar, globalOptions);
167168
useHotkeys(SHORTCUTS.SHORTCUTS_SHEET, onToggleShortcutsSheet, globalOptions);
169+
useHotkeys(SHORTCUTS.INBOX, navigateToInbox, globalOptions);
168170
useHotkeys(SHORTCUTS.PREV_TASK, handlePrevTask, globalOptions, [
169171
handlePrevTask,
170172
]);

apps/code/src/renderer/constants/keyboard-shortcuts.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const SHORTCUTS = {
1818
COPY_PATH: "mod+shift+c",
1919
TOGGLE_FOCUS: "mod+r",
2020
PASTE_AS_FILE: "mod+shift+v",
21+
INBOX: "mod+shift+i",
2122
BLUR: "escape",
2223
SUBMIT_BLUR: "mod+enter",
2324
} as const;
@@ -66,6 +67,12 @@ export const KEYBOARD_SHORTCUTS: KeyboardShortcut[] = [
6667
description: "Show keyboard shortcuts",
6768
category: "general",
6869
},
70+
{
71+
id: "inbox",
72+
keys: SHORTCUTS.INBOX,
73+
description: "Open inbox",
74+
category: "navigation",
75+
},
6976
{
7077
id: "switch-task",
7178
keys: "mod+0-9",

apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ import {
2828
CircleNotchIcon,
2929
ClockIcon,
3030
Cloud as CloudIcon,
31+
CommandIcon,
3132
GithubLogoIcon,
33+
KanbanIcon,
34+
KeyReturnIcon,
35+
TicketIcon,
36+
VideoIcon,
3237
WarningIcon,
3338
XIcon,
3439
} from "@phosphor-icons/react";
@@ -377,6 +382,58 @@ export function InboxSignalsTab() {
377382
[reports, selectedReportId],
378383
);
379384

385+
// ── Arrow-key navigation between reports ──────────────────────────────
386+
const reportsRef = useRef(reports);
387+
reportsRef.current = reports;
388+
const selectedReportIdRef = useRef(selectedReportId);
389+
selectedReportIdRef.current = selectedReportId;
390+
391+
const leftPaneRef = useRef<HTMLDivElement>(null);
392+
393+
// Auto-focus the list pane when the inbox mounts so arrow keys work immediately
394+
useEffect(() => {
395+
leftPaneRef.current?.focus();
396+
}, []);
397+
398+
const navigateReport = useCallback((direction: 1 | -1) => {
399+
const list = reportsRef.current;
400+
if (list.length === 0) return;
401+
const currentId = selectedReportIdRef.current;
402+
const currentIndex = currentId
403+
? list.findIndex((r) => r.id === currentId)
404+
: -1;
405+
const nextIndex =
406+
currentIndex === -1
407+
? 0
408+
: Math.max(0, Math.min(list.length - 1, currentIndex + direction));
409+
const nextId = list[nextIndex].id;
410+
setSelectedReportId(nextId);
411+
// Move focus back to the list container so the previously clicked card
412+
// loses its focus outline
413+
leftPaneRef.current?.focus();
414+
leftPaneRef.current
415+
?.querySelector(`[data-report-id="${nextId}"]`)
416+
?.scrollIntoView({ block: "nearest" });
417+
}, []);
418+
419+
const handleCreateTaskRef = useRef<() => void>(() => {});
420+
421+
const handleListKeyDown = useCallback(
422+
(e: React.KeyboardEvent) => {
423+
if (e.key === "ArrowDown") {
424+
e.preventDefault();
425+
navigateReport(1);
426+
} else if (e.key === "ArrowUp") {
427+
e.preventDefault();
428+
navigateReport(-1);
429+
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
430+
e.preventDefault();
431+
handleCreateTaskRef.current();
432+
}
433+
},
434+
[navigateReport],
435+
);
436+
380437
const artefactsQuery = useInboxReportArtefacts(selectedReport?.id ?? "", {
381438
enabled: !!selectedReport,
382439
});
@@ -448,6 +505,7 @@ export function InboxSignalsTab() {
448505
});
449506
navigateToTaskInput();
450507
};
508+
handleCreateTaskRef.current = handleCreateTask;
451509

452510
const handleOpenCloudConfirm = useCallback(() => {
453511
openCloudConfirm(repositories[0] ?? null);
@@ -582,8 +640,11 @@ export function InboxSignalsTab() {
582640
}
583641
className="text-[12px]"
584642
>
585-
<CloudIcon size={12} />
586-
{isRunningCloudTask ? "Running..." : "Run cloud"}
643+
Pick up task
644+
<span className="ml-1 inline-flex items-center gap-px text-gray-9">
645+
<CommandIcon size={11} />
646+
<KeyReturnIcon size={11} />
647+
</span>
587648
</Button>
588649
)}
589650
</Flex>
@@ -882,7 +943,13 @@ export function InboxSignalsTab() {
882943
className="scroll-area-constrain-width"
883944
style={{ height: "100%" }}
884945
>
885-
<Flex direction="column">
946+
<Flex
947+
ref={leftPaneRef}
948+
direction="column"
949+
tabIndex={-1}
950+
onKeyDown={handleListKeyDown}
951+
className="outline-none"
952+
>
886953
<InboxLiveRail active={inboxPollingActive} />
887954
<SignalsToolbar
888955
totalCount={totalCount}

apps/code/src/renderer/features/inbox/components/ReportCard.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export function ReportCard({
5959
<motion.div
6060
role="button"
6161
tabIndex={0}
62+
data-report-id={report.id}
6263
initial={{ opacity: 0, y: 6 }}
6364
animate={{ opacity: 1, y: 0 }}
6465
transition={{

apps/code/src/renderer/features/sidebar/components/items/HomeItem.tsx

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
import { Tooltip } from "@components/ui/Tooltip";
12
import { EnvelopeSimple, Plus } from "@phosphor-icons/react";
3+
import {
4+
formatHotkey,
5+
SHORTCUTS,
6+
} from "@renderer/constants/keyboard-shortcuts";
27
import { SidebarItem } from "../SidebarItem";
38

49
interface NewTaskItemProps {
@@ -31,23 +36,33 @@ function formatSignalCount(count: number): string {
3136

3237
export function InboxItem({ isActive, onClick, signalCount }: InboxItemProps) {
3338
return (
34-
<SidebarItem
35-
depth={0}
36-
icon={<EnvelopeSimple size={16} weight={isActive ? "fill" : "regular"} />}
37-
label="Inbox"
38-
isActive={isActive}
39-
onClick={onClick}
40-
endContent={
41-
signalCount && signalCount > 0 ? (
42-
<span
43-
className="inline-flex min-w-[16px] items-center justify-center rounded-full px-1 text-[11px] text-gray-11 leading-none"
44-
style={{ height: "16px" }}
45-
title={`${signalCount} ready reports`}
46-
>
47-
{formatSignalCount(signalCount)}
48-
</span>
49-
) : undefined
50-
}
51-
/>
39+
<Tooltip
40+
content="Open inbox"
41+
shortcut={formatHotkey(SHORTCUTS.INBOX)}
42+
side="right"
43+
>
44+
<div>
45+
<SidebarItem
46+
depth={0}
47+
icon={
48+
<EnvelopeSimple size={16} weight={isActive ? "fill" : "regular"} />
49+
}
50+
label="Inbox"
51+
isActive={isActive}
52+
onClick={onClick}
53+
endContent={
54+
signalCount && signalCount > 0 ? (
55+
<span
56+
className="inline-flex min-w-[16px] items-center justify-center rounded-full px-1 text-[11px] text-gray-11 leading-none"
57+
style={{ height: "16px" }}
58+
title={`${signalCount} ready reports`}
59+
>
60+
{formatSignalCount(signalCount)}
61+
</span>
62+
) : undefined
63+
}
64+
/>
65+
</div>
66+
</Tooltip>
5267
);
5368
}

0 commit comments

Comments
 (0)