fix: Edit Layout keeping the command menu open (#21161)

## Summary
- Fixes https://github.com/twentyhq/core-team-issues/issues/2460
- Engine/headless commands always skipped closing the menu
(`closeSidePanelOnCommandMenuListExecution: false`), even when the item
was not pinned. Edit Layout is `isPinned: false`, so the menu should
close like other list-only actions.

## Approach
- Option 1 (I chose this one): Derive close behavior from
`item.isPinned` -> pinned commands keep the menu open; non-pinned ones
close it.
- Option 2 (not chosen): remove the engine command override totally and
use the default close behavior for all commands.

Option 1 is more targeted: it fixed Edit Layout without changing pinned
commands (e.g. Export progress in the menu list). Option 2 is simpler
but widens the blast radius to every engine command clicked from the
side panel list.

## Screenshots
### Before

https://github.com/user-attachments/assets/70b8dc75-af00-4917-81a1-646381f571d5

### After

https://github.com/user-attachments/assets/f733d696-9330-4e6c-993b-8a8133c53e0d

---------

Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Parship Chowdhury
2026-06-19 16:51:50 +05:30
committed by GitHub
parent 1ec59beb8b
commit 4de9f45015
3 changed files with 49 additions and 20 deletions
@@ -1,13 +1,17 @@
import { useResetLocationHash } from 'twenty-ui/utilities';
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
import { useEnterLayoutCustomizationMode } from '@/layout-customization/hooks/useEnterLayoutCustomizationMode';
import { useResetLocationHash } from 'twenty-ui/utilities';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
export const EditRecordPageLayoutSingleRecordCommand = () => {
const { enterLayoutCustomizationMode } = useEnterLayoutCustomizationMode();
const { closeSidePanelMenu } = useSidePanelMenu();
const { resetLocationHash } = useResetLocationHash();
const handleExecute = () => {
const handleExecute = async () => {
await closeSidePanelMenu();
enterLayoutCustomizationMode();
resetLocationHash();
};
@@ -11,6 +11,7 @@ import { sidePanelSearchObjectFilterState } from '@/side-panel/states/sidePanelS
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
import { emitSidePanelOpenEvent } from '@/ui/layout/side-panel/utils/emitSidePanelOpenEvent';
import { waitForSidePanelClose } from '@/ui/layout/side-panel/utils/waitForSidePanelClose';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { t } from '@lingui/core/macro';
import { useStore } from 'jotai';
@@ -30,26 +31,30 @@ export const useSidePanelMenu = () => {
const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const closeSidePanelMenu = useCallback(() => {
const closeSidePanelMenu = useCallback(async () => {
const isSidePanelOpened = store.get(isSidePanelOpenedState.atom);
if (isSidePanelOpened) {
const isLayoutCustomizationModeEnabled = store.get(
isLayoutCustomizationModeEnabledState.atom,
);
if (isLayoutCustomizationModeEnabled) {
resetRecordIndexSelection();
}
store.set(sidePanelNavigationStackState.atom, []);
store.set(isSidePanelOpenedState.atom, false);
store.set(isSidePanelClosingState.atom, true);
closeAnyOpenDropdown();
removeFocusItemFromFocusStackById({
focusId: SIDE_PANEL_FOCUS_ID,
});
if (!isSidePanelOpened) {
return;
}
const isLayoutCustomizationModeEnabled = store.get(
isLayoutCustomizationModeEnabledState.atom,
);
if (isLayoutCustomizationModeEnabled) {
resetRecordIndexSelection();
}
store.set(sidePanelNavigationStackState.atom, []);
store.set(isSidePanelOpenedState.atom, false);
store.set(isSidePanelClosingState.atom, true);
closeAnyOpenDropdown();
removeFocusItemFromFocusStackById({
focusId: SIDE_PANEL_FOCUS_ID,
});
await waitForSidePanelClose();
}, [
closeAnyOpenDropdown,
removeFocusItemFromFocusStackById,
@@ -0,0 +1,20 @@
import { SIDE_PANEL_CLOSE_EVENT_NAME } from '@/ui/layout/side-panel/utils/emitSidePanelCloseEvent';
const SIDE_PANEL_CLOSE_TIMEOUT_MS = 500;
export const waitForSidePanelClose = (): Promise<void> => {
return new Promise((resolve) => {
const handler = () => {
window.removeEventListener(SIDE_PANEL_CLOSE_EVENT_NAME, handler);
clearTimeout(timeoutId);
resolve();
};
const timeoutId = setTimeout(() => {
window.removeEventListener(SIDE_PANEL_CLOSE_EVENT_NAME, handler);
resolve();
}, SIDE_PANEL_CLOSE_TIMEOUT_MS);
window.addEventListener(SIDE_PANEL_CLOSE_EVENT_NAME, handler);
});
};