Skip to content

feat(web): replace existing shortcuts with TanStack Hotkeys#1507

Open
tibisabau wants to merge 4 commits intoSwitchbackTech:mainfrom
tibisabau:feature/replace-shortcuts-with-tanstack-hotkeys
Open

feat(web): replace existing shortcuts with TanStack Hotkeys#1507
tibisabau wants to merge 4 commits intoSwitchbackTech:mainfrom
tibisabau:feature/replace-shortcuts-with-tanstack-hotkeys

Conversation

@tibisabau
Copy link

Description

Replaced the existing custom keyboard shortcut implementation with TanStack Hotkeys library. This migration reduces the amount of custom code we need to maintain while leveraging a well-tested, production-ready hotkeys solution. TanStack Hotkeys provides a cleaner API and better support for advanced use cases like sequence shortcuts.

The migration involved:

  • Replacing react-hotkeys-hook with @tanstack/react-hotkeys
  • Refactoring useKeyboardEvent hook to use TanStack Hotkeys internally
  • Updating all components that directly used react-hotkeys-hook
  • Updating tests to work with the new implementation
  • Maintaining backward compatibility with existing keyboard event handlers

Closes #1472.

The code in this pull request was generated by GitHub Copilot with the Claude Sonnet 4.5 model.

Checklist if Applicable

  • The tests passed – yarn test packages/web/src/common/hooks/useKeyboardEvent.test.ts and yarn test packages/web/src/views/Forms/SomedayEventForm/useSomedayFormShortcuts.test.tsx
  • Linting passed – yarn prettier . --write
  • Documentation has been added
  • CHANGELOG.md has been updated

Copilot AI review requested due to automatic review settings March 3, 2026 21:57
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the web app’s keyboard shortcut handling from react-hotkeys-hook (and some custom global keyboard plumbing) to TanStack Hotkeys, aiming to reduce custom maintenance while keeping existing shortcut behavior working.

Changes:

  • Added @tanstack/react-hotkeys and removed react-hotkeys-hook.
  • Updated form and calendar shortcut hooks/components to register TanStack hotkeys and normalized several key combo strings (e.g., $mod, arrow*).
  • Refactored useKeyboardEvent to delegate to TanStack Hotkeys and updated its unit tests accordingly.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
yarn.lock Adds TanStack hotkeys/store dependencies and removes react-hotkeys-hook.
packages/web/package.json Swaps dependency from react-hotkeys-hook to @tanstack/react-hotkeys.
packages/web/src/views/Forms/SomedayEventForm/useSomedayFormShortcuts.ts Updates Someday form hotkeys to TanStack format ($mod, arrow*).
packages/web/src/views/Forms/SomedayEventForm/useSomedayFormShortcuts.test.tsx Adjusts mocks and expectations for TanStack hotkey combos.
packages/web/src/views/Forms/EventForm/EventForm.tsx Migrates EventForm shortcuts to TanStack and updates combo strings/deps.
packages/web/src/views/Calendar/hooks/shortcuts/useFocusHotkey.ts Migrates reminder hotkey hook to TanStack.
packages/web/src/views/Calendar/components/Dedication/Dedication.tsx Migrates dedication modal shortcut hook to TanStack.
packages/web/src/common/hooks/useKeyboardEvent.ts Replaces custom key stream logic with TanStack useHotkeys.
packages/web/src/common/hooks/useKeyboardEvent.test.ts Reworks tests to mock TanStack useHotkeys and validate registration/behavior.
Comments suppressed due to low confidence (1)

packages/web/src/common/hooks/useKeyboardEvent.ts:25

  • exactMatch is destructured with a default (exactMatch = true) but never used. With noUnusedLocals: true in tsconfig, this will fail TypeScript compilation. Either implement the exactMatch behavior (to preserve the prior API) or remove it from the options/destructuring to avoid the unused local.
export function useKeyboardEvent({
  combination,
  exactMatch = true,
  handler,
  listenWhileEditing,

Copy link
Contributor

@tyler-dane tyler-dane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. However, e2e tests are failing and there are valid PR comments

Copilot AI review requested due to automatic review settings March 5, 2026 11:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.

Comment on lines 95 to +104
/**
* useSetupKeyboardEvents
*
* hook to setup global key event listeners
* should only be ideally called once in the app root component
* TanStack Hotkeys handles this automatically, so this is now a no-op
* kept for backward compatibility
*/
export function useSetupKeyboardEvents() {
useEffect(() => {
window.addEventListener("keydown", globalOnKeyPressHandler);
window.addEventListener("keyup", globalOnKeyUpHandler);

return () => {
window.removeEventListener("keydown", globalOnKeyPressHandler);
window.removeEventListener("keyup", globalOnKeyUpHandler);
};
}, []);
// TanStack Hotkeys automatically sets up event listeners
// This function is kept for backward compatibility but does nothing
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useSetupKeyboardEvents function is now a no-op, but the old RxJS-based event system (keyPressed$, keyReleased$, globalOnKeyPressHandler, globalOnKeyUpHandler) is still used by other parts of the codebase. Critically, the pressKey utility (used in multiple test files and in production code like CmdPalette.tsx and DayCmdPalette.tsx) dispatches events to window, while TanStack Hotkeys listens on document. This means:

  1. Broken tests: useDayViewShortcuts.test.ts, useNowShortcuts.test.ts, useGlobalShortcuts.test.ts, and DayCmdPalette.test.tsx all use pressKey() to simulate keyboard events through the old system. Since useKeyboardEvent no longer subscribes to keyPressed$/keyReleased$, these tests will fail. Run yarn test:web to confirm.

  2. Broken production code: CmdPalette.tsx, DayCmdPalette.tsx, and NowCmdPalette.tsx call pressKey() to programmatically trigger shortcuts (e.g., onClick: () => pressKey("z")). These will no longer trigger the corresponding shortcut handlers since the RxJS pipeline is disconnected.

The migration needs to also update pressKey to dispatch to document instead of window, and the dependent tests need to be updated or the test approach needs to change.

Copilot uses AI. Check for mistakes.
combination: string[];
handler?: (e: KeyCombination) => void;
handler?: (e: KeyboardEvent) => void;
exactMatch?: boolean;
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exactMatch option is still accepted in the Options interface (line 8) but is never used in the new implementation. It's destructured on the equivalent of line 45 but silently discarded. This is a behavioral regression: useDayViewShortcuts.ts (lines 150, 157) passes exactMatch: false to useKeyDownEvent for migration shortcuts, which previously allowed superset key combinations to match. With TanStack Hotkeys, the matching behavior is entirely controlled by the library and exactMatch has no effect. Either remove exactMatch from the Options interface (and update callers) or implement equivalent behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +10
// Mock TanStack Hotkeys
jest.mock("@tanstack/react-hotkeys", () => ({
useHotkey: jest.fn(),
}));

const mockUseHotkey = jest.requireMock("@tanstack/react-hotkeys").useHotkey;
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests have been rewritten from integration-style tests (dispatching real keyboard events and observing outcomes) to implementation-detail tests (mocking useHotkey and inspecting mock calls). This violates the testing guidelines in AGENTS.md which state: "Write tests the way a user would use the application by using the DOM and user interactions" and "When writing tests, avoid mocking as much as possible." The previous tests were closer to these guidelines. Consider testing with real keyboard events via @testing-library/user-event and verifying the handler callbacks were invoked, rather than mocking the entire TanStack Hotkeys library.

Copilot generated this review using guidance from repository custom instructions.
const pressShortcut = async (page: Page, key: string) => {
await page.evaluate((shortcut) => {
window.dispatchEvent(
document.dispatchEvent(
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "Dispatch a keyboard shortcut to the window" but the code now dispatches events to document instead. Update the JSDoc to say "Dispatch a keyboard shortcut to the document" to match the actual behavior.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace existing shortcuts with TanStack Hotkeys

3 participants