From bcca5d00025930ab85331b3c03f07c505a40c344 Mon Sep 17 00:00:00 2001
From: BugIsGod <87571967+bugisthegod@users.noreply.github.com>
Date: Tue, 31 Mar 2026 11:07:19 +0100
Subject: [PATCH] fix: show disabled file chip when file no longer exists in
timeline (#19045)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixes: #18943
Follow-up pr: #19001
## Summary:
- Timeline activity shows file upload history, but deleted files had no
signed URL and were still rendered as clickable — clicking did nothing
- grab the fileId from properties.diff.after, look it up in the current
record's files field: if present, use live signed URL; if absent, mark
as deleted
- Deleted file chips show line-through label, not-allowed cursor, and
"File no longer exists" tooltip on hover
https://github.com/user-attachments/assets/5df6a675-0003-4fd1-ad57-a07e4338923f
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
---
.../components/EventFieldDiffValueEffect.tsx | 38 +++++++++++++++++--
.../record-field/ui/types/FieldMetadata.ts | 1 +
.../ui/field/display/components/FileChip.tsx | 33 ++++++++++------
.../twenty-ui/src/components/chip/Chip.tsx | 11 +++++-
.../tooltip/OverflowingTextWithTooltip.tsx | 6 ++-
5 files changed, 71 insertions(+), 18 deletions(-)
diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/rows/main-object/components/EventFieldDiffValueEffect.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/rows/main-object/components/EventFieldDiffValueEffect.tsx
index c2fb1bf523..a9e924da59 100644
--- a/packages/twenty-front/src/modules/activities/timeline-activities/rows/main-object/components/EventFieldDiffValueEffect.tsx
+++ b/packages/twenty-front/src/modules/activities/timeline-activities/rows/main-object/components/EventFieldDiffValueEffect.tsx
@@ -1,9 +1,13 @@
-import { useEffect } from 'react';
+import { useContext, useEffect } from 'react';
-import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
+import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
+import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
+import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
+import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
export const EventFieldDiffValueEffect = ({
@@ -22,13 +26,39 @@ export const EventFieldDiffValueEffect = ({
diffArtificialRecordStoreId,
);
+ const { recordId } = useContext(TimelineActivityContext);
+ const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
+
useEffect(() => {
if (!isDefined(diffRecord)) return;
+ let fieldValue = diffRecord;
+
+ if (
+ fieldMetadataItem.type === FieldMetadataType.FILES &&
+ isDefined(recordStore) &&
+ Array.isArray(diffRecord)
+ ) {
+ const currentFiles = Array.isArray(recordStore[fieldMetadataItem.name])
+ ? (recordStore[fieldMetadataItem.name] as FieldFilesValue[])
+ : [];
+ const currentFileMap = new Map(
+ currentFiles.map((file) => [file.fileId, file]),
+ );
+
+ fieldValue = (diffRecord as FieldFilesValue[]).map((file) => {
+ const currentFile = currentFileMap.get(file.fileId);
+ if (isDefined(currentFile)) {
+ return { ...file, url: currentFile.url };
+ }
+ return { ...file, isDeleted: true, url: undefined };
+ });
+ }
+
const forgedObjectRecord = {
__typename: mainObjectMetadataItem.nameSingular,
id: diffArtificialRecordStoreId,
- [fieldMetadataItem.name]: diffRecord,
+ [fieldMetadataItem.name]: fieldValue,
};
setRecordStore(forgedObjectRecord);
@@ -36,8 +66,10 @@ export const EventFieldDiffValueEffect = ({
diffRecord,
diffArtificialRecordStoreId,
fieldMetadataItem.name,
+ fieldMetadataItem.type,
mainObjectMetadataItem.nameSingular,
setRecordStore,
+ recordStore,
]);
return <>>;
diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/types/FieldMetadata.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/types/FieldMetadata.ts
index b51dabdf9b..bacd2a2b78 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/ui/types/FieldMetadata.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/ui/types/FieldMetadata.ts
@@ -335,4 +335,5 @@ export type FieldFilesValue = {
extension?: string;
url?: string;
fileCategory?: FileCategory;
+ isDeleted?: boolean;
};
diff --git a/packages/twenty-front/src/modules/ui/field/display/components/FileChip.tsx b/packages/twenty-front/src/modules/ui/field/display/components/FileChip.tsx
index 3e110798fb..e313e5ebb5 100644
--- a/packages/twenty-front/src/modules/ui/field/display/components/FileChip.tsx
+++ b/packages/twenty-front/src/modules/ui/field/display/components/FileChip.tsx
@@ -1,4 +1,5 @@
import { styled } from '@linaria/react';
+import { t } from '@lingui/core/macro';
import { FileIcon } from '@/file/components/FileIcon';
import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
@@ -24,7 +25,8 @@ export const FileChip = ({
onClick,
forceDisableClick,
}: FileChipProps) => {
- const isClickable = forceDisableClick !== true;
+ const isDeleted = file.isDeleted === true;
+ const isClickable = forceDisableClick !== true && !isDeleted;
const handleMouseDown = (event: React.MouseEvent): void => {
if (!isClickable) {
@@ -45,17 +47,24 @@ export const FileChip = ({
);
return (
-