From f9c400cd78f70b15e5c755e8952376470132e7d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:19:58 +0100 Subject: [PATCH] Fix hotkey bugs (#17273) - Fix a race condition due to a page change effect re-trigger which reset the focus stack (A user had a bug which happened when he opened the command menu quickly after the page load. The focus stack was reset and the hotkeys listening were the one from the table instead of the one from the command menu) - Fix the focus id in the side panel input which prevented using the hotkeys --- .../app/effect-components/PageChangeEffect.tsx | 4 ++++ .../command-menu/components/CommandMenuTopBar.tsx | 10 +++++----- .../components/__stories__/CommandMenu.stories.tsx | 14 ++++---------- .../constants/CommandMenuSearchInputFocusId.ts | 1 - .../modules/command-menu/hooks/useCommandMenu.ts | 6 +----- 5 files changed, 14 insertions(+), 21 deletions(-) delete mode 100644 packages/twenty-front/src/modules/command-menu/constants/CommandMenuSearchInputFocusId.ts diff --git a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx index 77db3d456b..aed6a91dee 100644 --- a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx @@ -151,6 +151,10 @@ export const PageChangeEffect = () => { } } + if (location.pathname === previousLocation) { + return; + } + switch (true) { case isMatchingLocation(location, AppPath.RecordIndexPage): { resetFocusStackToRecordIndex(); diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx index f34c230b9c..f8cdeabe8d 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx @@ -5,7 +5,7 @@ import { CommandMenuTopBarRightCornerIcon } from '@/command-menu/components/Comm import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight'; import { COMMAND_MENU_SEARCH_BAR_HEIGHT_MOBILE } from '@/command-menu/constants/CommandMenuSearchBarHeightMobile'; import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding'; -import { COMMAND_MENU_SEARCH_INPUT_FOCUS_ID } from '@/command-menu/constants/CommandMenuSearchInputFocusId'; +import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenuContextChips } from '@/command-menu/hooks/useCommandMenuContextChips'; import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; @@ -110,10 +110,10 @@ export const CommandMenuTopBar = () => { const handleInputFocus = () => { pushFocusItemToFocusStack({ - focusId: COMMAND_MENU_SEARCH_INPUT_FOCUS_ID, + focusId: SIDE_PANEL_FOCUS_ID, component: { type: FocusComponentType.TEXT_INPUT, - instanceId: COMMAND_MENU_SEARCH_INPUT_FOCUS_ID, + instanceId: SIDE_PANEL_FOCUS_ID, }, globalHotkeysConfig: { enableGlobalHotkeysConflictingWithKeyboard: false, @@ -123,7 +123,7 @@ export const CommandMenuTopBar = () => { const handleInputBlur = () => { removeFocusItemFromFocusStackById({ - focusId: COMMAND_MENU_SEARCH_INPUT_FOCUS_ID, + focusId: SIDE_PANEL_FOCUS_ID, }); }; @@ -175,7 +175,7 @@ export const CommandMenuTopBar = () => { commandMenuPage === CommandMenuPages.SearchRecords) && ( <> { const canvas = within(canvasElement); - const searchInput = await canvas.findByTestId( - COMMAND_MENU_SEARCH_INPUT_FOCUS_ID, - ); + const searchInput = await canvas.findByTestId(SIDE_PANEL_FOCUS_ID); await sleep(openTimeout); await userEvent.type(searchInput, 'ta'); expect(await canvas.findByText('Go to Tasks')).toBeVisible(); @@ -165,9 +163,7 @@ export const MatchingNavigate: Story = { export const MatchingNavigateShortcuts: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const searchInput = await canvas.findByTestId( - COMMAND_MENU_SEARCH_INPUT_FOCUS_ID, - ); + const searchInput = await canvas.findByTestId(SIDE_PANEL_FOCUS_ID); await sleep(openTimeout); await userEvent.type(searchInput, 'gp'); expect(await canvas.findByText('Go to People')).toBeVisible(); @@ -192,9 +188,7 @@ export const MatchingNavigateShortcuts: Story = { export const NoResultsSearchFallback: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const searchInput = await canvas.findByTestId( - COMMAND_MENU_SEARCH_INPUT_FOCUS_ID, - ); + const searchInput = await canvas.findByTestId(SIDE_PANEL_FOCUS_ID); await sleep(openTimeout); await userEvent.type(searchInput, 'input without results'); expect(await canvas.findByText('No results found')).toBeVisible(); diff --git a/packages/twenty-front/src/modules/command-menu/constants/CommandMenuSearchInputFocusId.ts b/packages/twenty-front/src/modules/command-menu/constants/CommandMenuSearchInputFocusId.ts deleted file mode 100644 index 35ce16675f..0000000000 --- a/packages/twenty-front/src/modules/command-menu/constants/CommandMenuSearchInputFocusId.ts +++ /dev/null @@ -1 +0,0 @@ -export const COMMAND_MENU_SEARCH_INPUT_FOCUS_ID = 'command-menu-search-input'; diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts index 8a6c4e7b77..b184f50227 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts @@ -2,10 +2,10 @@ import { useRecoilCallback } from 'recoil'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; -import { COMMAND_MENU_SEARCH_INPUT_FOCUS_ID } from '@/command-menu/constants/CommandMenuSearchInputFocusId'; import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId'; import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState'; +import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown'; import { emitSidePanelOpenEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelOpenEvent'; @@ -13,7 +13,6 @@ import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks import { t } from '@lingui/core/macro'; import { useCallback } from 'react'; import { IconDotsVertical } from 'twenty-ui/display'; -import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; export const useCommandMenu = () => { const { navigateCommandMenu } = useNavigateCommandMenu(); @@ -33,9 +32,6 @@ export const useCommandMenu = () => { set(isCommandMenuOpenedState, false); set(isCommandMenuClosingState, true); closeAnyOpenDropdown(); - removeFocusItemFromFocusStackById({ - focusId: COMMAND_MENU_SEARCH_INPUT_FOCUS_ID, - }); removeFocusItemFromFocusStackById({ focusId: SIDE_PANEL_FOCUS_ID, });