Allow custom fields to be editable for system objects and prevent the error for timeline activity on system object pages. (#16268)

Solves #16015 

- Added a check in `useTimelineActivities.ts` to see if the system
object page we're viewing has timelineActivity being tracked before
querying to get the activity history.
- Removed @WorkspaceIsObjectUIReadOnly decorator from system objects and
added @WorkspaceIsFieldUIReadOnly to standard and system fields to allow
custom field edits as requested in the issue.
- Did not add calendarEvents or other system objects to timelineActivity
just yet since keeping track of timeline activity for every one of them
felt counter-intuitive and bloated. In order to determine which objects
need timelineActivity, I think we need to fix the broken views of system
objects first, such as the one in the attached screenshots - a good
number of them are broken. The check I added to
`useTimelineActivities.ts` hook displays timeline activity as empty for
the time - error would not be shown as suggested by Thomas.

<img width="1062" height="858" alt="image"
src="https://github.com/user-attachments/assets/e877e0fe-b665-46e3-b785-e84f2af7f833"
/>

<br />

<img width="1061" height="858" alt="image"
src="https://github.com/user-attachments/assets/0eba8c1c-444a-4b13-beda-64b95cf39077"
/>

- Editing custom fields on calendar events (and other objects without
position fields) crashed with TypeError: Cannot convert undefined or
null to object in sortCachedObjectEdges. This happened because some
cached queries had empty orderBy arrays ([]), and the optimistic effect
tried to sort with them. Objects without position fields returned
empty orderBy arrays when there were no sorts, while objects with
position fields automatically got [{ position: 'AscNullsFirst' }].
The backend always adds { id: 'AscNullsFirst' } as a fallback, but
the frontend didn't match this. So, I added { id: 'AscNullsFirst' } to
the Frontend as default orderBy when there are no sorts and no position
field.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Apply per-field UI read-only to system/standard fields and skip
timeline activity fetching when the target object isn’t related; refine
record-table cell open/navigation behavior.
> 
> - Server: Replace object-level UI read-only with per-field
`isUIReadOnly` across many standard objects (e.g., `calendarEvent`,
`workspaceMember`, messaging, calendar, favorites, attachments,
workflows, etc.), and mirror this via `@WorkspaceIsFieldUIReadOnly` on
workspace entities.
> - Frontend: In `useTimelineActivities.ts`, check object metadata for a
relation to `timelineActivity` and `skip` the query when absent,
preventing errors on system object pages.
> - Frontend: Simplify/adjust record-table cell logic—remove unused
args, allow navigation from first column when non-empty, block editing
for read-only records (while still allowing navigation), and update
button handlers/hover styles accordingly.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
dac1262d70. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Abdullah.
2025-12-22 22:22:30 +00:00
committed by GitHub
co-authored by Félix Malfait
parent 5003fc4196
commit c6ea7ae288
52 changed files with 465 additions and 77 deletions
@@ -50,6 +50,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
description: msg`Attachment name`,
icon: 'IconFileUpload',
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
name: string | null;
@@ -60,6 +61,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
description: msg`Attachment full path`,
icon: 'IconLink',
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
fullPath: string | null;
@@ -70,6 +72,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
description: msg`Attachment type (deprecated - use fileCategory)`,
icon: 'IconList',
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
@WorkspaceIsDeprecated()
type: string | null;
@@ -132,6 +135,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
],
defaultValue: "'OTHER'",
})
@WorkspaceIsFieldUIReadOnly()
fileCategory: string;
@WorkspaceField({
@@ -154,6 +158,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'authoredAttachments',
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
@WorkspaceIsDeprecated()
author: Relation<WorkspaceMemberWorkspaceEntity> | null;
@@ -171,6 +176,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
task: Relation<TaskWorkspaceEntity> | null;
@@ -187,6 +193,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
note: Relation<NoteWorkspaceEntity> | null;
@@ -203,6 +210,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
onDelete: RelationOnDeleteAction.CASCADE,
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
person: Relation<PersonWorkspaceEntity> | null;
@@ -219,6 +227,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
onDelete: RelationOnDeleteAction.CASCADE,
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
company: Relation<CompanyWorkspaceEntity> | null;
@@ -235,6 +244,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
onDelete: RelationOnDeleteAction.CASCADE,
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
opportunity: Relation<OpportunityWorkspaceEntity> | null;
@@ -251,6 +261,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
onDelete: RelationOnDeleteAction.CASCADE,
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
dashboard: Relation<DashboardWorkspaceEntity> | null;
@@ -267,6 +278,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
onDelete: RelationOnDeleteAction.CASCADE,
})
@WorkspaceIsFieldUIReadOnly()
@WorkspaceIsNullable()
workflow: Relation<WorkflowWorkspaceEntity> | null;