From 286bf030eef0cd949e0f0893933a3fca8d2de62b Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Tue, 9 Jun 2026 20:20:38 +0530 Subject: [PATCH 01/14] fix: prevent abrupt navigation when deleting notes from side panel in people records page - This commit introduces isInSidePanel flag into the HeadlessCommandContextApi, passing it down from the command menu click event. DestroyRecordsCommand now check this flagl if the deletion originates from a side panel, the app simply closes the panel and keeps the user on their current page. --- .../engine-command/hooks/useMountCommand.ts | 3 +++ .../record/components/DestroyRecordsCommand.tsx | 14 ++++++++++---- .../types/HeadlessCommandContextApi.ts | 1 + .../utils/buildHeadlessCommandContextApi.ts | 3 +++ .../hooks/useCommandMenuItemClick.ts | 1 + 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountCommand.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountCommand.ts index 08ff584e874e2..114e89667d676 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountCommand.ts +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountCommand.ts @@ -20,6 +20,7 @@ type MountCommandParams = { availabilityType?: CommandMenuItemAvailabilityType; availabilityObjectMetadataId?: string | null; payload?: CommandMenuItemPayload | null; + isInSidePanel?: boolean; }; export const useMountCommand = () => { @@ -39,12 +40,14 @@ export const useMountCommand = () => { availabilityType, availabilityObjectMetadataId, payload, + isInSidePanel, }: MountCommandParams) => { const headlessEngineCommandContextApi = buildHeadlessCommandContextApi({ store, contextStoreInstanceId, engineComponentKey, payload, + isInSidePanel, }); const commandState = isDefined(frontComponentId) diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx index 1fde0f7f98dfd..1b0523efefeed 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx @@ -4,13 +4,14 @@ import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryP import { useIncrementalDestroyManyRecords } from '@/object-record/hooks/useIncrementalDestroyManyRecords'; import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { t } from '@lingui/core/macro'; import { AppPath, type RecordGqlOperationFilter } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; export const DestroyRecordsCommand = () => { - const { recordIndexId, objectMetadataItem, selectedRecords, graphqlFilter } = + const { recordIndexId, objectMetadataItem, selectedRecords, graphqlFilter, isInSidePanel, } = useHeadlessCommandContextApi(); if (!isDefined(recordIndexId) || !isDefined(objectMetadataItem)) { @@ -22,6 +23,7 @@ export const DestroyRecordsCommand = () => { const isSingleRecord = selectedRecords.length === 1; const navigateApp = useNavigateApp(); + const { closeSidePanelMenu } = useSidePanelMenu(); const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId); const { removeSelectedRecordsFromRecordBoard } = @@ -56,9 +58,13 @@ export const DestroyRecordsCommand = () => { await incrementalDestroyManyRecords(); if (isSingleRecord) { - navigateApp(AppPath.RecordIndexPage, { - objectNamePlural: objectMetadataItem.namePlural, - }); + if (isInSidePanel) { + closeSidePanelMenu(); + } else { + navigateApp(AppPath.RecordIndexPage, { + objectNamePlural: objectMetadataItem.namePlural, + }); + } } }; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/types/HeadlessCommandContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/types/HeadlessCommandContextApi.ts index 68a383f301d90..e92e510a41f0e 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/types/HeadlessCommandContextApi.ts +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/types/HeadlessCommandContextApi.ts @@ -22,6 +22,7 @@ export type HeadlessEngineCommandContextApi = { selectedRecords: ObjectRecord[]; graphqlFilter: Nullable; payload: Nullable; + isInSidePanel?: boolean; }; export type HeadlessFrontComponentCommandContextApi = diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi.ts index fa57b59581c35..6d64a45f49a90 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi.ts +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi.ts @@ -25,11 +25,13 @@ export const buildHeadlessCommandContextApi = ({ contextStoreInstanceId, engineComponentKey, payload, + isInSidePanel, }: { store: Store; contextStoreInstanceId: string; engineComponentKey: EngineComponentKey; payload?: CommandMenuItemPayload | null; + isInSidePanel?: boolean; }): HeadlessEngineCommandContextApi => { const objectMetadataItemId = store.get( contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({ @@ -127,5 +129,6 @@ export const buildHeadlessCommandContextApi = ({ selectedRecords, graphqlFilter, payload: payload ?? null, + isInSidePanel: isInSidePanel ?? false, }; }; diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemClick.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemClick.ts index dfe85fa421a15..9bd6e9cbf3cbc 100644 --- a/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemClick.ts +++ b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemClick.ts @@ -81,6 +81,7 @@ export const useCommandMenuItemClick = ({ availabilityType: item.availabilityType, availabilityObjectMetadataId: item.availabilityObjectMetadataId, payload: item.payload ?? undefined, + isInSidePanel: commandMenuContextApi.isInSidePanel, }); return; From de17cee8abdb173bbf5951a56885767a58d3b42b Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 11:01:23 +0530 Subject: [PATCH 02/14] fix: instantly remove deleted relation chips from UI without refresh - Updated ObjectRecordOperation to include the specific destroyRecordIds in the browser event payload. - Introduced a global locallyDeletedRecordIdsAtom so the list of deleted records survives cell unmount/remounts during mouse hovers. - Updated RelationfromManyFieldDisplay to listen for destruction events, instantly log the destroyed IDs to the global state, and forcefully filter them out of the UI --- .../hooks/useDestroyOneRecord.ts | 3 +- .../hooks/useIncrementalDestroyManyRecords.ts | 5 +- .../RelationFromManyFieldDisplay.tsx | 99 ++++++++++++++++--- .../types/ObjectRecordOperation.ts | 10 +- 4 files changed, 99 insertions(+), 18 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts index 3d3a4bc311ba9..1d59ee518d1eb 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts @@ -2,6 +2,7 @@ import { useCallback } from 'react'; import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent'; import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; @@ -9,7 +10,6 @@ import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordF import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; -import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent'; import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField'; import { capitalize, isDefined } from 'twenty-shared/utils'; @@ -94,6 +94,7 @@ export const useDestroyOneRecord = ({ objectMetadataItem, operation: { type: 'destroy-one', + destroyedRecordId: idToDestroy, }, }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts index 6654c2160532b..3053969358b02 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts @@ -1,5 +1,6 @@ import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent'; import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; @@ -12,7 +13,6 @@ import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent'; import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { sleep } from '~/utils/sleep'; @@ -149,12 +149,14 @@ export const useIncrementalDestroyManyRecords = ({ const incrementalDestroyManyRecords = async () => { let totalDestroyedCount = 0; + const destroyedIdsOverall: string[] = []; await incrementalFetchAndMutate( async ({ recordIds, totalCount, abortSignal }) => { await destroyManyRecordsBatch(recordIds, abortSignal); totalDestroyedCount += recordIds.length; + destroyedIdsOverall.push(...recordIds); updateProgress(totalDestroyedCount, totalCount); }, @@ -168,6 +170,7 @@ export const useIncrementalDestroyManyRecords = ({ objectMetadataItem, operation: { type: 'destroy-many', + destroyedRecordIds: destroyedIdsOverall, }, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx index cdd8f1661ed4b..d5676c260610e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -1,10 +1,13 @@ -import { useContext } from 'react'; +import { atom, useAtom } from 'jotai'; +import { useContext, useEffect, useMemo, useState } from 'react'; + +import { useListenToObjectRecordOperationBrowserEvent } from '@/browser-event/hooks/useListenToObjectRecordOperationBrowserEvent'; +import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail'; import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords'; import { type NoteTarget } from '@/activities/types/NoteTarget'; import { type TaskTarget } from '@/activities/types/TaskTarget'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; -import { CoreObjectNameSingular } from 'twenty-shared/types'; import { RecordChip } from '@/object-record/components/RecordChip'; import { isActivityTargetField } from '@/object-record/record-field-list/utils/categorizeRelationFields'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -13,6 +16,7 @@ import { useRelationFromManyFieldDisplay } from '@/object-record/record-field/ui import { extractTargetRecordsFromJunction } from '@/object-record/record-field/ui/utils/junction/extractTargetRecordsFromJunction'; import { getJunctionConfig } from '@/object-record/record-field/ui/utils/junction/getJunctionConfig'; import { hasJunctionConfig } from '@/object-record/record-field/ui/utils/junction/hasJunctionConfig'; +import { CoreObjectNameSingular } from 'twenty-shared/types'; import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList'; import { styled } from '@linaria/react'; @@ -30,6 +34,8 @@ const StyledContainer = styled.div` width: 100%; `; +export const locallyDeletedRecordIdsAtom = atom([]); + export const RelationFromManyFieldDisplay = () => { const { fieldValue, fieldDefinition, generateRecordChipData } = useRelationFromManyFieldDisplay(); @@ -57,16 +63,85 @@ export const RelationFromManyFieldDisplay = () => { objectMetadataItems, }); + const [displayedFieldValue, setDisplayedFieldValue] = useState(fieldValue); + const [locallyDeletedIds, setLocallyDeletedIds] = useAtom(locallyDeletedRecordIdsAtom); + + useEffect(() => { + if(!fieldValue || !isArray(fieldValue)) { + setDisplayedFieldValue(fieldValue); + return; + } + + if(locallyDeletedIds.length == 0) { + setDisplayedFieldValue(fieldValue); + return; + } + + const filteredValues = fieldValue.filter((record) => { + if(locallyDeletedIds.includes(record.id)) return false; + + const hasMatchingForeignKey = Object.values(record).some((val) => + typeof val === 'string' && locallyDeletedIds.includes(val) + ) + if(hasMatchingForeignKey) return false; + + const hasDestroyedNestedRecord = Object.values(record).some( + val => + isDefined(val) && + typeof val === 'object' && + 'id' in val && + typeof val.id === 'string' && + locallyDeletedIds.includes(val.id) + ) + if(hasDestroyedNestedRecord) return false; + + return true; + }) + setDisplayedFieldValue(filteredValues) + }, [fieldValue, locallyDeletedIds]); + + const isRelationFromManyActivities = + (fieldName === 'noteTargets' && + objectMetadataNameSingular !== CoreObjectNameSingular.Note) || + (fieldName === 'taskTargets' && + objectMetadataNameSingular !== CoreObjectNameSingular.Task); + + const listenObjectMetadataId = useMemo(() => { + if( isRelationFromManyActivities) { + const targetName = + fieldName === 'noteTargets' + ? CoreObjectNameSingular.Note + : CoreObjectNameSingular.Task; + return objectMetadataItems.find((item) => item.nameSingular === targetName)?.id; + } + + return fieldDefinition.metadata.relationObjectMetadataId; + }, [isRelationFromManyActivities, fieldName, objectMetadataItems, fieldDefinition.metadata.relationObjectMetadataId]) + + useListenToObjectRecordOperationBrowserEvent({ + onObjectRecordOperationBrowserEvent: (detail: ObjectRecordOperationBrowserEventDetail) => { + if(detail.operation.type === 'destroy-many') { + const destroyedIds = detail.operation.destroyedRecordIds; + setLocallyDeletedIds((prevIds) => [...prevIds, ...destroyedIds]); + } + else if(detail.operation.type === 'destroy-one') { + const destroyedId = detail.operation.destroyedRecordId; + setLocallyDeletedIds((prevIds) => [...prevIds, destroyedId]); + } + }, + objectMetadataItemId: listenObjectMetadataId ?? '', + }) + const { activityTargetObjectRecords } = useActivityTargetObjectRecords( '', - fieldValue as NoteTarget[] | TaskTarget[], + displayedFieldValue as NoteTarget[] | TaskTarget[], ); - if (!isDefined(fieldValue)) { + if (!isDefined(displayedFieldValue)) { return null; } - if (!isArray(fieldValue)) { + if (!isArray(displayedFieldValue)) { return null; } @@ -79,12 +154,6 @@ export const RelationFromManyFieldDisplay = () => { objectMetadataNameSingular ?? '', ); - const isRelationFromManyActivities = - (fieldName === 'noteTargets' && - objectMetadataNameSingular !== CoreObjectNameSingular.Note) || - (fieldName === 'taskTargets' && - objectMetadataNameSingular !== CoreObjectNameSingular.Task); - if (isRelationFromManyActivities) { const objectNameSingular = fieldName === 'noteTargets' @@ -92,7 +161,7 @@ export const RelationFromManyFieldDisplay = () => { : CoreObjectNameSingular.Task; const relationFieldName = fieldName === 'noteTargets' ? 'note' : 'task'; - const chips = fieldValue + const chips = displayedFieldValue .map((record) => { if (!isDefined(record) || !isDefined(record[relationFieldName])) { return undefined; @@ -127,7 +196,7 @@ export const RelationFromManyFieldDisplay = () => { } const extractedRecords = extractTargetRecordsFromJunction({ - junctionRecords: fieldValue, + junctionRecords: displayedFieldValue, targetFields, objectMetadataItems, includeRecord: true, @@ -145,7 +214,7 @@ export const RelationFromManyFieldDisplay = () => { }) .filter(isDefined); - if (fieldValue.some(isDefined) && targetRecordsWithMetadata.length === 0) { + if (displayedFieldValue.some(isDefined) && targetRecordsWithMetadata.length === 0) { return null; } @@ -181,7 +250,7 @@ export const RelationFromManyFieldDisplay = () => { return ( - {fieldValue.filter(isDefined).map((record) => { + {displayedFieldValue.filter(isDefined).map((record) => { const recordChipData = generateRecordChipData(record); return ( Date: Wed, 10 Jun 2026 11:42:47 +0530 Subject: [PATCH 03/14] fix: linting issues --- .../components/DestroyRecordsCommand.tsx | 9 ++- .../hooks/useDestroyManyRecords.ts | 1 + .../RelationFromManyFieldDisplay.tsx | 65 +++++++++++-------- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx index 1b0523efefeed..9fe9da93ec90d 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx @@ -11,8 +11,13 @@ import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; export const DestroyRecordsCommand = () => { - const { recordIndexId, objectMetadataItem, selectedRecords, graphqlFilter, isInSidePanel, } = - useHeadlessCommandContextApi(); + const { + recordIndexId, + objectMetadataItem, + selectedRecords, + graphqlFilter, + isInSidePanel, + } = useHeadlessCommandContextApi(); if (!isDefined(recordIndexId) || !isDefined(objectMetadataItem)) { throw new Error( diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts index 759ab7f46851e..626734d5e7be6 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts @@ -153,6 +153,7 @@ export const useDestroyManyRecords = ({ objectMetadataItem, operation: { type: 'destroy-many', + destroyedRecordIds: recordIdsToDestroy, }, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx index d5676c260610e..fc8de0cea09c5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -64,40 +64,42 @@ export const RelationFromManyFieldDisplay = () => { }); const [displayedFieldValue, setDisplayedFieldValue] = useState(fieldValue); - const [locallyDeletedIds, setLocallyDeletedIds] = useAtom(locallyDeletedRecordIdsAtom); + const [locallyDeletedIds, setLocallyDeletedIds] = useAtom( + locallyDeletedRecordIdsAtom, + ); useEffect(() => { - if(!fieldValue || !isArray(fieldValue)) { + if (!fieldValue || !isArray(fieldValue)) { setDisplayedFieldValue(fieldValue); return; } - if(locallyDeletedIds.length == 0) { + if (locallyDeletedIds.length == 0) { setDisplayedFieldValue(fieldValue); return; } const filteredValues = fieldValue.filter((record) => { - if(locallyDeletedIds.includes(record.id)) return false; + if (locallyDeletedIds.includes(record.id)) return false; - const hasMatchingForeignKey = Object.values(record).some((val) => - typeof val === 'string' && locallyDeletedIds.includes(val) - ) - if(hasMatchingForeignKey) return false; + const hasMatchingForeignKey = Object.values(record).some( + (val) => typeof val === 'string' && locallyDeletedIds.includes(val), + ); + if (hasMatchingForeignKey) return false; const hasDestroyedNestedRecord = Object.values(record).some( - val => + (val) => isDefined(val) && - typeof val === 'object' && - 'id' in val && - typeof val.id === 'string' && - locallyDeletedIds.includes(val.id) - ) - if(hasDestroyedNestedRecord) return false; + typeof val === 'object' && + 'id' in val && + typeof val.id === 'string' && + locallyDeletedIds.includes(val.id), + ); + if (hasDestroyedNestedRecord) return false; return true; - }) - setDisplayedFieldValue(filteredValues) + }); + setDisplayedFieldValue(filteredValues); }, [fieldValue, locallyDeletedIds]); const isRelationFromManyActivities = @@ -107,30 +109,38 @@ export const RelationFromManyFieldDisplay = () => { objectMetadataNameSingular !== CoreObjectNameSingular.Task); const listenObjectMetadataId = useMemo(() => { - if( isRelationFromManyActivities) { + if (isRelationFromManyActivities) { const targetName = fieldName === 'noteTargets' ? CoreObjectNameSingular.Note : CoreObjectNameSingular.Task; - return objectMetadataItems.find((item) => item.nameSingular === targetName)?.id; + return objectMetadataItems.find( + (item) => item.nameSingular === targetName, + )?.id; } return fieldDefinition.metadata.relationObjectMetadataId; - }, [isRelationFromManyActivities, fieldName, objectMetadataItems, fieldDefinition.metadata.relationObjectMetadataId]) + }, [ + isRelationFromManyActivities, + fieldName, + objectMetadataItems, + fieldDefinition.metadata.relationObjectMetadataId, + ]); useListenToObjectRecordOperationBrowserEvent({ - onObjectRecordOperationBrowserEvent: (detail: ObjectRecordOperationBrowserEventDetail) => { - if(detail.operation.type === 'destroy-many') { + onObjectRecordOperationBrowserEvent: ( + detail: ObjectRecordOperationBrowserEventDetail, + ) => { + if (detail.operation.type === 'destroy-many') { const destroyedIds = detail.operation.destroyedRecordIds; setLocallyDeletedIds((prevIds) => [...prevIds, ...destroyedIds]); - } - else if(detail.operation.type === 'destroy-one') { + } else if (detail.operation.type === 'destroy-one') { const destroyedId = detail.operation.destroyedRecordId; setLocallyDeletedIds((prevIds) => [...prevIds, destroyedId]); } }, objectMetadataItemId: listenObjectMetadataId ?? '', - }) + }); const { activityTargetObjectRecords } = useActivityTargetObjectRecords( '', @@ -214,7 +224,10 @@ export const RelationFromManyFieldDisplay = () => { }) .filter(isDefined); - if (displayedFieldValue.some(isDefined) && targetRecordsWithMetadata.length === 0) { + if ( + displayedFieldValue.some(isDefined) && + targetRecordsWithMetadata.length === 0 + ) { return null; } From ae091490c1525cc110399e236f408a62797e9a43 Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 11:46:04 +0530 Subject: [PATCH 04/14] feat: add dependency updates and support for destroy operations in SSE database events --- ...eObjectRecordEventToObjectRecordOperationBrowserEvent.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/twenty-front/src/modules/sse-db-event/utils/turnSseObjectRecordEventToObjectRecordOperationBrowserEvent.ts b/packages/twenty-front/src/modules/sse-db-event/utils/turnSseObjectRecordEventToObjectRecordOperationBrowserEvent.ts index 8cd338e67a930..ebe2ae46a0de4 100644 --- a/packages/twenty-front/src/modules/sse-db-event/utils/turnSseObjectRecordEventToObjectRecordOperationBrowserEvent.ts +++ b/packages/twenty-front/src/modules/sse-db-event/utils/turnSseObjectRecordEventToObjectRecordOperationBrowserEvent.ts @@ -1,5 +1,5 @@ -import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem'; import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail'; +import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem'; import { getObjectRecordOperationUpdateInputs } from '@/sse-db-event/utils/getObjectRecordOperationUpdateInputs'; import { groupObjectRecordSseEventsByEventType } from '@/sse-db-event/utils/groupObjectRecordSseEventsByEventType'; import { assertUnreachable, isDefined } from 'twenty-shared/utils'; @@ -62,6 +62,7 @@ export const turnSseObjectRecordEventsToObjectRecordOperationBrowserEvents = ({ objectMetadataItem, operation: { type: 'destroy-one', + destroyedRecordId: objectRecordEventsForThisEventType[0].properties.before.id, }, }); } else { @@ -69,6 +70,9 @@ export const turnSseObjectRecordEventsToObjectRecordOperationBrowserEvents = ({ objectMetadataItem, operation: { type: 'destroy-many', + destroyedRecordIds: objectRecordEventsForThisEventType.map( + (event) => event.properties.before.id, + ), }, }); } From 645e5b4595803e93e06f4ca1f7738a296e833aed Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 11:56:41 +0530 Subject: [PATCH 05/14] fix: linting issue --- ...nSseObjectRecordEventToObjectRecordOperationBrowserEvent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/twenty-front/src/modules/sse-db-event/utils/turnSseObjectRecordEventToObjectRecordOperationBrowserEvent.ts b/packages/twenty-front/src/modules/sse-db-event/utils/turnSseObjectRecordEventToObjectRecordOperationBrowserEvent.ts index ebe2ae46a0de4..cbcaaf21f80cc 100644 --- a/packages/twenty-front/src/modules/sse-db-event/utils/turnSseObjectRecordEventToObjectRecordOperationBrowserEvent.ts +++ b/packages/twenty-front/src/modules/sse-db-event/utils/turnSseObjectRecordEventToObjectRecordOperationBrowserEvent.ts @@ -62,7 +62,8 @@ export const turnSseObjectRecordEventsToObjectRecordOperationBrowserEvents = ({ objectMetadataItem, operation: { type: 'destroy-one', - destroyedRecordId: objectRecordEventsForThisEventType[0].properties.before.id, + destroyedRecordId: + objectRecordEventsForThisEventType[0].properties.before.id, }, }); } else { From ebbabca1d4e56f610b56d6cbc50e603b9c00027e Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 12:05:23 +0530 Subject: [PATCH 06/14] refactor(ui): scope locally deleted records state to individual cells using atomFamily Previously, the `locallyDeletedRecordIdsAtom` (used to instantly hide destroyed chips in `RelationFromManyFieldDisplay`) was defined as a single global array. This raised architectural concerns as it could create a small memory leak over long sessions (an ever-growing array) and caused all table cells across the application to share and iterate over the same list of destroyed IDs. This commit refactors the state to use Jotai's `atomFamily`, keying the state dynamically by `${recordId}-${fieldName}`. This ensures the fallback state remains cleanly isolated per-cell while still surviving component unmounts/remounts during mouse hovers. --- .../display/components/RelationFromManyFieldDisplay.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx index fc8de0cea09c5..2d2821747fb24 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -1,4 +1,5 @@ import { atom, useAtom } from 'jotai'; +import { atomFamily } from 'jotai/utils'; import { useContext, useEffect, useMemo, useState } from 'react'; import { useListenToObjectRecordOperationBrowserEvent } from '@/browser-event/hooks/useListenToObjectRecordOperationBrowserEvent'; @@ -34,13 +35,13 @@ const StyledContainer = styled.div` width: 100%; `; -export const locallyDeletedRecordIdsAtom = atom([]); +export const locallyDeletedRecordIdsAtomFamily = atomFamily((_key: string) => atom([])); export const RelationFromManyFieldDisplay = () => { const { fieldValue, fieldDefinition, generateRecordChipData } = useRelationFromManyFieldDisplay(); const { isFocused } = useFieldFocus(); - const { disableChipClick, triggerEvent } = useContext(FieldContext); + const { disableChipClick, triggerEvent, recordId } = useContext(FieldContext); const { objectMetadataItems } = useObjectMetadataItems(); const { fieldName, objectMetadataNameSingular } = fieldDefinition.metadata; @@ -64,8 +65,9 @@ export const RelationFromManyFieldDisplay = () => { }); const [displayedFieldValue, setDisplayedFieldValue] = useState(fieldValue); + const cellKey = `${recordId}-${fieldName}`; const [locallyDeletedIds, setLocallyDeletedIds] = useAtom( - locallyDeletedRecordIdsAtom, + locallyDeletedRecordIdsAtomFamily(cellKey), ); useEffect(() => { From db8e6c7279f48d0f6b2e2d92f882cf1198831d50 Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 12:06:24 +0530 Subject: [PATCH 07/14] fix: objectMetadataItemId typing in RelationFromManyFieldDisplay --- .../display/components/RelationFromManyFieldDisplay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx index 2d2821747fb24..d592c8edb6226 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -141,7 +141,7 @@ export const RelationFromManyFieldDisplay = () => { setLocallyDeletedIds((prevIds) => [...prevIds, destroyedId]); } }, - objectMetadataItemId: listenObjectMetadataId ?? '', + objectMetadataItemId: listenObjectMetadataId, }); const { activityTargetObjectRecords } = useActivityTargetObjectRecords( From 5772242df515c5bdb88a85e2a72458ed7bb0e4c8 Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 12:07:51 +0530 Subject: [PATCH 08/14] refactor(ui): optimize relation field event listener and state updates This commit resolves PR feedback regarding the event-driven fallback in RelationFromManyFieldDisplay: - Removed the empty string fallback (`?? ''`) for `objectMetadataItemId` to safely pass `undefined` and prevent the listener from subscribing to invalid keys. - Enforced strict equality (`===`) for the locally deleted IDs length check. - Wrapped state updates in `Array.from(new Set(...))` to guarantee ID uniqueness, ensuring fast lookups and preventing array bloat from duplicate destroy events. --- .../display/components/RelationFromManyFieldDisplay.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx index d592c8edb6226..f302e87e3d3dd 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -76,7 +76,7 @@ export const RelationFromManyFieldDisplay = () => { return; } - if (locallyDeletedIds.length == 0) { + if (locallyDeletedIds.length === 0) { setDisplayedFieldValue(fieldValue); return; } @@ -135,10 +135,10 @@ export const RelationFromManyFieldDisplay = () => { ) => { if (detail.operation.type === 'destroy-many') { const destroyedIds = detail.operation.destroyedRecordIds; - setLocallyDeletedIds((prevIds) => [...prevIds, ...destroyedIds]); + setLocallyDeletedIds((prevIds) => Array.from(new Set([...prevIds, ...destroyedIds]))); } else if (detail.operation.type === 'destroy-one') { const destroyedId = detail.operation.destroyedRecordId; - setLocallyDeletedIds((prevIds) => [...prevIds, destroyedId]); + setLocallyDeletedIds((prevIds) => Array.from(new Set([...prevIds, destroyedId]))); } }, objectMetadataItemId: listenObjectMetadataId, From 29bef93ab2be5d52821d927b87678b28e988c3dc Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 12:11:38 +0530 Subject: [PATCH 09/14] refactor(records): dispatch destroy browser events per-batch to improve performance During bulk deletions, `useIncrementalDestroyManyRecords` was accumulating all destroyed IDs in memory and waiting until the entire operation finished before broadcasting the UI event. For massive deletions, this caused unnecessary memory bloat and delayed UI responsiveness. This commit removes the accumulation array and moves the event dispatch inside the `incrementalFetchAndMutate` loop. Destroy events are now broadcast progressively per-batch, bounding memory usage and ensuring the UI updates incrementally. --- .../hooks/useIncrementalDestroyManyRecords.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts index 3053969358b02..60f0d238c4f86 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts @@ -149,14 +149,20 @@ export const useIncrementalDestroyManyRecords = ({ const incrementalDestroyManyRecords = async () => { let totalDestroyedCount = 0; - const destroyedIdsOverall: string[] = []; await incrementalFetchAndMutate( async ({ recordIds, totalCount, abortSignal }) => { await destroyManyRecordsBatch(recordIds, abortSignal); totalDestroyedCount += recordIds.length; - destroyedIdsOverall.push(...recordIds); + + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'destroy-many', + destroyedRecordIds: recordIds, + }, + }); updateProgress(totalDestroyedCount, totalCount); }, @@ -166,14 +172,6 @@ export const useIncrementalDestroyManyRecords = ({ objectMetadataNamePlural: objectMetadataItem.namePlural, }); - dispatchObjectRecordOperationBrowserEvent({ - objectMetadataItem, - operation: { - type: 'destroy-many', - destroyedRecordIds: destroyedIdsOverall, - }, - }); - return totalDestroyedCount; }; From ea4d914fb922a62aa46dc1b9d7399438c45faf4e Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 12:15:46 +0530 Subject: [PATCH 10/14] fix: use actual destroyed record IDs in hook --- .../modules/object-record/hooks/useDestroyManyRecords.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts index 626734d5e7be6..095f5243b0115 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts @@ -147,13 +147,17 @@ export const useDestroyManyRecords = ({ objectMetadataNamePlural: objectMetadataItem.namePlural, }); - removeNavigationMenuItemsByTargetRecordIds(recordIdsToDestroy); + const actualDestroyedRecordIds = destroyedRecords.map( + (record) => record.id, + ); + + removeNavigationMenuItemsByTargetRecordIds(actualDestroyedRecordIds); dispatchObjectRecordOperationBrowserEvent({ objectMetadataItem, operation: { type: 'destroy-many', - destroyedRecordIds: recordIdsToDestroy, + destroyedRecordIds: actualDestroyedRecordIds, }, }); From c72fc339721a954bed549645ecc00e582f57bfc9 Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 12:17:36 +0530 Subject: [PATCH 11/14] fix: update incrementalDestroyManyRecords to return actual destroyed IDs --- .../hooks/useIncrementalDestroyManyRecords.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts index 60f0d238c4f86..0b025f607ce03 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts @@ -78,6 +78,8 @@ export const useIncrementalDestroyManyRecords = ({ recordIdsToDestroy.length / mutationPageSize, ); + const actualDestroyedRecordIds: string[] = []; + for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) { const batchedIdsToDestroy = recordIdsToDestroy.slice( batchIndex * mutationPageSize, @@ -88,7 +90,7 @@ export const useIncrementalDestroyManyRecords = ({ .map((recordId) => getRecordFromCache(recordId, apolloCoreClient.cache)) .filter(isDefined); - await apolloCoreClient + const response = await apolloCoreClient .mutate>({ mutation: destroyManyRecordsMutation, variables: { @@ -141,10 +143,15 @@ export const useIncrementalDestroyManyRecords = ({ throw error; }); + const destroyedRecordsForThisBatch = response.data?.[mutationResponseField] ?? []; + actualDestroyedRecordIds.push(...destroyedRecordsForThisBatch.map(r => r.id)); + if (delayInMsBetweenMutations > 0) { await sleep(delayInMsBetweenMutations); } } + + return actualDestroyedRecordIds; }; const incrementalDestroyManyRecords = async () => { @@ -152,15 +159,15 @@ export const useIncrementalDestroyManyRecords = ({ await incrementalFetchAndMutate( async ({ recordIds, totalCount, abortSignal }) => { - await destroyManyRecordsBatch(recordIds, abortSignal); + const actualDestroyedRecordIds = await destroyManyRecordsBatch(recordIds, abortSignal); - totalDestroyedCount += recordIds.length; + totalDestroyedCount += actualDestroyedRecordIds.length; dispatchObjectRecordOperationBrowserEvent({ objectMetadataItem, operation: { type: 'destroy-many', - destroyedRecordIds: recordIds, + destroyedRecordIds: actualDestroyedRecordIds, }, }); From 20955237be38530b7670255978bb580da8dbfca7 Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 12:18:38 +0530 Subject: [PATCH 12/14] fix: refine record filtering logic --- .../display/components/RelationFromManyFieldDisplay.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx index f302e87e3d3dd..326962e82668a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -84,8 +84,11 @@ export const RelationFromManyFieldDisplay = () => { const filteredValues = fieldValue.filter((record) => { if (locallyDeletedIds.includes(record.id)) return false; - const hasMatchingForeignKey = Object.values(record).some( - (val) => typeof val === 'string' && locallyDeletedIds.includes(val), + const hasMatchingForeignKey = Object.entries(record).some( + ([key, val]) => + key.endsWith('Id') && + typeof val === 'string' && + locallyDeletedIds.includes(val), ); if (hasMatchingForeignKey) return false; From 43316e4bde6e03e8af9d58defd32d264a44e096a Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 12:19:38 +0530 Subject: [PATCH 13/14] fix: handle undefined values in RelationFromManyFieldDisplay --- .../display/components/RelationFromManyFieldDisplay.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx index 326962e82668a..0dfb2ad10be41 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -82,6 +82,8 @@ export const RelationFromManyFieldDisplay = () => { } const filteredValues = fieldValue.filter((record) => { + if (!isDefined(record)) return true; + if (locallyDeletedIds.includes(record.id)) return false; const hasMatchingForeignKey = Object.entries(record).some( From eb43a578f2c2f60c96fba15bedecb6b747e576d5 Mon Sep 17 00:00:00 2001 From: DeviSriSaiCharan Date: Wed, 10 Jun 2026 12:22:03 +0530 Subject: [PATCH 14/14] fix: linting issues --- .../hooks/useIncrementalDestroyManyRecords.ts | 12 +++++++++--- .../components/RelationFromManyFieldDisplay.tsx | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts index 0b025f607ce03..dc5587632a301 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts @@ -143,8 +143,11 @@ export const useIncrementalDestroyManyRecords = ({ throw error; }); - const destroyedRecordsForThisBatch = response.data?.[mutationResponseField] ?? []; - actualDestroyedRecordIds.push(...destroyedRecordsForThisBatch.map(r => r.id)); + const destroyedRecordsForThisBatch = + response.data?.[mutationResponseField] ?? []; + actualDestroyedRecordIds.push( + ...destroyedRecordsForThisBatch.map((r) => r.id), + ); if (delayInMsBetweenMutations > 0) { await sleep(delayInMsBetweenMutations); @@ -159,7 +162,10 @@ export const useIncrementalDestroyManyRecords = ({ await incrementalFetchAndMutate( async ({ recordIds, totalCount, abortSignal }) => { - const actualDestroyedRecordIds = await destroyManyRecordsBatch(recordIds, abortSignal); + const actualDestroyedRecordIds = await destroyManyRecordsBatch( + recordIds, + abortSignal, + ); totalDestroyedCount += actualDestroyedRecordIds.length; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx index 0dfb2ad10be41..17232fd57d529 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -35,7 +35,9 @@ const StyledContainer = styled.div` width: 100%; `; -export const locallyDeletedRecordIdsAtomFamily = atomFamily((_key: string) => atom([])); +export const locallyDeletedRecordIdsAtomFamily = atomFamily((_key: string) => + atom([]), +); export const RelationFromManyFieldDisplay = () => { const { fieldValue, fieldDefinition, generateRecordChipData } = @@ -140,10 +142,14 @@ export const RelationFromManyFieldDisplay = () => { ) => { if (detail.operation.type === 'destroy-many') { const destroyedIds = detail.operation.destroyedRecordIds; - setLocallyDeletedIds((prevIds) => Array.from(new Set([...prevIds, ...destroyedIds]))); + setLocallyDeletedIds((prevIds) => + Array.from(new Set([...prevIds, ...destroyedIds])), + ); } else if (detail.operation.type === 'destroy-one') { const destroyedId = detail.operation.destroyedRecordId; - setLocallyDeletedIds((prevIds) => Array.from(new Set([...prevIds, destroyedId]))); + setLocallyDeletedIds((prevIds) => + Array.from(new Set([...prevIds, destroyedId])), + ); } }, objectMetadataItemId: listenObjectMetadataId,