diff --git a/apps/web/components/task/recent-task-switcher-hooks.ts b/apps/web/components/task/recent-task-switcher-hooks.ts index a0e22c35ce..c499e28e88 100644 --- a/apps/web/components/task/recent-task-switcher-hooks.ts +++ b/apps/web/components/task/recent-task-switcher-hooks.ts @@ -27,6 +27,7 @@ import { import { buildRecentTaskDisplayItems, buildRecentTaskEntry, + getInitialReverseSelectionIndex, getInitialSelectionIndex, getNextSelectionIndex, getPreviousSelectionIndex, @@ -39,6 +40,9 @@ import { isCycleShortcutEvent, } from "./recent-task-switcher-keys"; +/** Direction the switcher cycles through recent tasks. */ +type CycleDirection = "forward" | "backward"; + export type RecentTaskSwitcherController = { open: boolean; setOpen: (open: boolean) => void; @@ -46,6 +50,7 @@ export type RecentTaskSwitcherController = { selectedIndex: number; setSelectedIndex: (index: number) => void; shortcutLabel: string; + reverseShortcutLabel: string; selectItem: (item: RecentTaskDisplayItem | undefined) => void; handleKeyDown: (event: ReactKeyboardEvent) => void; }; @@ -58,14 +63,19 @@ type SwitcherRefs = { itemsRef: MutableRefObject; activeTaskIdRef: MutableRefObject; shortcutRef: MutableRefObject; + reverseShortcutRef: MutableRefObject; + // The binding whose hold-modifier release should commit — set to the shortcut + // of the direction that opened/last drove the switcher, so a divergent custom + // binding for the other direction can't commit early. + activeCommitShortcutRef: MutableRefObject; }; type SwitcherActions = { setOpen: (open: boolean) => void; setSelectedIndex: (index: number) => void; selectItem: (item: RecentTaskDisplayItem | undefined) => void; - openSwitcher: (commitOnRelease: boolean) => void; - cycleSwitcher: (commitOnRelease: boolean) => void; + openSwitcher: (commitOnRelease: boolean, direction?: CycleDirection) => void; + cycleSwitcher: (commitOnRelease: boolean, direction?: CycleDirection) => void; cancelSwitcher: () => void; selectCurrentItem: () => void; }; @@ -182,13 +192,21 @@ function useLatestRef(value: T) { return ref; } -function useSwitcherRefs( - open: boolean, - selectedIndex: number, - items: RecentTaskDisplayItem[], - activeTaskId: string | null, - shortcut: KeyboardShortcut, -): SwitcherRefs { +function useSwitcherRefs({ + open, + selectedIndex, + items, + activeTaskId, + shortcut, + reverseShortcut, +}: { + open: boolean; + selectedIndex: number; + items: RecentTaskDisplayItem[]; + activeTaskId: string | null; + shortcut: KeyboardShortcut; + reverseShortcut: KeyboardShortcut; +}): SwitcherRefs { const openRef = useRef(open); const selectedIndexRef = useRef(selectedIndex); const commitOnReleaseRef = useRef(false); @@ -196,6 +214,8 @@ function useSwitcherRefs( const itemsRef = useLatestRef(items); const activeTaskIdRef = useLatestRef(activeTaskId); const shortcutRef = useLatestRef(shortcut); + const reverseShortcutRef = useLatestRef(reverseShortcut); + const activeCommitShortcutRef = useRef(null); useEffect(() => { openRef.current = open; @@ -210,6 +230,8 @@ function useSwitcherRefs( itemsRef, activeTaskIdRef, shortcutRef, + reverseShortcutRef, + activeCommitShortcutRef, }; } @@ -228,6 +250,95 @@ function useSelectedIndexSetter(refs: SwitcherRefs, setRawSelectedIndex: (index: ); } +type SwitcherCycleActions = Pick; + +function useSwitcherCycleActions({ + refs, + setCommandPanelOpen, + setOpenState, + setSelectedIndex, +}: { + refs: SwitcherRefs; + setCommandPanelOpen: (open: boolean) => void; + setOpenState: (open: boolean) => void; + setSelectedIndex: (index: number) => void; +}): SwitcherCycleActions { + const { + activeCommitShortcutRef, + activeTaskIdRef, + cancelledRef, + commitOnReleaseRef, + itemsRef, + openRef, + reverseShortcutRef, + selectedIndexRef, + shortcutRef, + } = refs; + + const commitShortcutForDirection = useCallback( + (direction: CycleDirection) => + direction === "backward" ? reverseShortcutRef.current : shortcutRef.current, + [reverseShortcutRef, shortcutRef], + ); + + const openSwitcher = useCallback( + (commitOnRelease: boolean, direction: CycleDirection = "forward") => { + setCommandPanelOpen(false); + cancelledRef.current = false; + commitOnReleaseRef.current = commitOnRelease; + activeCommitShortcutRef.current = commitShortcutForDirection(direction); + openRef.current = true; + const initialIndex = + direction === "backward" + ? getInitialReverseSelectionIndex(itemsRef.current, activeTaskIdRef.current) + : getInitialSelectionIndex(itemsRef.current, activeTaskIdRef.current); + setSelectedIndex(initialIndex); + setOpenState(true); + }, + [ + activeCommitShortcutRef, + activeTaskIdRef, + cancelledRef, + commitOnReleaseRef, + commitShortcutForDirection, + itemsRef, + openRef, + setCommandPanelOpen, + setOpenState, + setSelectedIndex, + ], + ); + + const cycleSwitcher = useCallback( + (commitOnRelease: boolean, direction: CycleDirection = "forward") => { + if (!openRef.current) { + openSwitcher(commitOnRelease, direction); + return; + } + if (commitOnRelease) commitOnReleaseRef.current = true; + activeCommitShortcutRef.current = commitShortcutForDirection(direction); + const count = itemsRef.current.length; + const nextIndex = + direction === "backward" + ? getPreviousSelectionIndex(selectedIndexRef.current, count) + : getNextSelectionIndex(selectedIndexRef.current, count); + setSelectedIndex(nextIndex); + }, + [ + activeCommitShortcutRef, + commitOnReleaseRef, + commitShortcutForDirection, + itemsRef, + openRef, + openSwitcher, + selectedIndexRef, + setSelectedIndex, + ], + ); + + return { openSwitcher, cycleSwitcher }; +} + function useSwitcherActions({ refs, routeToTask, @@ -241,14 +352,27 @@ function useSwitcherActions({ setOpenState: (open: boolean) => void; setSelectedIndex: (index: number) => void; }): SwitcherActions { - const { activeTaskIdRef, cancelledRef, commitOnReleaseRef, itemsRef, openRef, selectedIndexRef } = - refs; + const { + activeCommitShortcutRef, + cancelledRef, + commitOnReleaseRef, + itemsRef, + openRef, + selectedIndexRef, + } = refs; + const { openSwitcher, cycleSwitcher } = useSwitcherCycleActions({ + refs, + setCommandPanelOpen, + setOpenState, + setSelectedIndex, + }); const closeSwitcher = useCallback(() => { openRef.current = false; commitOnReleaseRef.current = false; + activeCommitShortcutRef.current = null; setOpenState(false); - }, [commitOnReleaseRef, openRef, setOpenState]); + }, [activeCommitShortcutRef, commitOnReleaseRef, openRef, setOpenState]); const cancelSwitcher = useCallback(() => { cancelledRef.current = true; @@ -276,39 +400,6 @@ function useSwitcherActions({ [closeSwitcher, routeToTask], ); - const openSwitcher = useCallback( - (commitOnRelease: boolean) => { - setCommandPanelOpen(false); - cancelledRef.current = false; - commitOnReleaseRef.current = commitOnRelease; - openRef.current = true; - setSelectedIndex(getInitialSelectionIndex(itemsRef.current, activeTaskIdRef.current)); - setOpenState(true); - }, - [ - activeTaskIdRef, - cancelledRef, - commitOnReleaseRef, - itemsRef, - openRef, - setCommandPanelOpen, - setOpenState, - setSelectedIndex, - ], - ); - - const cycleSwitcher = useCallback( - (commitOnRelease: boolean) => { - if (!openRef.current) { - openSwitcher(commitOnRelease); - return; - } - if (commitOnRelease) commitOnReleaseRef.current = true; - setSelectedIndex(getNextSelectionIndex(selectedIndexRef.current, itemsRef.current.length)); - }, - [commitOnReleaseRef, itemsRef, openRef, openSwitcher, selectedIndexRef, setSelectedIndex], - ); - const selectCurrentItem = useCallback(() => { selectItem(itemsRef.current[selectedIndexRef.current]); }, [itemsRef, selectItem, selectedIndexRef]); @@ -342,7 +433,14 @@ function useRecentTaskSwitcherCommand(shortcut: KeyboardShortcut, openSwitcher: } function useSwitcherGlobalKeyboard(refs: SwitcherRefs, actions: SwitcherActions) { - const { cancelledRef, commitOnReleaseRef, openRef, shortcutRef } = refs; + const { + activeCommitShortcutRef, + cancelledRef, + commitOnReleaseRef, + openRef, + reverseShortcutRef, + shortcutRef, + } = refs; const { cancelSwitcher, cycleSwitcher, selectCurrentItem } = actions; useEffect(() => { @@ -354,18 +452,33 @@ function useSwitcherGlobalKeyboard(refs: SwitcherRefs, actions: SwitcherActions) return; } + // Check the reverse shortcut first: it carries an extra modifier (Shift), + // so it is the more specific match and the forward shortcut never matches + // a Shift-held event anyway. + const reverseShortcut = reverseShortcutRef.current; + if (isCycleShortcutEvent(event, reverseShortcut)) { + event.preventDefault(); + event.stopPropagation(); + cycleSwitcher(hasHoldModifier(reverseShortcut), "backward"); + return; + } + const currentShortcut = shortcutRef.current; if (!isCycleShortcutEvent(event, currentShortcut)) return; event.preventDefault(); event.stopPropagation(); - cycleSwitcher(hasHoldModifier(currentShortcut)); + cycleSwitcher(hasHoldModifier(currentShortcut), "forward"); }; const handleKeyUp = (event: KeyboardEvent) => { - const currentShortcut = shortcutRef.current; if (!openRef.current || !commitOnReleaseRef.current) return; - if (!isCommitReleaseEvent(event, currentShortcut)) return; + // Commit only when the hold modifier of the binding that drove the + // switcher is released. Both directions default to Ctrl/Cmd, but custom + // bindings may differ — releasing the other direction's modifier must not + // commit early. + const activeShortcut = activeCommitShortcutRef.current; + if (!activeShortcut || !isCommitReleaseEvent(event, activeShortcut)) return; event.preventDefault(); event.stopPropagation(); @@ -396,11 +509,13 @@ function useSwitcherGlobalKeyboard(refs: SwitcherRefs, actions: SwitcherActions) document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [ + activeCommitShortcutRef, cancelSwitcher, cancelledRef, commitOnReleaseRef, cycleSwitcher, openRef, + reverseShortcutRef, selectCurrentItem, shortcutRef, ]); @@ -451,12 +566,20 @@ export function useRecentTaskSwitcherController(): RecentTaskSwitcherController const { setOpen: setCommandPanelOpen } = useCommandPanelOpen(); const keyboardShortcuts = useAppStore((state) => state.userSettings.keyboardShortcuts); const shortcut = getShortcut("TASK_SWITCHER", keyboardShortcuts); + const reverseShortcut = getShortcut("TASK_SWITCHER_REVERSE", keyboardShortcuts); const context = useRecentTaskBuildContext(); useRecordActiveTask(context); const items = useMemo(() => buildRecentTaskDisplayItems(entries, context), [entries, context]); const selectedIndex = getResolvedSelectionIndex(rawSelectedIndex, items, context.activeTaskId); - const refs = useSwitcherRefs(open, selectedIndex, items, context.activeTaskId, shortcut); + const refs = useSwitcherRefs({ + open, + selectedIndex, + items, + activeTaskId: context.activeTaskId, + shortcut, + reverseShortcut, + }); const setSelectedIndex = useSelectedIndexSetter(refs, setRawSelectedIndex); const routeToTask = useCallback((taskId: string) => router.push(linkToTask(taskId)), [router]); const actions = useSwitcherActions({ @@ -479,6 +602,7 @@ export function useRecentTaskSwitcherController(): RecentTaskSwitcherController selectedIndex, setSelectedIndex, shortcutLabel: formatShortcut(shortcut), + reverseShortcutLabel: formatShortcut(reverseShortcut), selectItem: actions.selectItem, handleKeyDown, }; diff --git a/apps/web/components/task/recent-task-switcher-keys.test.ts b/apps/web/components/task/recent-task-switcher-keys.test.ts index fc071bae5f..37fe3c6d7a 100644 --- a/apps/web/components/task/recent-task-switcher-keys.test.ts +++ b/apps/web/components/task/recent-task-switcher-keys.test.ts @@ -24,6 +24,11 @@ const taskSwitcherShortcut: KeyboardShortcut = { modifiers: { ctrlOrCmd: true }, }; +const taskSwitcherReverseShortcut: KeyboardShortcut = { + key: KEYS.SPACE, + modifiers: { ctrlOrCmd: true, shift: true }, +}; + function platform(platform: Platform): Platform { return platform; } @@ -70,6 +75,30 @@ describe("recent task switcher key helpers", () => { expect(isCycleShortcutEvent(event({ key: "y", ctrlKey: true }), shortcut)).toBe(false); }); + it("separates forward and reverse switcher shortcuts by the Shift modifier", () => { + const forwardEvent = event({ key: KEYS.SPACE, ctrlKey: true }); + const reverseEvent = event({ key: KEYS.SPACE, ctrlKey: true, shiftKey: true }); + + // Forward fires only without Shift; reverse fires only with Shift. + expect(isCycleShortcutEvent(forwardEvent, taskSwitcherShortcut)).toBe(true); + expect(isCycleShortcutEvent(forwardEvent, taskSwitcherReverseShortcut)).toBe(false); + expect(isCycleShortcutEvent(reverseEvent, taskSwitcherShortcut)).toBe(false); + expect(isCycleShortcutEvent(reverseEvent, taskSwitcherReverseShortcut)).toBe(true); + }); + + it("commits the reverse shortcut on Ctrl/Cmd release, not Shift release", () => { + expect( + isCommitReleaseEvent( + event({ key: "Control" }), + taskSwitcherReverseShortcut, + platform("linux"), + ), + ).toBe(true); + expect( + isCommitReleaseEvent(event({ key: "Shift" }), taskSwitcherReverseShortcut, platform("linux")), + ).toBe(false); + }); + it("matches the hold modifier release, not secondary modifier release", () => { const shortcut: KeyboardShortcut = { key: KEYS.Y, diff --git a/apps/web/components/task/recent-task-switcher-model.test.ts b/apps/web/components/task/recent-task-switcher-model.test.ts index 755209495c..c2d4a472df 100644 --- a/apps/web/components/task/recent-task-switcher-model.test.ts +++ b/apps/web/components/task/recent-task-switcher-model.test.ts @@ -11,6 +11,7 @@ import { import { buildRecentTaskDisplayItems, buildRecentTaskEntry, + getInitialReverseSelectionIndex, getInitialSelectionIndex, getNextSelectionIndex, getPreviousSelectionIndex, @@ -134,6 +135,26 @@ describe("recent task switcher model", () => { expect(getInitialSelectionIndex([], CURRENT_TASK_ID)).toBe(-1); }); + it("starts reverse selection on the last non-current task", () => { + const items = [ + { taskId: CURRENT_TASK_ID }, + { taskId: PREVIOUS_TASK_ID }, + { taskId: "task-older" }, + ]; + + expect(getInitialReverseSelectionIndex(items, CURRENT_TASK_ID)).toBe(2); + // When the current task is the last item, skip back to the previous one. + expect( + getInitialReverseSelectionIndex( + [{ taskId: PREVIOUS_TASK_ID }, { taskId: CURRENT_TASK_ID }], + CURRENT_TASK_ID, + ), + ).toBe(0); + // All items are current -> fall back to the last index. + expect(getInitialReverseSelectionIndex([{ taskId: CURRENT_TASK_ID }], CURRENT_TASK_ID)).toBe(0); + expect(getInitialReverseSelectionIndex([], CURRENT_TASK_ID)).toBe(-1); + }); + it("cycles through items and handles empty lists", () => { expect(getNextSelectionIndex(0, 3)).toBe(1); expect(getNextSelectionIndex(2, 3)).toBe(0); diff --git a/apps/web/components/task/recent-task-switcher-model.ts b/apps/web/components/task/recent-task-switcher-model.ts index 62ec06880f..e8adf8844b 100644 --- a/apps/web/components/task/recent-task-switcher-model.ts +++ b/apps/web/components/task/recent-task-switcher-model.ts @@ -362,6 +362,17 @@ export function getInitialSelectionIndex( return firstNonCurrent === -1 ? 0 : firstNonCurrent; } +export function getInitialReverseSelectionIndex( + items: Array<{ taskId: string }>, + currentTaskId: string | null, +): number { + if (items.length === 0) return -1; + for (let index = items.length - 1; index >= 0; index--) { + if (items[index].taskId !== currentTaskId) return index; + } + return items.length - 1; +} + export function getNextSelectionIndex(currentIndex: number, itemCount: number): number { if (itemCount === 0) return -1; if (currentIndex < 0) return 0; diff --git a/apps/web/components/task/recent-task-switcher.tsx b/apps/web/components/task/recent-task-switcher.tsx index 7d1c356bd9..4001803d60 100644 --- a/apps/web/components/task/recent-task-switcher.tsx +++ b/apps/web/components/task/recent-task-switcher.tsx @@ -128,10 +128,21 @@ function RecentTaskList({ ); } -function SwitcherFooter({ shortcutLabel }: { shortcutLabel: string }) { +function SwitcherFooter({ + shortcutLabel, + reverseShortcutLabel, +}: { + shortcutLabel: string; + reverseShortcutLabel: string; +}) { return ( -
- {shortcutLabel} +
+ + {shortcutLabel} Next + + + {reverseShortcutLabel} Previous +
); } @@ -160,7 +171,10 @@ function RecentTaskSwitcherDialog(props: RecentTaskSwitcherController) { selectItem={props.selectItem} />
- + ); diff --git a/apps/web/e2e/tests/settings/keyboard-shortcuts.spec.ts b/apps/web/e2e/tests/settings/keyboard-shortcuts.spec.ts index 80551bb75e..b5a5344092 100644 --- a/apps/web/e2e/tests/settings/keyboard-shortcuts.spec.ts +++ b/apps/web/e2e/tests/settings/keyboard-shortcuts.spec.ts @@ -19,6 +19,7 @@ test.describe("Keyboard Shortcuts Settings", () => { await expect(testPage.getByTestId("shortcut-recorder-FOCUS_INPUT")).toBeVisible(); await expect(testPage.getByTestId("shortcut-recorder-TOGGLE_PLAN_MODE")).toBeVisible(); await expect(testPage.getByTestId("shortcut-recorder-TASK_SWITCHER")).toBeVisible(); + await expect(testPage.getByTestId("shortcut-recorder-TASK_SWITCHER_REVERSE")).toBeVisible(); }); test("can record a new shortcut and persist it", async ({ testPage, apiClient, seedData }) => { diff --git a/apps/web/e2e/tests/task/recent-task-switcher.spec.ts b/apps/web/e2e/tests/task/recent-task-switcher.spec.ts index 52683c2392..b2393c160f 100644 --- a/apps/web/e2e/tests/task/recent-task-switcher.spec.ts +++ b/apps/web/e2e/tests/task/recent-task-switcher.spec.ts @@ -68,6 +68,67 @@ test.describe("Recent task switcher", () => { await expect(testPage).toHaveURL(new RegExp(`/t/${first.id}`), { timeout: 10_000 }); }); + test("reverse shortcut cycles backward through recent tasks", async ({ + testPage, + apiClient, + seedData, + }) => { + const first = await apiClient.createTask(seedData.workspaceId, "Reverse Switcher One", { + workflow_id: seedData.workflowId, + workflow_step_id: seedData.startStepId, + repository_ids: [seedData.repositoryId], + }); + const second = await apiClient.createTask(seedData.workspaceId, "Reverse Switcher Two", { + workflow_id: seedData.workflowId, + workflow_step_id: seedData.startStepId, + repository_ids: [seedData.repositoryId], + }); + const third = await apiClient.createTask(seedData.workspaceId, "Reverse Switcher Three", { + workflow_id: seedData.workflowId, + workflow_step_id: seedData.startStepId, + repository_ids: [seedData.repositoryId], + }); + + // Visit oldest -> newest so the recent list is [third(current), second, first]. + const titleByTask = new Map([ + [first.id, "Reverse Switcher One"], + [second.id, "Reverse Switcher Two"], + [third.id, "Reverse Switcher Three"], + ]); + for (const task of [first, second, third]) { + await testPage.goto(`/t/${task.id}`); + await expect( + testPage.getByText(titleByTask.get(task.id)!, { exact: true }).first(), + ).toBeVisible({ timeout: 15_000 }); + } + + const modifier = taskSwitcherModifier(); + await testPage.keyboard.down(modifier); + await testPage.keyboard.down("Shift"); + await testPage.keyboard.press("Space"); + + const switcher = testPage.getByTestId("recent-task-switcher"); + await expect(switcher).toBeVisible({ timeout: 5_000 }); + + // Reverse opens on the oldest non-current task (first). + const firstRow = switcher.getByTestId(`recent-task-switcher-item-${first.id}`); + const secondRow = switcher.getByTestId(`recent-task-switcher-item-${second.id}`); + await expect(firstRow).toHaveAttribute("data-selected", "true"); + + // Another reverse step moves selection backward to the newer "second" task. + await testPage.keyboard.press("Space"); + await expect(secondRow).toHaveAttribute("data-selected", "true"); + + // Releasing Shift alone must not commit — only the hold modifier does. + await testPage.keyboard.up("Shift"); + await expect(switcher).toBeVisible(); + await expect(secondRow).toHaveAttribute("data-selected", "true"); + + await testPage.keyboard.up(modifier); + await expect(switcher).not.toBeVisible({ timeout: 5_000 }); + await expect(testPage).toHaveURL(new RegExp(`/t/${second.id}`), { timeout: 10_000 }); + }); + test("escape cancels the held switcher without routing on release", async ({ testPage, apiClient, diff --git a/apps/web/lib/keyboard/constants.ts b/apps/web/lib/keyboard/constants.ts index e05ab23736..7d7c824e31 100644 --- a/apps/web/lib/keyboard/constants.ts +++ b/apps/web/lib/keyboard/constants.ts @@ -145,6 +145,12 @@ export const SHORTCUTS = { key: KEYS.SPACE, modifiers: { ctrlOrCmd: true }, }, + // Cycle the recent-task switcher backward (oldest -> most-recent). Shift + // distinguishes it from TASK_SWITCHER so both can be held together. + TASK_SWITCHER_REVERSE: { + key: KEYS.SPACE, + modifiers: { ctrlOrCmd: true, shift: true }, + }, BOTTOM_TERMINAL: { key: KEYS.J, modifiers: { ctrlOrCmd: true }, diff --git a/apps/web/lib/keyboard/shortcut-overrides.test.ts b/apps/web/lib/keyboard/shortcut-overrides.test.ts index 43c59c3dfd..b03c70d9da 100644 --- a/apps/web/lib/keyboard/shortcut-overrides.test.ts +++ b/apps/web/lib/keyboard/shortcut-overrides.test.ts @@ -20,8 +20,9 @@ describe("CONFIGURABLE_SHORTCUTS", () => { expect(ids).toContain("FOCUS_INPUT"); expect(ids).toContain("TOGGLE_PLAN_MODE"); expect(ids).toContain("TASK_SWITCHER"); + expect(ids).toContain("TASK_SWITCHER_REVERSE"); expect(ids).toContain("VOICE_INPUT_TOGGLE"); - expect(ids).toHaveLength(11); + expect(ids).toHaveLength(12); }); it("each entry has a label and default matching SHORTCUTS", () => { @@ -45,6 +46,13 @@ describe("CONFIGURABLE_SHORTCUTS", () => { expect(CONFIGURABLE_SHORTCUTS.TASK_SWITCHER.label).toBe("Recent Task Switcher"); expect(CONFIGURABLE_SHORTCUTS.TASK_SWITCHER.default).toBe(SHORTCUTS.TASK_SWITCHER); + + expect(CONFIGURABLE_SHORTCUTS.TASK_SWITCHER_REVERSE.label).toBe( + "Recent Task Switcher (Backward)", + ); + expect(CONFIGURABLE_SHORTCUTS.TASK_SWITCHER_REVERSE.default).toBe( + SHORTCUTS.TASK_SWITCHER_REVERSE, + ); }); }); diff --git a/apps/web/lib/keyboard/shortcut-overrides.ts b/apps/web/lib/keyboard/shortcut-overrides.ts index a31d61e154..98c78808a2 100644 --- a/apps/web/lib/keyboard/shortcut-overrides.ts +++ b/apps/web/lib/keyboard/shortcut-overrides.ts @@ -11,6 +11,7 @@ export type ConfigurableShortcutId = | "FOCUS_INPUT" | "TOGGLE_PLAN_MODE" | "TASK_SWITCHER" + | "TASK_SWITCHER_REVERSE" | "VOICE_INPUT_TOGGLE"; export type StoredShortcutOverrides = Record< @@ -32,6 +33,10 @@ export const CONFIGURABLE_SHORTCUTS: Record< FOCUS_INPUT: { label: "Focus Chat Input", default: SHORTCUTS.FOCUS_INPUT }, TOGGLE_PLAN_MODE: { label: "Toggle Plan Mode", default: SHORTCUTS.TOGGLE_PLAN_MODE }, TASK_SWITCHER: { label: "Recent Task Switcher", default: SHORTCUTS.TASK_SWITCHER }, + TASK_SWITCHER_REVERSE: { + label: "Recent Task Switcher (Backward)", + default: SHORTCUTS.TASK_SWITCHER_REVERSE, + }, VOICE_INPUT_TOGGLE: { label: "Voice Input", default: SHORTCUTS.VOICE_INPUT_TOGGLE }, };