Skip to content

Enable editing for calendar event custom fields#17063

Merged
charlesBochet merged 8 commits intotwentyhq:mainfrom
salmonumbrella:feat/calendar-event-custom-field-edit
Jan 12, 2026
Merged

Enable editing for calendar event custom fields#17063
charlesBochet merged 8 commits intotwentyhq:mainfrom
salmonumbrella:feat/calendar-event-custom-field-edit

Conversation

@salmonumbrella
Copy link
Copy Markdown
Contributor

Summary

  • Enable editing for custom calendar event fields while keeping standard fields read-only
  • Load calendar event fields dynamically so custom field values are editable everywhere they appear
  • Preserve calendar event participants rendering

Testing

  • npx nx run twenty-front:lint:diff-with-main --skip-nx-cache --output-style=stream
  • npx jest --config packages/twenty-front/jest.config.mjs --runTestsByPath packages/twenty-front/src/modules/activities/calendar/hooks/tests/useCalendarEvents.test.tsx

Notes

  • Full nx lint/test are very slow locally after barrel generation; CI should run the full suite

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 9, 2026

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:18291

This environment will automatically shut down when the PR is closed or after 5 hours.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx">

<violation number="1" location="packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx:136">
P2: The `loading` state is hardcoded to `false`, so the UI won't show loading feedback during updates. Consider using the actual loading state from `useUpdateOneRecord` if the mutation provides one, or document why loading state is intentionally disabled for calendar events.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

});
};

return [updateEntity, { loading: false }];
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The loading state is hardcoded to false, so the UI won't show loading feedback during updates. Consider using the actual loading state from useUpdateOneRecord if the mutation provides one, or document why loading state is intentionally disabled for calendar events.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx, line 136:

<comment>The `loading` state is hardcoded to `false`, so the UI won't show loading feedback during updates. Consider using the actual loading state from `useUpdateOneRecord` if the mutation provides one, or document why loading state is intentionally disabled for calendar events.</comment>

<file context>
@@ -113,11 +121,43 @@ export const CalendarEventDetails = ({
+      });
+    };
+
+    return [updateEntity, { loading: false }];
+  };
+
</file context>

✅ Addressed in fa40a7c

@salmonumbrella
Copy link
Copy Markdown
Contributor Author

Updated the calendar event update hook to track the async update and surface the loading state instead of hardcoding false.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx">

<violation number="1" location="packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx:130">
P1: Using `useState` inside a function defined within a component body violates React's Rules of Hooks. The function `useUpdateOneCalendarEventRecordMutation` is recreated on every render and contains a `useState` call that executes when child components invoke it. Consider moving the loading state to the component level or using `useCallback` with state managed at the parent level.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

});

const useUpdateOneCalendarEventRecordMutation: RecordUpdateHook = () => {
const [isUpdating, setIsUpdating] = useState(false);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Using useState inside a function defined within a component body violates React's Rules of Hooks. The function useUpdateOneCalendarEventRecordMutation is recreated on every render and contains a useState call that executes when child components invoke it. Consider moving the loading state to the component level or using useCallback with state managed at the parent level.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx, line 130:

<comment>Using `useState` inside a function defined within a component body violates React's Rules of Hooks. The function `useUpdateOneCalendarEventRecordMutation` is recreated on every render and contains a `useState` call that executes when child components invoke it. Consider moving the loading state to the component level or using `useCallback` with state managed at the parent level.</comment>

<file context>
@@ -126,14 +127,19 @@ export const CalendarEventDetails = ({
   });
 
   const useUpdateOneCalendarEventRecordMutation: RecordUpdateHook = () => {
+    const [isUpdating, setIsUpdating] = useState(false);
+
     const updateEntity = ({ variables }: RecordUpdateHookParams) => {
</file context>

✅ Addressed in 1695b3c

@salmonumbrella
Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai moved the loading state to the component level and now return it via a memoized update hook, so no hooks are called inside the nested function.

@charlesBochet
Copy link
Copy Markdown
Member

@salmonumbrella Thanks for opening a PR, much appreciated

However, I'm not sure what we are trying to do here. Is there any issue describing the change?
First look, I would say that we don't want users to modify calendarEvents

@charlesBochet
Copy link
Copy Markdown
Member

I'm going to close the PR for now but let's keep discussing here and you can also re-open it by commenting /twenty pr open

@salmonumbrella
Copy link
Copy Markdown
Contributor Author

salmonumbrella commented Jan 10, 2026

@charlesBochet Thanks for the quick review!

To clarify the use case: I created a custom field (via the API) that links to a Notion meeting page for a calendar event. This custom field lives entirely within Twenty and is separate from the synced Google Calendar data.

The issue is that I'm unable to edit the URL for this custom field on the frontend. Since custom fields don't touch the actual Google Calendar variables, there's no risk of data corruption or sync conflicts—it's purely Twenty-side metadata.

This PR enables editing only for custom fields while keeping the standard calendar fields (title, dates, participants, etc.) read-only as intended.

Example of frontend UI:
https://cln.sh/647ctNkwnqjxPHSsTPKM

@salmonumbrella
Copy link
Copy Markdown
Contributor Author

/twenty pr open

@twenty-eng-sync twenty-eng-sync bot reopened this Jan 10, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

const [isUpdating, setIsUpdating] = useState(false);
const updateEntity = useCallback(
({ variables }: RecordUpdateHookParams) => {
if (!updateOneRecord) return;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this check? updateOneRecord cannot be undefined

objectRecordId: viewableRecordId ?? '',
recordGqlFields: FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE.fields,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE use elsewhere? If not we should remove it, if yes, there is likely a regression as signatures are here to make sure queries are consistent while making cache udpates

@salmonumbrella
Copy link
Copy Markdown
Contributor Author

Thanks @charlesBochet! Addressed the notes: removed the updateOneRecord guard + standardFieldNames Set, switched to the shared read-only helper, dropped the unnecessary memoization, and removed the unused operation signature. Pushed in 346c2d5.

[updateOneRecord],
);

const useUpdateOneCalendarEventRecordMutation: RecordUpdateHook = useCallback(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this?

objectMetadataId: objectMetadataItem.id,
});

const isFieldReadOnly = (fieldMetadataItem: FieldMetadataItem) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this method?

Copy link
Copy Markdown
Member

@charlesBochet charlesBochet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a last round of comment, please re-test and then we are good to go, ty!

@charlesBochet
Copy link
Copy Markdown
Member

Please also fix the lint :)

@salmonumbrella
Copy link
Copy Markdown
Contributor Author

@charlesBochet fixed lint by making the boolean predicate explicit in the workflow story (commit 6c6b9d5). Ran successfully (only warnings about ignored generated files).

@salmonumbrella
Copy link
Copy Markdown
Contributor Author

@charlesBochet (follow-up) lint now clean on diff: ran nx lint:diff-with-main successfully; only warnings are for ignored generated files.

@charlesBochet
Copy link
Copy Markdown
Member

@salmonumbrella I've left two comments (still not resolved) above too

@salmonumbrella
Copy link
Copy Markdown
Contributor Author

@charlesBochet addressed the two latest comments in CalendarEventDetails: removed the memoized wrapper for useUpdateRecord and inlined the read-only helper (commit 7153a3d).

@charlesBochet
Copy link
Copy Markdown
Member

Thank you!

@charlesBochet charlesBochet added this pull request to the merge queue Jan 12, 2026
Merged via the queue into twentyhq:main with commit 94e5d93 Jan 12, 2026
63 checks passed
@twenty-eng-sync
Copy link
Copy Markdown

Hey @charlesBochet! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

@github-actions
Copy link
Copy Markdown
Contributor

Fails
🚫

node failed.

Log

Details
�[31mError: �[39m RequestError [HttpError]: Validation Failed: {"message":"The listed users cannot be searched either because the users do not exist or you do not have permission to view the users.","resource":"Search","field":"q","code":"invalid"} - https://docs.github.com/v3/search/
    at /home/runner/work/twenty/twenty/node_modules/�[4m@octokit/request�[24m/dist-node/index.js:125:21
�[90m    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)�[39m {
  status: �[33m422�[39m,
  response: {
    url: �[32m'https://api.github.com/search/issues?q=is%3Apr%20author%3Asalmonumbrella%20is%3Aclosed%20repo%3Atwentyhq%2Ftwenty&per_page=2&page=1'�[39m,
    status: �[33m422�[39m,
    headers: {
      �[32m'access-control-allow-origin'�[39m: �[32m'*'�[39m,
      �[32m'access-control-expose-headers'�[39m: �[32m'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'�[39m,
      �[32m'cache-control'�[39m: �[32m'no-cache'�[39m,
      �[32m'content-length'�[39m: �[32m'300'�[39m,
      �[32m'content-security-policy'�[39m: �[32m"default-src 'none'"�[39m,
      �[32m'content-type'�[39m: �[32m'application/json; charset=utf-8'�[39m,
      date: �[32m'Mon, 12 Jan 2026 10:33:24 GMT'�[39m,
      �[32m'referrer-policy'�[39m: �[32m'origin-when-cross-origin, strict-origin-when-cross-origin'�[39m,
      server: �[32m'github.com'�[39m,
      �[32m'strict-transport-security'�[39m: �[32m'max-age=31536000; includeSubdomains; preload'�[39m,
      vary: �[32m'Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With'�[39m,
      �[32m'x-accepted-github-permissions'�[39m: �[32m'allows_permissionless_access=true'�[39m,
      �[32m'x-content-type-options'�[39m: �[32m'nosniff'�[39m,
      �[32m'x-frame-options'�[39m: �[32m'deny'�[39m,
      �[32m'x-github-api-version-selected'�[39m: �[32m'2022-11-28'�[39m,
      �[32m'x-github-media-type'�[39m: �[32m'github.v3; format=json'�[39m,
      �[32m'x-github-request-id'�[39m: �[32m'0819:1444B9:3927210:FC0B2DB:6964CDF3'�[39m,
      �[32m'x-ratelimit-limit'�[39m: �[32m'30'�[39m,
      �[32m'x-ratelimit-remaining'�[39m: �[32m'30'�[39m,
      �[32m'x-ratelimit-reset'�[39m: �[32m'1768214064'�[39m,
      �[32m'x-ratelimit-resource'�[39m: �[32m'search'�[39m,
      �[32m'x-ratelimit-used'�[39m: �[32m'1'�[39m,
      �[32m'x-xss-protection'�[39m: �[32m'0'�[39m
    },
    data: {
      message: �[32m'Validation Failed'�[39m,
      errors: �[36m[Array]�[39m,
      documentation_url: �[32m'https://docs.github.com/v3/search/'�[39m,
      status: �[32m'422'�[39m
    }
  },
  request: {
    method: �[32m'GET'�[39m,
    url: �[32m'https://api.github.com/search/issues?q=is%3Apr%20author%3Asalmonumbrella%20is%3Aclosed%20repo%3Atwentyhq%2Ftwenty&per_page=2&page=1'�[39m,
    headers: {
      accept: �[32m'application/vnd.github.v3+json'�[39m,
      �[32m'user-agent'�[39m: �[32m'octokit-rest.js/20.1.2 octokit-core.js/5.2.2 Node.js/24'�[39m,
      authorization: �[32m'token [REDACTED]'�[39m
    },
    request: { hook: �[36m[Function: bound bound register]�[39m }
  }
}
danger-results://tmp/danger-results-6122a8c0.json

Generated by 🚫 dangerJS against 7153a3d

@charlesBochet
Copy link
Copy Markdown
Member

@salmonumbrella Just tried on our pre-prod environment, and I can't edit a textField on calendarEvent, could you check?
image

@salmonumbrella
Copy link
Copy Markdown
Contributor Author

salmonumbrella commented Jan 26, 2026

I created a custom field called "Notion" on the CalendarEvent object via the API. The field appears in the Calendar Event details panel, but it's not editable - it shows as read-only like the standard fields.

Expected: Custom fields (where isCustom: true and isUIReadOnly: false) should be editable.
Actual: The custom field appears but cannot be edited.

Custom field not editable [screenshot]

Possible causes I investigated:

  • The isRecordFieldReadOnly logic looks correct - it checks fieldMetadataItem.isUIReadOnly ?? false
  • The customFields filter correctly identifies field.isCustom fields
  • Unclear if the field was created with isUIReadOnly: true by default, or if there's an object-level permission issue

@charlesBochet Could you help investigate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants