Fix a hotkey scope race condition in command menu (#13025)

Fixes https://github.com/twentyhq/twenty/issues/12885

This PR fixes a hotkey scope race condition happening on note/task
creation.

The problem is that `ActivityRichTextEditor` catches the click event
before the title cell.

So here we prevent this from happening by checking if the record title
cell is.

This is only temporary and should be improved after the persist logic
refactor : https://github.com/twentyhq/core-team-issues/issues/192
This commit is contained in:
Lucas Bordeau
2025-07-03 14:04:37 +02:00
committed by GitHub
parent 74c8ab422b
commit 1f1318febf
5 changed files with 147 additions and 53 deletions

View File

@ -11,7 +11,6 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
import { Key } from 'ts-key-enum';
@ -33,7 +32,14 @@ import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
import { isInlineCellInEditModeScopedState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeScopedState';
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
import { getRecordTitleCellId } from '@/object-record/record-title-cell/utils/getRecordTitleCellId';
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import type { PartialBlock } from '@blocknote/core';
import '@blocknote/core/fonts/inter.css';
import '@blocknote/mantine/style.css';
@ -86,6 +92,10 @@ export const ActivityRichTextEditor = ({
objectNameSingular: CoreObjectNameSingular.Attachment,
});
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const { fetchAllRecords: findSoftDeletedAttachments } =
useLazyFetchAllRecords({
objectNameSingular: CoreObjectNameSingular.Attachment,
@ -96,11 +106,6 @@ export const ActivityRichTextEditor = ({
},
});
const {
goBackToPreviousHotkeyScope,
setHotkeyScopeAndMemorizePreviousScope,
} = usePreviousHotkeyScope();
const { upsertActivity } = useUpsertActivity({
activityObjectNameSingular: activityObjectNameSingular,
});
@ -354,16 +359,60 @@ export const ActivityRichTextEditor = ({
preventDefault: false,
},
);
const { labelIdentifierFieldMetadataItem } = useRecordShowContainerData({
objectNameSingular: activityObjectNameSingular,
objectRecordId: activityId,
});
const handleBlockEditorFocus = () => {
setHotkeyScopeAndMemorizePreviousScope({
scope: ActivityEditorHotkeyScope.ActivityBody,
});
};
const recordTitleCellId = getRecordTitleCellId(
activityId,
labelIdentifierFieldMetadataItem?.id,
RecordTitleCellContainerType.ShowPage,
);
const handlerBlockEditorBlur = () => {
goBackToPreviousHotkeyScope();
};
const handleBlockEditorFocus = useRecoilCallback(
({ snapshot }) =>
() => {
const isRecordTitleCellOpen = snapshot
.getLoadable(isInlineCellInEditModeScopedState(recordTitleCellId))
.getValue();
if (isRecordTitleCellOpen) {
editor.domElement?.blur();
return;
}
pushFocusItemToFocusStack({
component: {
instanceId: activityId,
type: FocusComponentType.ACTIVITY_RICH_TEXT_EDITOR,
},
focusId: activityId,
hotkeyScope: {
scope: ActivityEditorHotkeyScope.ActivityBody,
},
});
},
[recordTitleCellId, activityId, editor, pushFocusItemToFocusStack],
);
const handlerBlockEditorBlur = useRecoilCallback(
({ snapshot }) =>
() => {
const isRecordTitleCellOpen = snapshot
.getLoadable(isInlineCellInEditModeScopedState(recordTitleCellId))
.getValue();
if (isRecordTitleCellOpen) {
return;
}
removeFocusItemFromFocusStackById({
focusId: activityId,
});
},
[activityId, recordTitleCellId, removeFocusItemFromFocusStackById],
);
return (
<>