fix: guard arrow hotkeys when current board is not in cache

Applies CodeRabbit review feedback: only register the arrow shortcuts
when the current board is found in the boards cache (currentIndex !== -1),
otherwise the modulo arithmetic would jump to an unexpected board.

Extracts the wraparound index calculation into a pure getNextBoardIndex
helper with unit tests.
This commit is contained in:
Thomas Camlong committed 2026-08-03 17:27:42 +02:00
1 parent 16091290e6
commit 2845f61d1b
3 files changed
+40 -3

No files matched your search

@@ -0,0 +1,23 @@
import { describe, expect, test } from "vitest";
import { getNextBoardIndex } from "./_board-navigation";
describe("get next board index", () => {
test.each([
[3, 0, 1, 1],
[3, 2, 1, 0],
[3, 2, -1, 1],
[3, 0, -1, 2],
] as const)("moves through boards with wraparound", (boardCount, currentIndex, direction, expected) => {
expect(getNextBoardIndex(boardCount, currentIndex, direction)).toBe(expected);
});
test.each([
[0, 0],
[1, 0],
[3, -1],
[3, 3],
])("does not navigate with an invalid current board", (boardCount, currentIndex) => {
expect(getNextBoardIndex(boardCount, currentIndex, 1)).toBeNull();
});
});
@@ -0,0 +1,4 @@
export const getNextBoardIndex = (boardCount: number, currentIndex: number, direction: -1 | 1) => {
if (boardCount <= 1 || currentIndex < 0 || currentIndex >= boardCount) return null;
return (currentIndex + direction + boardCount) % boardCount;
};
@@ -40,6 +40,7 @@ import { CategoryEditModal } from "~/components/board/sections/category/category
import { useDynamicSectionActions } from "~/components/board/sections/dynamic/dynamic-actions";
import { IntegrationSelectModal } from "~/components/integration/integration-select-modal";
import { HeaderButton } from "~/components/layout/header/button";
import { getNextBoardIndex } from "./_board-navigation";
export const BoardContentHeaderActions = ({ demoReadOnly }: { demoReadOnly: boolean }) => {
const [isEditMode] = useEditMode();
@@ -223,9 +224,18 @@ const BoardNavigationHotkeys = () => {
const hotkeyEntries = useMemo(() => {
const entries: [string, () => void][] = [];
if (boards.length > 1) {
entries.push(["mod+shift+ArrowRight", () => navigateToBoard((currentIndex + 1) % boards.length)]);
entries.push(["mod+shift+ArrowLeft", () => navigateToBoard((currentIndex - 1 + boards.length) % boards.length)]);
// Only register the arrow shortcuts when the current board is known. If the
// board hasn't synced with the getAllBoards cache yet, currentIndex is -1 and
// the modulo arithmetic would otherwise jump to an unexpected board.
if (boards.length > 1 && currentIndex !== -1) {
entries.push(["mod+shift+ArrowRight", () => {
const nextIndex = getNextBoardIndex(boards.length, currentIndex, 1);
if (nextIndex !== null) navigateToBoard(nextIndex);
}]);
entries.push(["mod+shift+ArrowLeft", () => {
const nextIndex = getNextBoardIndex(boards.length, currentIndex, -1);
if (nextIndex !== null) navigateToBoard(nextIndex);
}]);
}
for (let i = 0; i < Math.min(boards.length, 9); i++) {