Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/code/src/renderer/components/GlobalEventHandlers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function GlobalEventHandlers({
(state) => state.navigateToTaskInput,
);
const navigateToTask = useNavigationStore((state) => state.navigateToTask);
const navigateToInbox = useNavigationStore((state) => state.navigateToInbox);
const navigateToFolderSettings = useNavigationStore(
(state) => state.navigateToFolderSettings,
);
Expand Down Expand Up @@ -165,6 +166,7 @@ export function GlobalEventHandlers({
useHotkeys(SHORTCUTS.TOGGLE_LEFT_SIDEBAR, toggleLeftSidebar, globalOptions);
useHotkeys(SHORTCUTS.TOGGLE_RIGHT_SIDEBAR, toggleRightSidebar, globalOptions);
useHotkeys(SHORTCUTS.SHORTCUTS_SHEET, onToggleShortcutsSheet, globalOptions);
useHotkeys(SHORTCUTS.INBOX, navigateToInbox, globalOptions);
useHotkeys(SHORTCUTS.PREV_TASK, handlePrevTask, globalOptions, [
handlePrevTask,
]);
Expand Down
7 changes: 7 additions & 0 deletions apps/code/src/renderer/constants/keyboard-shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const SHORTCUTS = {
COPY_PATH: "mod+shift+c",
TOGGLE_FOCUS: "mod+r",
PASTE_AS_FILE: "mod+shift+v",
INBOX: "mod+i",
BLUR: "escape",
SUBMIT_BLUR: "mod+enter",
} as const;
Expand Down Expand Up @@ -66,6 +67,12 @@ export const KEYBOARD_SHORTCUTS: KeyboardShortcut[] = [
description: "Show keyboard shortcuts",
category: "general",
},
{
id: "inbox",
keys: SHORTCUTS.INBOX,
description: "Open inbox",
category: "navigation",
},
{
id: "switch-task",
keys: "mod+0-9",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useAuthStateValue } from "@features/auth/hooks/authQueries";
import { InboxLiveRail } from "@features/inbox/components/InboxLiveRail";
import {
useInboxReportArtefacts,
useInboxReportSignals,
Expand Down Expand Up @@ -32,8 +33,10 @@ import {
CircleNotchIcon,
ClockIcon,
Cloud as CloudIcon,
CommandIcon,
GithubLogoIcon,
KanbanIcon,
KeyReturnIcon,
TicketIcon,
VideoIcon,
WarningIcon,
Expand Down Expand Up @@ -556,6 +559,68 @@ export function InboxSignalsTab() {
[reports, selectedReportId],
);

// ── Arrow-key navigation between reports ──────────────────────────────
const reportsRef = useRef(reports);
reportsRef.current = reports;
const selectedReportIdRef = useRef(selectedReportId);
selectedReportIdRef.current = selectedReportId;

const leftPaneRef = useRef<HTMLDivElement>(null);

// Auto-focus the list pane when the inbox mounts so arrow keys work immediately
useEffect(() => {
leftPaneRef.current?.focus();
}, []);

const navigateReport = useCallback((direction: 1 | -1) => {
const list = reportsRef.current;
if (list.length === 0) return;
const currentId = selectedReportIdRef.current;
const currentIndex = currentId
? list.findIndex((r) => r.id === currentId)
: -1;
const nextIndex =
currentIndex === -1
? 0
: Math.max(0, Math.min(list.length - 1, currentIndex + direction));
const nextId = list[nextIndex].id;
setSelectedReportId(nextId);
// Move focus back to the list container so the previously clicked card
// loses its focus outline
leftPaneRef.current?.focus();
leftPaneRef.current
?.querySelector(`[data-report-id="${nextId}"]`)
?.scrollIntoView({ block: "nearest" });
}, []);

const handleCreateTaskRef = useRef<() => void>(() => {});

const handleListKeyDown = useCallback(
(e: React.KeyboardEvent) => {
// Don't capture arrow keys when focus is inside interactive child UI
// like filter popovers, dropdowns, or search inputs
const target = e.target as HTMLElement;
if (
target.closest(
"[role='menu'], [role='listbox'], [role='dialog'], [data-radix-popper-content-wrapper], input, select, textarea",
)
)
return;

if (e.key === "ArrowDown") {
e.preventDefault();
navigateReport(1);
} else if (e.key === "ArrowUp") {
e.preventDefault();
navigateReport(-1);
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
handleCreateTaskRef.current();
}
},
[navigateReport],
);

const artefactsQuery = useInboxReportArtefacts(selectedReport?.id ?? "", {
enabled: !!selectedReport,
});
Expand Down Expand Up @@ -635,6 +700,7 @@ export function InboxSignalsTab() {
});
navigateToTaskInput();
};
handleCreateTaskRef.current = handleCreateTask;

const handleOpenCloudConfirm = useCallback(() => {
openCloudConfirm(repositories[0] ?? null);
Expand Down Expand Up @@ -759,7 +825,12 @@ export function InboxSignalsTab() {
disabled={!canActOnReport}
className="text-[12px]"
>
Pick up task
<CloudIcon size={12} />
{isRunningCloudTask ? "Running..." : "Run task"}
<span className="ml-1 inline-flex items-center gap-px text-gray-9">
<CommandIcon size={11} />
<KeyReturnIcon size={11} />
</span>
</Button>
{cloudModeEnabled && (
<Button
Expand Down Expand Up @@ -1100,7 +1171,14 @@ export function InboxSignalsTab() {
className="scroll-area-constrain-width"
style={{ height: "100%" }}
>
<Flex direction="column">
<Flex
ref={leftPaneRef}
direction="column"
tabIndex={-1}
onKeyDown={handleListKeyDown}
className="outline-none"
>
<InboxLiveRail active={inboxPollingActive} />
<SignalsToolbar
totalCount={totalCount}
filteredCount={reports.length}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export function ReportCard({
<motion.div
role="button"
tabIndex={0}
data-report-id={report.id}
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Tooltip } from "@components/ui/Tooltip";
import { EnvelopeSimple, Plus } from "@phosphor-icons/react";
import {
formatHotkey,
SHORTCUTS,
} from "@renderer/constants/keyboard-shortcuts";
import { SidebarItem } from "../SidebarItem";

interface NewTaskItemProps {
Expand Down Expand Up @@ -31,23 +36,33 @@ function formatSignalCount(count: number): string {

export function InboxItem({ isActive, onClick, signalCount }: InboxItemProps) {
return (
<SidebarItem
depth={0}
icon={<EnvelopeSimple size={16} weight={isActive ? "fill" : "regular"} />}
label="Inbox"
isActive={isActive}
onClick={onClick}
endContent={
signalCount && signalCount > 0 ? (
<span
className="inline-flex min-w-[16px] items-center justify-center rounded-full px-1 text-[11px] text-gray-11 leading-none"
style={{ height: "16px" }}
title={`${signalCount} ready reports`}
>
{formatSignalCount(signalCount)}
</span>
) : undefined
}
/>
<Tooltip
content="Open inbox"
shortcut={formatHotkey(SHORTCUTS.INBOX)}
side="right"
>
<div>
<SidebarItem
depth={0}
icon={
<EnvelopeSimple size={16} weight={isActive ? "fill" : "regular"} />
}
label="Inbox"
isActive={isActive}
onClick={onClick}
endContent={
signalCount && signalCount > 0 ? (
<span
className="inline-flex min-w-[16px] items-center justify-center rounded-full px-1 text-[11px] text-gray-11 leading-none"
style={{ height: "16px" }}
title={`${signalCount} ready reports`}
>
{formatSignalCount(signalCount)}
</span>
) : undefined
}
/>
</div>
</Tooltip>
);
}
Loading