Skip to content

Comments

feat(event-types): add team members as optional guests#22651

Draft
husniabad wants to merge 4 commits intocalcom:mainfrom
husniabad:feature/add-team-member-as-optional-guest
Draft

feat(event-types): add team members as optional guests#22651
husniabad wants to merge 4 commits intocalcom:mainfrom
husniabad:feature/add-team-member-as-optional-guest

Conversation

@husniabad
Copy link
Contributor

@husniabad husniabad commented Jul 20, 2025

Adds a setting to event types to automatically invite selected team members as optional attendees to meetings. This does not check their availability. Closes CAL-5091

What does this PR do?

Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).

Video Demo (if applicable):

  • Show screen recordings of the issue or feature.
  • Demonstrate how to reproduce the issue, the behavior before and after the change.
#### Image Demo (if applicable):
  • Add side-by-side screenshots of the original and updated change.
  • Highlight any significant change(s).
image image

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

@husniabad husniabad requested review from a team July 20, 2025 16:10
@husniabad husniabad requested a review from a team as a code owner July 20, 2025 16:10
@vercel
Copy link

vercel bot commented Jul 20, 2025

@husniabad is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link

CLAassistant commented Jul 20, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 20, 2025

Walkthrough

Adds support for selecting team members as optional guests and propagates that selection across DB schema, API, UI, booking flow, and calendar adapters. Introduces a Prisma many-to-many relation and migration, extends TRPC input/handler and repository projections to accept/persist optionalGuestTeamMembers, adds UI controls (OptionalTeamGuestSetting) and localization, updates form/defaults and CalendarEventBuilder to include optional guests, and updates multiple calendar services to add optional attendees and avoid duplicating them as required.

Assessment against linked issues

Objective Addressed Explanation
Limit selection to team members and display UpgradeTeamsBadge ([CAL-5091], #18947) UI restricts selection to team.members but no UpgradeTeamsBadge rendering was added.
Do not check team members’ calendars for conflicts; only invite them ([CAL-5091], #18947) Optional guests are flagged/added as optional attendees in adapters, but no explicit change to conflict/free-busy checking logic was found.
Mark invited team members as optional where possible ([CAL-5091], #18947)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Host assignment logic refactor (packages/trpc/server/routers/viewer/eventTypes/update.handler.ts) Host processing and diff/create/update/delete host logic changes are unrelated to optional guest feature.
Provider event type string changes (packages/app-store/exchange2013calendar/lib/CalendarService.ts, packages/app-store/exchange2016calendar/lib/CalendarService.ts, packages/app-store/exchangecalendar/lib/CalendarService.ts) Changing returned event type metadata is not part of optional-guest requirements.
Switch to Prisma.validator for select typing (packages/trpc/server/routers/viewer/eventTypes/update.handler.ts) Type-safety refactor for select payload is unrelated to optional-guest functionality.
Test assertion tolerance changes in Google calendar tests (packages/app-store/googlecalendar/lib/tests/CalendarService.test.ts) Adjusting test tolerance and messages for date chunking is unrelated to optional-guest feature.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b6c02ee and 1c59531.

📒 Files selected for processing (1)
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Jul 20, 2025
@graphite-app graphite-app bot requested a review from a team July 20, 2025 16:10
@github-actions github-actions bot added $25 community Created by Linear-GitHub Sync teams area: teams, round robin, collective, managed event-types ✨ feature New feature or request ❗️ migrations contains migration files labels Jul 20, 2025
@dosubot dosubot bot added the event-types area: event types, event-types label Jul 20, 2025
@dosubot dosubot bot added this to the Community Only milestone Jul 20, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
apps/web/public/static/locales/en/common.json (1)

1497-1498: Grammar & clarity – adjust pluralization and streamline the description
The current copy mixes singular (“an optional guest”) with plural (“team members”) and is a bit verbose. Refining it improves readability and remains consistent with neighbouring strings.

-  "optional_guest_team_members": "Add team members as optional guests",
-  "optional_guest_team_members_description": "Adding team members as an optional guest will always send an optional invite, but not check their availability.",
+  "optional_guest_team_members": "Add team members as optional guests",
+  "optional_guest_team_members_description": "Selected team members are invited as optional guests without checking their availability.",
packages/features/eventtypes/components/tabs/advanced/OptionalTeamGuestSetting.tsx (3)

26-28: Consider removing redundant useEffect.

The useEffect updating isOptionalTeamGuestEnabled appears redundant since the initial state already derives from eventType.optionalGuestTeamMembers.length > 0. The component will re-render when props change, making this effect unnecessary.

-  useEffect(() => {
-    setIsOptionalTeamGuestEnabled(eventType.optionalGuestTeamMembers.length > 0);
-  }, [eventType.optionalGuestTeamMembers]);

42-42: Ensure consistent email fallback values.

The email fallback values are inconsistent:

  • Line 42: "No Email"
  • Line 89: ""

Use consistent fallback values to prevent display inconsistencies.

-          label: option.user.email || "No Email",
+          label: option.user.email || "",

Also applies to: 89-89


82-83: Remove unnecessary null check for onChange.

The onChange function is provided by react-hook-form's Controller and will always be defined. The null check is unnecessary.

-                  if (!onChange) return;
                   onChange(options.map((option) => ({ id: parseInt(option.value) })));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb4260c and 2f2e97f.

📒 Files selected for processing (22)
  • apps/web/public/static/locales/en/common.json (1 hunks)
  • packages/app-store/exchange2013calendar/lib/CalendarService.ts (3 hunks)
  • packages/app-store/exchange2016calendar/lib/CalendarService.ts (3 hunks)
  • packages/app-store/exchangecalendar/lib/CalendarService.ts (4 hunks)
  • packages/app-store/feishucalendar/lib/CalendarService.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/CalendarService.ts (2 hunks)
  • packages/app-store/larkcalendar/lib/CalendarService.ts (1 hunks)
  • packages/app-store/zohocalendar/lib/CalendarService.ts (2 hunks)
  • packages/features/CalendarEventBuilder.ts (1 hunks)
  • packages/features/bookings/lib/handleNewBooking.ts (1 hunks)
  • packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts (1 hunks)
  • packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx (2 hunks)
  • packages/features/eventtypes/components/tabs/advanced/OptionalTeamGuestSetting.tsx (1 hunks)
  • packages/features/eventtypes/lib/types.ts (1 hunks)
  • packages/lib/defaultEvents.ts (1 hunks)
  • packages/lib/server/repository/eventType.ts (1 hunks)
  • packages/platform/atoms/event-types/hooks/useEventTypeForm.ts (1 hunks)
  • packages/prisma/migrations/20250716153028_add_team_member_as_optional_guest/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (2 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/types.ts (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (4 hunks)
  • packages/types/Calendar.d.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (16)
📓 Common learnings
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.255Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be added as they would break this subscription sharing functionality.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.256Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The `upsertSelectedCalendarsForEventTypeIds` method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be applied as they would prevent this intended functionality.
packages/features/eventtypes/lib/types.ts (1)
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.341Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.
packages/platform/atoms/event-types/hooks/useEventTypeForm.ts (3)
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.341Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.
Learnt from: alishaz-polymath
PR: calcom/cal.com#22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.001Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the `currentLink.maxUsageCount ?? 1` fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.255Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be added as they would break this subscription sharing functionality.
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts (1)
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.341Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.
packages/features/bookings/lib/handleNewBooking.ts (1)
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.341Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.
packages/trpc/server/routers/viewer/eventTypes/types.ts (1)
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.255Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be added as they would break this subscription sharing functionality.
packages/app-store/feishucalendar/lib/CalendarService.ts (1)
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.370Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
packages/app-store/exchange2013calendar/lib/CalendarService.ts (3)
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.370Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.256Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The `upsertSelectedCalendarsForEventTypeIds` method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be applied as they would prevent this intended functionality.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.255Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be added as they would break this subscription sharing functionality.
packages/app-store/exchangecalendar/lib/CalendarService.ts (3)
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.370Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.256Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The `upsertSelectedCalendarsForEventTypeIds` method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be applied as they would prevent this intended functionality.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.255Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be added as they would break this subscription sharing functionality.
packages/app-store/larkcalendar/lib/CalendarService.ts (1)
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.370Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
packages/app-store/exchange2016calendar/lib/CalendarService.ts (3)
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.370Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.256Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The `upsertSelectedCalendarsForEventTypeIds` method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be applied as they would prevent this intended functionality.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.255Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be added as they would break this subscription sharing functionality.
packages/app-store/googlecalendar/lib/CalendarService.ts (1)
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.370Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
packages/app-store/zohocalendar/lib/CalendarService.ts (1)
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.370Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
packages/features/eventtypes/components/tabs/advanced/OptionalTeamGuestSetting.tsx (2)
Learnt from: alishaz-polymath
PR: calcom/cal.com#22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.001Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the `currentLink.maxUsageCount ?? 1` fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.341Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.
apps/web/public/static/locales/en/common.json (1)

undefined

<retrieved_learning>
Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.201Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.
</retrieved_learning>

packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx (2)
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.341Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.
Learnt from: alishaz-polymath
PR: calcom/cal.com#22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.001Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the `currentLink.maxUsageCount ?? 1` fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.
🧬 Code Graph Analysis (2)
packages/app-store/exchangecalendar/lib/CalendarService.ts (2)
packages/trpc/server/routers/publicViewer/procedures/event.ts (1)
  • event (8-12)
packages/types/Calendar.d.ts (1)
  • Person (33-45)
packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (1)
packages/platform/libraries/index.ts (2)
  • TRPCError (56-56)
  • SchedulingType (30-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: required
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Security Check
🔇 Additional comments (32)
packages/features/eventtypes/lib/types.ts (1)

168-168: LGTM! Type definition is appropriate for the feature.

The optionalGuestTeamMembers property is correctly defined as an array of objects with numeric IDs, which is suitable for storing references to selected team members in the form state.

packages/lib/defaultEvents.ts (1)

147-147: LGTM! Proper default initialization.

The empty array default for optionalGuestTeamMembers is appropriate and follows the established pattern in the commons object for initializing optional array properties.

packages/types/Calendar.d.ts (1)

224-227: LGTM! Well-structured type definition for calendar events.

The optionalGuestTeamMembers property is appropriately defined with:

  • Required email for calendar invitations
  • Optional name field (allowing null)
  • Optional property for backward compatibility

The structure correctly differs from the form type (using email/name instead of just id) as calendar events need actual contact information rather than just references.

packages/trpc/server/routers/viewer/eventTypes/types.ts (1)

90-93: LGTM! Proper schema validation definition.

The optionalGuestTeamMembers schema is well-defined with:

  • Appropriate Zod validation for array of objects with numeric IDs
  • Optional and nullable for backward compatibility
  • Consistent structure with the form type definitions

The validation ensures type safety while maintaining flexibility for different input states.

packages/features/bookings/lib/handleNewBooking.ts (1)

1163-1163: LGTM! Clean integration with the CalendarEventBuilder pattern.

The addition follows the established builder pattern used throughout the file and includes appropriate null-safety with the empty array fallback.

packages/platform/atoms/event-types/hooks/useEventTypeForm.ts (1)

83-83: LGTM! Proper form field initialization.

The addition follows the existing pattern for initializing optional array fields in the form's default values, ensuring consistent form state management.

packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts (1)

175-181: LGTM! Well-structured Prisma relation selection.

The addition follows the established pattern for relation selections in this query and appropriately selects the necessary fields (name, email, id) for optional guest team member functionality.

packages/lib/server/repository/eventType.ts (1)

520-524: LGTM! Well-integrated addition following established patterns.

The addition of optionalGuestTeamMembers to the select object follows the same pattern as other relations in the codebase, appropriately selecting only the id field. This change aligns with the PR objective to support optional guest team members functionality.

packages/features/CalendarEventBuilder.ts (1)

265-276: LGTM! Clean builder method implementation.

The withOptionalGuestTeamMembers method follows the established builder pattern perfectly, with appropriate TypeScript typing and safe defaults. The method signature aligns with the CalendarEvent interface and provides a clean API for setting optional guest team members.

packages/prisma/schema.prisma (1)

206-209: Rename the new relation field for clarity & to avoid naming collisions

The field name EventType is too generic, starts with a capital letter (inconsistent with the rest of the model), and clashes semantically with the already-existing eventTypes / ownedEventTypes relations. This will generate an awkward Prisma Client API (user.EventType) that gives no hint that it represents “optional guest” event types.

-  optionalGuestTeamMembers User[]    @relation("optional_guest_team_members")
+  optionalGuestTeamMembers User[]    @relation("optional_guest_team_members")

No change is needed on the EventType side, but on User the mirror field should be renamed to something descriptive, e.g. optionalGuestForEventTypes.

Likely an incorrect or invalid review comment.

packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx (2)

66-66: LGTM: Clean import addition.

The import is properly placed in alphabetical order and follows the established pattern for local component imports.


1205-1207: LGTM: Well-implemented conditional rendering.

The component is properly conditionally rendered with appropriate checks for team existence and members. The props passed (team and eventType) align with the component's requirements, and the placement before the round-robin setting makes logical sense.

packages/app-store/larkcalendar/lib/CalendarService.ts (1)

402-448: Excellent refactor for attendee handling.

The restructured translateAttendees method demonstrates several best practices:

  1. Efficient lookup: Using a Set for optional guest emails provides O(1) lookup performance
  2. Clear separation: The 4-step approach with descriptive comments makes the logic easy to follow
  3. Duplication prevention: Properly filters required team members to exclude those marked as optional guests
  4. Proper flagging: Correctly sets is_optional based on attendee type

This refactor aligns well with the broader pattern across calendar services for handling optional guest team members.

packages/app-store/exchange2013calendar/lib/CalendarService.ts (3)

72-96: Well-implemented attendee handling in createEvent.

The changes properly implement the optional guest team member feature:

  • Efficient lookup: Set-based approach for checking optional guest emails
  • Proper segregation: Required team members are filtered to exclude optional guests, preventing duplication
  • Correct API usage: Uses Exchange's RequiredAttendees and OptionalAttendees collections appropriately
  • Clear logic flow: Well-commented steps make the intent obvious

128-155: Comprehensive updateEvent implementation.

The changes ensure proper attendee management during updates:

  • Prevents accumulation: Clearing existing attendees before adding new ones avoids stale entries
  • Consistent logic: Uses the same efficient attendee handling as createEvent
  • Complete coverage: Handles both required and optional attendees consistently

This thorough approach ensures the update operation maintains data integrity.


104-104: Good improvement: Added descriptive type field.

Setting the type to "exchange2013_calendar" instead of an empty string provides better service identification and debugging capabilities.

packages/app-store/googlecalendar/lib/CalendarService.ts (3)

104-107: LGTM! Efficient email lookup implementation.

The use of a Set with lowercase conversion provides optimal O(1) lookup performance and ensures case-insensitive email matching.


111-116: Good deduplication logic.

The enhanced filter correctly prevents team members from being added as both required and optional attendees, maintaining clean attendee lists.


130-147: Excellent implementation of optional guest handling.

The code correctly sets optional: true and responseStatus: "needsAction" for optional attendees, with proper deduplication logic to prevent conflicts with existing attendees.

packages/app-store/exchange2016calendar/lib/CalendarService.ts (4)

73-90: Consistent implementation with proper deduplication.

The optional guest handling follows the established pattern from Google Calendar integration, correctly preventing team members from being added as both required and optional attendees.


93-97: Correct use of Exchange API for optional attendees.

The code properly utilizes appointment.OptionalAttendees.Add() to distinguish optional guests from required attendees in the Exchange calendar system.


129-156: Good practice of clearing existing attendees in updates.

The implementation correctly clears both required and optional attendees before adding new ones, preventing stale data accumulation. The attendee handling logic consistently mirrors the createEvent method.


105-105: Good addition of explicit type identifier.

Setting the type field to "exchange2016_calendar" improves clarity and makes it easier to identify the calendar integration used for event creation.

packages/app-store/exchangecalendar/lib/CalendarService.ts (3)

63-87: Consistent implementation with proper TypeScript typing.

The optional guest handling follows the established pattern with correct type annotations. The deduplication logic properly prevents conflicts between required and optional attendees.


117-143: Proper attendee management in updates.

The implementation correctly clears existing attendees before reconstruction, preventing data inconsistencies. The logic consistently mirrors the createEvent method pattern.


95-95: Good addition of explicit type identifiers.

Setting the type field to "exchange_calendar" in both methods improves consistency and makes it easier to identify the calendar integration used.

Also applies to: 154-154

packages/app-store/feishucalendar/lib/CalendarService.ts (2)

404-423: Excellent refactoring with improved deduplication.

The introduction of the addUniqueAttendee helper function and Set-based deduplication significantly improves code organization and prevents duplicate attendees. The case-insensitive email tracking is a good practice.


424-444: Well-structured 4-step attendee processing.

The systematic approach with clear step documentation makes the attendee handling logic easy to understand and maintain. The filtering correctly prevents duplication and excludes the current user from team member invitations.

packages/prisma/migrations/20250716153028_add_team_member_as_optional_guest/migration.sql (1)

1-18: Well-structured many-to-many relationship migration.

The migration follows Prisma conventions with proper indexing and CASCADE constraints. The unique index on (A, B) prevents duplicate relationships while the index on B optimizes queries from the user side.

packages/app-store/zohocalendar/lib/CalendarService.ts (1)

470-470: Good refactoring to extract attendee logic.

Moving the attendee construction logic to a dedicated method improves code organization and maintainability.

packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (2)

394-418: Well-implemented validation for optional guest team members.

The validation logic properly handles:

  • Duplicate detection using Set for efficiency
  • Team membership verification to ensure data integrity
  • Clear error messages for different failure scenarios
  • Correct Prisma relation update pattern with set/connect

432-471: Good defensive programming for host updates.

The added conditional check if (hosts && Array.isArray(hosts)) prevents unintended modifications to the hosts relation when the field is not included in the update request. This is an important improvement for API robustness.

@graphite-app
Copy link

graphite-app bot commented Jul 20, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (07/20/25)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add community label" took an action on this PR • (07/20/25)

1 label was added to this PR based on Keith Williams's automation.

@husniabad husniabad force-pushed the feature/add-team-member-as-optional-guest branch from 2f2e97f to ad8e8a6 Compare July 20, 2025 16:54
Copy link
Contributor

@Devanshusharma2005 Devanshusharma2005 left a comment

Choose a reason for hiding this comment

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

typecheck issues are failing. Can you please address them.

@husniabad husniabad force-pushed the feature/add-team-member-as-optional-guest branch from ad8e8a6 to bf05524 Compare July 20, 2025 17:42
@husniabad
Copy link
Contributor Author

typecheck issues are failing. Can you please address them.

Issue fixed, please check again

@github-actions
Copy link
Contributor

github-actions bot commented Aug 6, 2025

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label Aug 6, 2025
Copy link
Contributor

@kart1ka kart1ka left a comment

Choose a reason for hiding this comment

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

Hey @husniabad, Could you pls resolve the merge conflicts?

@kart1ka kart1ka marked this pull request as draft August 6, 2025 05:19
@husniabad husniabad force-pushed the feature/add-team-member-as-optional-guest branch from bf05524 to 336a5da Compare August 6, 2025 14:14
@husniabad husniabad marked this pull request as ready for review August 6, 2025 15:04
@husniabad
Copy link
Contributor Author

husniabad commented Aug 6, 2025

Hi @kart1ka I have updated the PR, could you please review

@husniabad husniabad force-pushed the feature/add-team-member-as-optional-guest branch from 2f0cb60 to f87a6d7 Compare August 8, 2025 15:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/lib/csp.ts (1)

39-50: Document the magic number.

22 bytes is a good choice (base64 encodes to 32 chars). Add a quick inline note so future edits don’t accidentally change CSP expectations.

-  const BYTE_LEN = 22;
+  const BYTE_LEN = 22; // 22 bytes → 32 base64 chars (no padding) for CSP nonce
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b82c8fe and f87a6d7.

📒 Files selected for processing (23)
  • apps/web/lib/csp.ts (1 hunks)
  • apps/web/public/static/locales/en/common.json (1 hunks)
  • packages/app-store/exchange2013calendar/lib/CalendarService.ts (3 hunks)
  • packages/app-store/exchange2016calendar/lib/CalendarService.ts (3 hunks)
  • packages/app-store/exchangecalendar/lib/CalendarService.ts (4 hunks)
  • packages/app-store/feishucalendar/lib/CalendarService.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/CalendarService.ts (2 hunks)
  • packages/app-store/larkcalendar/lib/CalendarService.ts (1 hunks)
  • packages/app-store/zohocalendar/lib/CalendarService.ts (2 hunks)
  • packages/features/CalendarEventBuilder.ts (1 hunks)
  • packages/features/bookings/lib/handleNewBooking.ts (1 hunks)
  • packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts (1 hunks)
  • packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx (2 hunks)
  • packages/features/eventtypes/components/tabs/advanced/OptionalTeamGuestSetting.tsx (1 hunks)
  • packages/features/eventtypes/lib/types.ts (1 hunks)
  • packages/lib/defaultEvents.ts (1 hunks)
  • packages/lib/server/repository/eventTypeRepository.ts (2 hunks)
  • packages/platform/atoms/event-types/hooks/useEventTypeForm.ts (1 hunks)
  • packages/prisma/migrations/20250716153028_add_team_member_as_optional_guest/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (2 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/types.ts (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (3 hunks)
  • packages/types/Calendar.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (21)
  • packages/features/eventtypes/lib/types.ts
  • packages/lib/defaultEvents.ts
  • packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
  • packages/platform/atoms/event-types/hooks/useEventTypeForm.ts
  • packages/features/CalendarEventBuilder.ts
  • packages/trpc/server/routers/viewer/eventTypes/types.ts
  • packages/app-store/larkcalendar/lib/CalendarService.ts
  • packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx
  • packages/app-store/exchangecalendar/lib/CalendarService.ts
  • packages/features/bookings/lib/handleNewBooking.ts
  • packages/app-store/exchange2016calendar/lib/CalendarService.ts
  • packages/types/Calendar.d.ts
  • packages/prisma/schema.prisma
  • packages/app-store/exchange2013calendar/lib/CalendarService.ts
  • packages/app-store/feishucalendar/lib/CalendarService.ts
  • packages/prisma/migrations/20250716153028_add_team_member_as_optional_guest/migration.sql
  • packages/features/eventtypes/components/tabs/advanced/OptionalTeamGuestSetting.tsx
  • apps/web/public/static/locales/en/common.json
  • packages/app-store/googlecalendar/lib/CalendarService.ts
  • packages/app-store/zohocalendar/lib/CalendarService.ts
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • apps/web/lib/csp.ts
  • packages/lib/server/repository/eventTypeRepository.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • apps/web/lib/csp.ts
  • packages/lib/server/repository/eventTypeRepository.ts
**/*Repository.ts

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Repository files must include Repository suffix, prefix with technology if applicable (e.g., PrismaAppRepository.ts), and use PascalCase matching the exported class

Files:

  • packages/lib/server/repository/eventTypeRepository.ts
🧠 Learnings (9)
📓 Common learnings
Learnt from: sean-brydon
PR: calcom/cal.com#22618
File: packages/trpc/server/routers/viewer/eventTypes/utils/transformUtils.ts:113-113
Timestamp: 2025-08-05T07:42:06.335Z
Learning: In Cal.com's getUserEventGroups handler refactor (PR #22618), the membershipCount field for team event groups is intentionally set to 0 in the new createTeamEventGroup function, as confirmed by sean-brydon (PR author). This preserves the same behavior as the old implementation that was being refactored, maintaining backward compatibility. While other parts of the codebase may use actual member counts, this specific implementation maintains the previous behavior.
Learnt from: sean-brydon
PR: calcom/cal.com#22618
File: packages/trpc/server/routers/viewer/eventTypes/utils/transformUtils.ts:113-113
Timestamp: 2025-08-05T07:42:06.335Z
Learning: In Cal.com's event type system, the membershipCount field for team event groups is intentionally set to 0, as confirmed by sean-brydon (PR author). This behavior was preserved during the refactor from the old getUserEventGroups.handler.ts implementation to the new createTeamEventGroup function in transformUtils.ts. This is not a bug but the intended behavior that maintains consistency with the previous implementation.
Learnt from: sean-brydon
PR: calcom/cal.com#22618
File: packages/trpc/server/routers/viewer/eventTypes/utils/transformUtils.ts:113-113
Timestamp: 2025-08-05T07:42:06.335Z
Learning: In Cal.com's event type system, the membershipCount field for team event groups is intentionally hard-coded to 0, as confirmed by sean-brydon. This behavior was preserved during the refactor from the old getUserEventGroups.handler.ts implementation to the new createTeamEventGroup function in transformUtils.ts. This is not a bug but the intended behavior.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like `@unique([outlookSubscriptionId, eventTypeId])` should not be added as they would break this subscription sharing functionality.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The `upsertSelectedCalendarsForEventTypeIds` method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like `@unique([outlookSubscriptionId, eventTypeId])` should not be applied as they would prevent this intended functionality.
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.ts : Ensure the `credential.key` field is never returned from tRPC endpoints or APIs

Applied to files:

  • apps/web/lib/csp.ts
📚 Learning: 2025-08-05T07:42:06.335Z
Learnt from: sean-brydon
PR: calcom/cal.com#22618
File: packages/trpc/server/routers/viewer/eventTypes/utils/transformUtils.ts:113-113
Timestamp: 2025-08-05T07:42:06.335Z
Learning: In Cal.com's getUserEventGroups handler refactor (PR #22618), the membershipCount field for team event groups is intentionally set to 0 in the new createTeamEventGroup function, as confirmed by sean-brydon (PR author). This preserves the same behavior as the old implementation that was being refactored, maintaining backward compatibility. While other parts of the codebase may use actual member counts, this specific implementation maintains the previous behavior.

Applied to files:

  • packages/lib/server/repository/eventTypeRepository.ts
📚 Learning: 2025-08-05T07:42:06.335Z
Learnt from: sean-brydon
PR: calcom/cal.com#22618
File: packages/trpc/server/routers/viewer/eventTypes/utils/transformUtils.ts:113-113
Timestamp: 2025-08-05T07:42:06.335Z
Learning: In Cal.com's event type system, the membershipCount field for team event groups is intentionally set to 0, as confirmed by sean-brydon (PR author). This behavior was preserved during the refactor from the old getUserEventGroups.handler.ts implementation to the new createTeamEventGroup function in transformUtils.ts. This is not a bug but the intended behavior that maintains consistency with the previous implementation.

Applied to files:

  • packages/lib/server/repository/eventTypeRepository.ts
📚 Learning: 2025-08-05T07:42:06.335Z
Learnt from: sean-brydon
PR: calcom/cal.com#22618
File: packages/trpc/server/routers/viewer/eventTypes/utils/transformUtils.ts:113-113
Timestamp: 2025-08-05T07:42:06.335Z
Learning: In Cal.com's event type system, the membershipCount field for team event groups is intentionally hard-coded to 0, as confirmed by sean-brydon. This behavior was preserved during the refactor from the old getUserEventGroups.handler.ts implementation to the new createTeamEventGroup function in transformUtils.ts. This is not a bug but the intended behavior.

Applied to files:

  • packages/lib/server/repository/eventTypeRepository.ts
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.ts : For Prisma queries, only select data you need; never use `include`, always use `select`

Applied to files:

  • packages/lib/server/repository/eventTypeRepository.ts
📚 Learning: 2025-08-08T09:27:23.870Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/providers/retellAI/services/AgentService.ts:195-216
Timestamp: 2025-08-08T09:27:23.870Z
Learning: In PR calcom/cal.com#22919, file packages/features/calAIPhone/providers/retellAI/services/AgentService.ts, the updateAgentConfiguration method intentionally does not persist the optional `name` parameter to the repository for now, per maintainer (Udit-takkar). Future reviews should not flag this unless requirements change.

Applied to files:

  • packages/lib/server/repository/eventTypeRepository.ts
📚 Learning: 2025-08-07T18:42:34.056Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/lib/server/repository/PrismaPhoneNumberRepository.ts:412-417
Timestamp: 2025-08-07T18:42:34.056Z
Learning: In Cal.com codebase, the coding guideline requiring explicit `select` clauses instead of `include` for Prisma queries applies to read operations but not to update operations. Update operations don't need explicit select clauses.

Applied to files:

  • packages/lib/server/repository/eventTypeRepository.ts
📚 Learning: 2025-08-08T09:29:11.665Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts:118-143
Timestamp: 2025-08-08T09:29:11.665Z
Learning: In calcom/cal.com PR #22919, packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts (TypeScript), the AIPhoneServiceAgentListItem is required to include user.email in listAgents responses (per maintainer Udit-takkar). Future reviews should not flag this as unnecessary PII unless requirements change.

Applied to files:

  • packages/lib/server/repository/eventTypeRepository.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (4)
packages/lib/server/repository/eventTypeRepository.ts (3)

302-306: AI summary vs code: minimal-data method does not include optionalGuestTeamMembers

The AI summary mentions adding optionalGuestTeamMembers to findAllByUpIdWithMinimalData, but this select currently includes only eventTypeSelect and hashedLink. If minimal views/UI need to show existing optional guests, consider adding the id-only selection here; otherwise update the PR summary for accuracy.

Proposed change:

 const select = {
   ...eventTypeSelect,
   hashedLink: hashedLinkSelect,
+  optionalGuestTeamMembers: { select: { id: true } },
 };

To ensure we don’t duplicate what eventTypeSelect might already include, check its contents:

#!/bin/bash
# Locate and inspect eventTypeSelect definition and callers
fd -a 'eventTypeSelect.ts' | while read -r f; do
  echo "==> $f"
  rg -n 'optionalGuestTeamMembers|export const eventTypeSelect' "$f" -n -A 3
done

# Find usages of findAllByUpIdWithMinimalData to confirm consumer needs
rg -n --hidden --glob '!node_modules' 'findAllByUpIdWithMinimalData' -A 3

Likely an incorrect or invalid review comment.


866-870: Approve minimal id-only select for optionalGuestTeamMembers
Selecting only id in eventTypeRepository keeps the payload minimal and aligns with how the viewer and update routes consume this data. Downstream booking flows (via getEventTypesFromDB) still fetch email and name for calendar-provider integrations, and the UI maps the returned IDs to full member details in the OptionalTeamGuestSetting component. No further changes needed.


565-569: optionalGuestTeamMembers select is safe as implemented

All downstream consumers either only need an id or explicitly fetch additional fields:

  • UI (OptionalTeamGuestSetting.tsx) only reads .length and IDs to toggle settings.
  • tRPC schema (viewer/eventTypes/types.ts) defines optionalGuestTeamMembers as array<{id: number}>.
  • Booking flow (getEventTypesFromDB.ts) uses its own query selecting id, name, and email.

No changes required.

apps/web/lib/csp.ts (1)

41-48: No client-side bundling risk detected

We verified that getCspNonce is only imported in apps/web/middleware.ts (server‐only), and there are no imports of apps/web/lib/csp in any client components, pages, or hooks. The runtime guard around crypto/node:crypto covers both environments, so no further changes are needed here.

@husniabad husniabad force-pushed the feature/add-team-member-as-optional-guest branch 2 times, most recently from 55a564f to c2da2f1 Compare August 8, 2025 16:48
@husniabad
Copy link
Contributor Author

Hi @kart1ka I updated the PR and fixed merge conflicts as well I addressed the test failure

The test failure was not related to my PR, most probably due to the previous merged PR, I fixed it in this file: apps/web/lib/csp.ts

Copy link
Contributor

@kart1ka kart1ka 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 few questions.

const oldHostsSet = new Set(eventType.hosts.map((oldHost) => oldHost.userId));
const newHostsSet = new Set(hosts.map((oldHost) => oldHost.userId));
// weights were already enabled or are enabled now
const isWeightsEnabled =
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this added?

Comment on lines 39 to 60
const BYTE_LEN = 22;
const bytes = new Uint8Array(BYTE_LEN);

return nonce;
// Prefer Web Crypto in both browser and modern Node runtimes
const hasWebCrypto =
typeof (globalThis as any).crypto !== "undefined" &&
typeof (globalThis as any).crypto.getRandomValues === "function";

if (hasWebCrypto) {
(globalThis as any).crypto.getRandomValues(bytes);
} else {
// Node.js environment (including tests)
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nodeCrypto = require("node:crypto") as typeof import("node:crypto");
if (nodeCrypto.webcrypto?.getRandomValues) {
nodeCrypto.webcrypto.getRandomValues(bytes);
} else {
nodeCrypto.randomFillSync(bytes);
}
}

return buildNonce(bytes);
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one has nothing to do with the implementation feature; I added it because tests were failing due to another merged PR the time I pushed

@husniabad husniabad force-pushed the feature/add-team-member-as-optional-guest branch from b03394d to fd33b44 Compare August 25, 2025 17:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app-store/exchange2016calendar/lib/CalendarService.ts (1)

129-161: Await Update() to ensure persistence and error propagation.

Appointment.Update(...) returns a promise; without await, the function can exit early and errors won’t be caught in this try/catch. This is especially important after clearing and rebuilding attendees.

Apply this diff:

-      appointment.Update(
+      await appointment.Update(
         ConflictResolutionMode.AlwaysOverwrite,
         SendInvitationsOrCancellationsMode.SendToAllAndSaveCopy
       );
🧹 Nitpick comments (6)
packages/app-store/exchange2016calendar/lib/CalendarService.ts (3)

78-96: De-duplicate attendees and prevent conflicts between required and optional lists.

As written, duplicates can slip in (e.g., repeated attendee emails in input, overlap between event.attendees and team members, or between required and optional). EWS can end up with duplicate recipients or ambiguous attendee state. Normalize and track to avoid duplicates.

Proposed focused refactor within this block:

-      // Add the main booker as required
-      for (let i = 0; i < event.attendees.length; i++) {
-        appointment.RequiredAttendees.Add(new Attendee(event.attendees[i].email));
-      }
+      // Add the main booker as required (de-duped)
+      const seenRequired = new Set<string>();
+      for (const a of event.attendees ?? []) {
+        const email = a?.email?.trim();
+        if (!email) continue;
+        const l = email.toLowerCase();
+        if (seenRequired.has(l)) continue;
+        appointment.RequiredAttendees.Add(new Attendee(email));
+        seenRequired.add(l);
+      }
 
       // Add team members as required, ONLY if they aren't optional
       if (event.team?.members) {
-        event.team.members
-          .filter((member) => member.email && !optionalGuestEmails.has(member.email.toLowerCase()))
-          .forEach((member) => {
-            appointment.RequiredAttendees.Add(new Attendee(member.email));
-          });
+        for (const m of event.team.members ?? []) {
+          const email = m?.email?.trim();
+          if (!email) continue;
+          const l = email.toLowerCase();
+          if (optionalGuestEmails.has(l) || seenRequired.has(l)) continue;
+          appointment.RequiredAttendees.Add(new Attendee(email));
+          seenRequired.add(l);
+        }
       }
 
-      // Add optional members to the optional list
-      if (event.optionalGuestTeamMembers) {
-        event.optionalGuestTeamMembers.forEach((member) => {
-          appointment.OptionalAttendees.Add(new Attendee(member.email));
-        });
-      }
+      // Add optional members to the optional list (skip any already required, and de-dupe)
+      if (event.optionalGuestTeamMembers) {
+        const seenOptional = new Set<string>();
+        for (const m of event.optionalGuestTeamMembers) {
+          const email = m?.email?.trim();
+          if (!email) continue;
+          const l = email.toLowerCase();
+          if (seenOptional.has(l) || seenRequired.has(l)) continue;
+          appointment.OptionalAttendees.Add(new Attendee(email));
+          seenOptional.add(l);
+        }
+      }

137-156: De-duplicate required/optional attendees during updates as well.

Parity with createEvent avoids drift and duplicate recipients after edits.

Minimal localized changes:

-      // Add the main booker as required
-      for (let i = 0; i < event.attendees.length; i++) {
-        appointment.RequiredAttendees.Add(new Attendee(event.attendees[i].email));
-      }
+      // Add the main booker as required (de-duped)
+      const seenRequired = new Set<string>();
+      for (const a of event.attendees ?? []) {
+        const email = a?.email?.trim();
+        if (!email) continue;
+        const l = email.toLowerCase();
+        if (seenRequired.has(l)) continue;
+        appointment.RequiredAttendees.Add(new Attendee(email));
+        seenRequired.add(l);
+      }
 
       // Add team members as required, ONLY if they aren't optional
       if (event.team?.members) {
-        event.team.members
-          .filter((member) => member.email && !optionalGuestEmails.has(member.email.toLowerCase()))
-          .forEach((member) => {
-            appointment.RequiredAttendees.Add(new Attendee(member.email));
-          });
+        for (const m of event.team.members ?? []) {
+          const email = m?.email?.trim();
+          if (!email) continue;
+          const l = email.toLowerCase();
+          if (optionalGuestEmails.has(l) || seenRequired.has(l)) continue;
+          appointment.RequiredAttendees.Add(new Attendee(email));
+          seenRequired.add(l);
+        }
       }
 
       // Add optional members to the optional list
-      if (event.optionalGuestTeamMembers) {
-        event.optionalGuestTeamMembers.forEach((member) => {
-          appointment.OptionalAttendees.Add(new Attendee(member.email));
-        });
-      }
+      if (event.optionalGuestTeamMembers) {
+        const seenOptional = new Set<string>();
+        for (const m of event.optionalGuestTeamMembers) {
+          const email = m?.email?.trim();
+          if (!email) continue;
+          const l = email.toLowerCase();
+          if (seenOptional.has(l) || seenRequired.has(l)) continue;
+          appointment.OptionalAttendees.Add(new Attendee(email));
+          seenOptional.add(l);
+        }
+      }

36-36: Prefer named export to comply with repository guidelines.

Coding guideline recommends avoiding default exports in TS modules; consider export class ExchangeCalendarService ... and updating imports accordingly. This can be a follow-up if other calendar modules also default-export.

I can scan the repo to assess breakage risk and outline a safe migration plan if you want.

packages/app-store/feishucalendar/lib/CalendarService.ts (3)

428-435: Exclude organizer and compare emails case-insensitively to avoid self-invites.

Right now you exclude only this.credential.user?.email with a case-sensitive check. If the organizer differs from the credential user (service accounts) or casing differs, they can be added as a required attendee. Normalize and exclude both:

-    event.team?.members.forEach((member) => {
-      if (
-        member.email &&
-        member.email !== this.credential.user?.email &&
-        !optionalGuestEmails.has(member.email.toLowerCase())
-      ) {
-        addUniqueAttendee(member.email, false);
-      }
-    });
+    const currentUserEmailLc = this.credential.user?.email?.toLowerCase();
+    const organizerEmailLc = event.organizer?.email?.toLowerCase();
+    event.team?.members.forEach((member) => {
+      const memberEmailLc = member.email?.toLowerCase();
+      if (
+        memberEmailLc &&
+        memberEmailLc !== currentUserEmailLc &&
+        memberEmailLc !== organizerEmailLc &&
+        !optionalGuestEmails.has(memberEmailLc)
+      ) {
+        addUniqueAttendee(member.email, false);
+      }
+    });

404-444: Optional: prefer internal user IDs when available for better native behavior.

Feishu treats internal “user” attendees (user_id) differently from third_party emails (richer signals like is_external, org visibility). If you have Feishu user IDs for team members in event.team.members, consider sending type: "user" with user_id for those, falling back to third_party_email otherwise. This can improve consistency with in-tenant meetings. (pkg.go.dev)

I can draft a helper that maps team members to Feishu user attendees when an id field (e.g., feishuOpenId) is present, with graceful fallback.


404-444: Add targeted tests for attendee translation.

Recommend tests covering: (1) dedup across attendees/team/optionals (case-insensitive), (2) organizer/current user exclusion, (3) “required wins” precedence, and (4) optional guests flagged correctly. Since translateAttendees is private, you can extract a small pure helper (e.g., buildFeishuAttendees(event, credentialUserEmail)) for unit testing.

Happy to provide a test-ready helper + tests if you want.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b03394d and fd33b44.

📒 Files selected for processing (22)
  • apps/web/public/static/locales/en/common.json (1 hunks)
  • packages/app-store/exchange2013calendar/lib/CalendarService.ts (3 hunks)
  • packages/app-store/exchange2016calendar/lib/CalendarService.ts (3 hunks)
  • packages/app-store/exchangecalendar/lib/CalendarService.ts (4 hunks)
  • packages/app-store/feishucalendar/lib/CalendarService.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/CalendarService.ts (2 hunks)
  • packages/app-store/larkcalendar/lib/CalendarService.ts (1 hunks)
  • packages/app-store/zohocalendar/lib/CalendarService.ts (2 hunks)
  • packages/features/CalendarEventBuilder.ts (1 hunks)
  • packages/features/bookings/lib/handleNewBooking.ts (1 hunks)
  • packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts (1 hunks)
  • packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx (2 hunks)
  • packages/features/eventtypes/components/tabs/advanced/OptionalTeamGuestSetting.tsx (1 hunks)
  • packages/features/eventtypes/lib/types.ts (1 hunks)
  • packages/lib/defaultEvents.ts (1 hunks)
  • packages/lib/server/repository/eventTypeRepository.ts (2 hunks)
  • packages/platform/atoms/event-types/hooks/useEventTypeForm.ts (1 hunks)
  • packages/prisma/migrations/20250716153028_add_team_member_as_optional_guest/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (2 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/types.ts (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (3 hunks)
  • packages/types/Calendar.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (19)
  • packages/lib/defaultEvents.ts
  • packages/app-store/exchangecalendar/lib/CalendarService.ts
  • packages/platform/atoms/event-types/hooks/useEventTypeForm.ts
  • packages/prisma/migrations/20250716153028_add_team_member_as_optional_guest/migration.sql
  • packages/features/bookings/lib/handleNewBooking.ts
  • packages/app-store/exchange2013calendar/lib/CalendarService.ts
  • packages/types/Calendar.d.ts
  • packages/app-store/googlecalendar/lib/CalendarService.ts
  • packages/trpc/server/routers/viewer/eventTypes/types.ts
  • packages/prisma/schema.prisma
  • apps/web/public/static/locales/en/common.json
  • packages/features/eventtypes/lib/types.ts
  • packages/app-store/zohocalendar/lib/CalendarService.ts
  • packages/lib/server/repository/eventTypeRepository.ts
  • packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
  • packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx
  • packages/features/CalendarEventBuilder.ts
  • packages/features/eventtypes/components/tabs/advanced/OptionalTeamGuestSetting.tsx
  • packages/app-store/larkcalendar/lib/CalendarService.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*Service.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Service files must include Service suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts)

Files:

  • packages/app-store/exchange2016calendar/lib/CalendarService.ts
  • packages/app-store/feishucalendar/lib/CalendarService.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/app-store/exchange2016calendar/lib/CalendarService.ts
  • packages/app-store/feishucalendar/lib/CalendarService.ts
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/app-store/exchange2016calendar/lib/CalendarService.ts
  • packages/app-store/feishucalendar/lib/CalendarService.ts
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/app-store/exchange2016calendar/lib/CalendarService.ts
  • packages/app-store/feishucalendar/lib/CalendarService.ts
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
🧠 Learnings (3)
📚 Learning: 2025-08-05T12:04:29.037Z
Learnt from: din-prajapati
PR: calcom/cal.com#21854
File: packages/app-store/office365calendar/__tests__/unit_tests/SubscriptionManager.test.ts:0-0
Timestamp: 2025-08-05T12:04:29.037Z
Learning: In packages/app-store/office365calendar/lib/CalendarService.ts, the fetcher method in Office365CalendarService class is public, not private. It was specifically changed from private to public in this PR to support proper testing and external access patterns.

Applied to files:

  • packages/app-store/exchange2016calendar/lib/CalendarService.ts
📚 Learning: 2025-08-17T22:00:16.329Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts:117-126
Timestamp: 2025-08-17T22:00:16.329Z
Learning: In calcom/cal.com PR #22995, packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts, the enabled input parameter in the update endpoint is intentionally not forwarded to aiService.updateAgentConfiguration() as the enabled/disabled agent functionality is not required at the moment (per maintainer Udit-takkar). Future reviews should not flag this as missing functionality unless requirements change.

Applied to files:

  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
📚 Learning: 2025-08-21T13:44:06.784Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
🧬 Code graph analysis (2)
packages/app-store/exchange2016calendar/lib/CalendarService.ts (1)
packages/trpc/server/routers/publicViewer/procedures/event.ts (1)
  • event (8-12)
packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (1)
packages/platform/libraries/index.ts (2)
  • TRPCError (56-56)
  • SchedulingType (30-30)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tests / Unit
  • GitHub Check: Linters / lint
  • GitHub Check: Type check / check-types
🔇 Additional comments (7)
packages/app-store/exchange2016calendar/lib/CalendarService.ts (1)

105-106: Return “type” metadata looks good and aligns with integrationName.

Helps downstream consumers disambiguate provider type. Please ensure client-side discriminated unions include "exchange2016_calendar".

If you want, I can generate a quick search script to confirm all consumers handle this type.

packages/app-store/feishucalendar/lib/CalendarService.ts (3)

404-417: Solid dedup/accumulation approach for attendees.

Using a Set with a small helper to prevent duplicates is clean and ensures consistent is_optional flags. The order (required first, then optional) preserves intent when the same person appears in both collections.


411-414: Field mapping to Feishu API looks correct.

Feishu attendees accept type: "third_party" with third_party_email and the is_optional boolean, which aligns with the payload you build here. (apifox.com)


438-441: Order guarantees “required wins” over “optional.”

Adding optionals after required team members and main attendees means explicit invites remain required if duplicated. That matches the product intent.

packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (3)

450-453: LGTM: team guard and membership lookup entrypoint

Entering the team-scoped branch and fetching accepted member IDs up-front is the right shape for the validations that follow.


478-482: LGTM: host membership guard only when hosts are provided

The conditional membership guard keyed on hosts avoids unnecessary checks. The sub-team bypass remains intact.


681-684: Good move: use Prisma.validator for select typing

This improves type-safety and keeps the selection minimal (slug and schedulingType only), which aligns with the “select-only what you need” guideline.

Comment on lines +73 to +76
// Create a set of optional guest emails for easy lookup.
const optionalGuestEmails = new Set(
event.optionalGuestTeamMembers?.map((guest) => guest.email.toLowerCase()) ?? []
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden optional guest email normalization (avoid runtime TypeError) and add typing.

If any optionalGuestTeamMembers entries are missing email, .toLowerCase() will throw. Also, type the Set to string to keep TS strictness.

Apply this diff:

-      // Create a set of optional guest emails for easy lookup.
-      const optionalGuestEmails = new Set(
-        event.optionalGuestTeamMembers?.map((guest) => guest.email.toLowerCase()) ?? []
-      );
+      // Create a set of optional guest emails for easy lookup.
+      const optionalGuestEmails = new Set<string>(
+        (event.optionalGuestTeamMembers ?? [])
+          .map((guest) => guest?.email?.trim())
+          .filter((e): e is string => !!e)
+          .map((e) => e.toLowerCase())
+      );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Create a set of optional guest emails for easy lookup.
const optionalGuestEmails = new Set(
event.optionalGuestTeamMembers?.map((guest) => guest.email.toLowerCase()) ?? []
);
// Create a set of optional guest emails for easy lookup.
const optionalGuestEmails = new Set<string>(
(event.optionalGuestTeamMembers ?? [])
.map((guest) => guest?.email?.trim())
.filter((e): e is string => !!e)
.map((e) => e.toLowerCase())
);
🤖 Prompt for AI Agents
In packages/app-store/exchange2016calendar/lib/CalendarService.ts around lines
73 to 76, the code builds optionalGuestEmails without typing and calls
.toLowerCase() on possibly undefined emails which can throw; change the Set to
be typed Set<string> and only include defined emails after normalizing to
lowercase. Specifically, replace the map with a guarded transform such as
filtering out entries without an email (or using flatMap) and then pushing
guest.email.toLowerCase() into the Set so TypeScript knows the values are
strings and runtime errors are avoided.

Comment on lines +133 to +136
const optionalGuestEmails = new Set(
event.optionalGuestTeamMembers?.map((guest) => guest.email.toLowerCase()) ?? []
);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Mirror the createEvent hardening: filter undefined emails and add Set typing.

Same risk here for .toLowerCase() on undefined.

Apply this diff:

-      const optionalGuestEmails = new Set(
-        event.optionalGuestTeamMembers?.map((guest) => guest.email.toLowerCase()) ?? []
-      );
+      const optionalGuestEmails = new Set<string>(
+        (event.optionalGuestTeamMembers ?? [])
+          .map((guest) => guest?.email?.trim())
+          .filter((e): e is string => !!e)
+          .map((e) => e.toLowerCase())
+      );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const optionalGuestEmails = new Set(
event.optionalGuestTeamMembers?.map((guest) => guest.email.toLowerCase()) ?? []
);
const optionalGuestEmails = new Set<string>(
(event.optionalGuestTeamMembers ?? [])
.map((guest) => guest?.email?.trim())
.filter((e): e is string => !!e)
.map((e) => e.toLowerCase())
);
🤖 Prompt for AI Agents
In packages/app-store/exchange2016calendar/lib/CalendarService.ts around lines
133 to 136, the current construction of optionalGuestEmails calls toLowerCase()
on guest.email without guarding against undefined; update this to mirror
createEvent hardening by first extracting emails, filtering out
undefined/null/empty values, then mapping to lower-case, and declare the
variable as a Set<string> to enforce typing; ensure the filter runs before
toLowerCase so no undefined is passed to the method.

Comment on lines +420 to +422
const optionalGuestEmails = new Set(
event.optionalGuestTeamMembers?.map((guest) => guest.email.toLowerCase()) ?? []
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against undefined emails to avoid runtime errors; also normalize/trim before dedup.

If any optionalGuestTeamMembers entry lacks email, calling toLowerCase() will throw. Additionally, trimming avoids duplicate entries that differ only by whitespace. Apply this small hardening:

-    const optionalGuestEmails = new Set(
-      event.optionalGuestTeamMembers?.map((guest) => guest.email.toLowerCase()) ?? []
-    );
+    const optionalGuestEmails = new Set(
+      (event.optionalGuestTeamMembers ?? [])
+        .map((guest) => guest.email?.trim())
+        .filter((e): e is string => !!e)
+        .map((e) => e.toLowerCase())
+    );

And make addUniqueAttendee robust to stray whitespace and compute lowercased value once:

-    const addUniqueAttendee = (email: string, is_optional: boolean) => {
-      if (email && !addedEmails.has(email.toLowerCase())) {
-        attendeeArray.push({
-          type: "third_party",
-          is_optional,
-          third_party_email: email,
-        });
-        addedEmails.add(email.toLowerCase());
-      }
-    };
+    const addUniqueAttendee = (email: string, is_optional: boolean) => {
+      const trimmed = email?.trim();
+      const lc = trimmed?.toLowerCase();
+      if (lc && !addedEmails.has(lc)) {
+        attendeeArray.push({
+          type: "third_party",
+          is_optional,
+          third_party_email: trimmed,
+        });
+        addedEmails.add(lc);
+      }
+    };

Also applies to: 408-417

🤖 Prompt for AI Agents
In packages/app-store/feishucalendar/lib/CalendarService.ts around lines
408-422, guard against undefined/empty emails and normalize/trim before dedup:
when building optionalGuestEmails skip entries with no email, trim the email and
then compute a single lowercased value to insert into the Set (e.g. const e =
guest.email?.trim(); if (!e) continue;
optionalGuestEmails.add(e.toLowerCase())). Also update the addUniqueAttendee
helper to trim its input, skip empty strings, compute the lowercased key once,
and use that key for deduplication so stray whitespace or undefined values
cannot cause runtime errors or duplicate entries.

Comment on lines +484 to +525
// Only process hosts if they are provided
if (hosts && Array.isArray(hosts)) {
const oldHostsSet = new Set(eventType.hosts.map((oldHost) => oldHost.userId));
const newHostsSet = new Set(hosts.map((oldHost) => oldHost.userId));

const existingHosts = hosts.filter((newHost) => oldHostsSet.has(newHost.userId));
const newHosts = hosts.filter((newHost) => !oldHostsSet.has(newHost.userId));
const removedHosts = eventType.hosts.filter((oldHost) => !newHostsSet.has(oldHost.userId));
const existingHosts = hosts.filter((newHost) => oldHostsSet.has(newHost.userId));
const newHosts = hosts.filter((newHost) => !oldHostsSet.has(newHost.userId));
const removedHosts = eventType.hosts.filter((oldHost) => !newHostsSet.has(oldHost.userId));

data.hosts = {
deleteMany: {
OR: removedHosts.map((host) => ({
userId: host.userId,
eventTypeId: id,
})),
},
create: newHosts.map((host) => {
return {
...host,
isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
groupId: host.groupId,
};
}),
update: existingHosts.map((host) => ({
where: {
userId_eventTypeId: {
data.hosts = {
deleteMany: {
OR: removedHosts.map((host) => ({
userId: host.userId,
eventTypeId: id,
},
},
data: {
isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
scheduleId: host.scheduleId ?? null,
groupId: host.groupId,
})),
},
})),
};
create: newHosts.map((host) => {
return {
...host,
isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
groupId: host.groupId,
};
}),
update: existingHosts.map((host) => ({
where: {
userId_eventTypeId: {
userId: host.userId,
eventTypeId: id,
},
},
data: {
isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
scheduleId: host.scheduleId ?? null,
groupId: host.groupId,
},
})),
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix schedulingType dependency, include scheduleId on create, and guard deleteMany when empty

  • Bug: isFixed is computed from data.schedulingType, which may be undefined when the request doesn’t update schedulingType. Use the effective scheduling type from input or the existing eventType.
  • Inconsistency: scheduleId is set on update but not on create.
  • Robustness: deleteMany with OR: [] can error. Add the clause conditionally.

Apply this diff to the hosts block:

-    // Only process hosts if they are provided
-    if (hosts && Array.isArray(hosts)) {
-      const oldHostsSet = new Set(eventType.hosts.map((oldHost) => oldHost.userId));
-      const newHostsSet = new Set(hosts.map((oldHost) => oldHost.userId));
+    // Only process hosts if they are provided
+    if (hosts && Array.isArray(hosts)) {
+      // Use the effective schedulingType (from input if provided, otherwise current eventType)
+      const effectiveSchedulingType = (input.schedulingType ?? eventType.schedulingType) as SchedulingType;
+      const oldHostsSet = new Set(eventType.hosts.map((h) => h.userId));
+      const newHostsSet = new Set(hosts.map((h) => h.userId));
@@
-      data.hosts = {
-        deleteMany: {
-          OR: removedHosts.map((host) => ({
-            userId: host.userId,
-            eventTypeId: id,
-          })),
-        },
-        create: newHosts.map((host) => {
-          return {
-            ...host,
-            isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
-            priority: host.priority ?? 2,
-            weight: host.weight ?? 100,
-            groupId: host.groupId,
-          };
-        }),
-        update: existingHosts.map((host) => ({
-          where: {
-            userId_eventTypeId: {
-              userId: host.userId,
-              eventTypeId: id,
-            },
-          },
-          data: {
-            isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
-            priority: host.priority ?? 2,
-            weight: host.weight ?? 100,
-            scheduleId: host.scheduleId ?? null,
-            groupId: host.groupId,
-          },
-        })),
-      };
+      const hostUpdates: Prisma.EventTypeUpdateInput["hosts"] = {
+        create: newHosts.map((host) => ({
+          ...host,
+          isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
+          priority: host.priority ?? 2,
+          weight: host.weight ?? 100,
+          scheduleId: host.scheduleId ?? null,
+          groupId: host.groupId,
+        })),
+        update: existingHosts.map((host) => ({
+          where: {
+            userId_eventTypeId: {
+              userId: host.userId,
+              eventTypeId: id,
+            },
+          },
+          data: {
+            isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
+            priority: host.priority ?? 2,
+            weight: host.weight ?? 100,
+            scheduleId: host.scheduleId ?? null,
+            groupId: host.groupId,
+          },
+        })),
+        ...(removedHosts.length > 0
+          ? {
+              deleteMany: {
+                OR: removedHosts.map((host) => ({
+                  userId: host.userId,
+                  eventTypeId: id,
+                })),
+              },
+            }
+          : {}),
+      };
+      data.hosts = hostUpdates;
     }

Additional change required outside this hunk (to support eventType.schedulingType in the effective type calculation):

// In the eventType.findUniqueOrThrow({ select: { ... } }) above, add:
schedulingType: true,

This keeps isFixed consistent even when schedulingType isn’t updated in the same request.

To confirm the above after applying the patch:

  • Verify schedulingType is selected:

  • Sanity-check that optional guests can be cleared (look for set: with optionalGuestTeamMembers):


🏁 Script executed:

rg -nC2 -P 'findUniqueOrThrow\([\\s\\S]*?select:[\\s\\S]*?schedulingType:\s*true' packages/trpc/server/routers/viewer/eventTypes/update.handler.ts

Length of output: 148


🏁 Script executed:

rg -n 'optionalGuestTeamMembers.+set:' packages/trpc/server/routers/viewer/eventTypes/update.handler.ts -C2

Length of output: 107


🏁 Script executed:

#!/bin/bash
# Verify schedulingType is selected in the initial findUniqueOrThrow call
rg -nC5 'findUniqueOrThrow' packages/trpc/server/routers/viewer/eventTypes/update.handler.ts

# Inspect hosts block to confirm scheduleId on create and unconditional deleteMany
rg -nC5 'data\.hosts' packages/trpc/server/routers/viewer/eventTypes/update.handler.ts

Length of output: 946


Ensure schedulingType is selected, include scheduleId on create, and guard deleteMany

The current implementation still lacks the changes needed to keep host updates correct and robust:

  • The findUniqueOrThrow call (around line 102) does not select schedulingType, so eventType.schedulingType isn’t available when the request omits input.schedulingType.
  • In the hosts block (starting at line 488), deleteMany is always emitted, causing an error if removedHosts is empty (OR: []).
  • The create clause for new hosts doesn’t include scheduleId, even though the update clause does.

Please apply the following adjustments to the hosts block and the preceding selection:

--- a/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
+++ b/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
@@ -102,8 +102,9 @@ const eventType = await ctx.prisma.eventType.findUniqueOrThrow({
     where: { id },
     select: {
       title: true,
-      locations: true,
-      description: true,
+      locations: true,
+      description: true,
+      schedulingType: true,
       hostGroups: true,
       optionalGuestTeamMembers: true,
       // …other fields
@@ -490,18 +491, Thirty
-    data.hosts = {
-      deleteMany: {
-        OR: removedHosts.map((host) => ({
-          userId: host.userId,
-          eventTypeId: id,
-        })),
-      },
-      create: newHosts.map((host) => {
-        return {
-          ...host,
-          isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
-          priority: host.priority ?? 2,
-          weight: host.weight ?? 100,
-          groupId: host.groupId,
-        };
-      }),
-      update: existingHosts.map((host) => ({
+    const effectiveSchedulingType = (input.schedulingType ?? eventType.schedulingType) as SchedulingType;
+    const hostUpdates: Prisma.EventTypeUpdateInput["hosts"] = {
+      create: newHosts.map((host) => ({
+        ...host,
+        isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
+        priority: host.priority ?? 2,
+        weight: host.weight ?? 100,
+        scheduleId: host.scheduleId ?? null,
+        groupId: host.groupId,
+      })),
+      update: existingHosts.map((host) => ({
         where: {
           userId_eventTypeId: {
             userId: host.userId,
             eventTypeId: id,
           },
         },
+        data: {
+          isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
+          priority: host.priority ?? 2,
+          weight: host.weight ?? 100,
+          scheduleId: host.scheduleId ?? null,
+          groupId: host.groupId,
+        },
+      })),
+      ...(removedHosts.length > 0
+        ? {
+            deleteMany: {
+              OR: removedHosts.map((host) => ({
+                userId: host.userId,
+                eventTypeId: id,
+              })),
+            },
+          }
+        : {}),
+    };
+    data.hosts = hostUpdates;

These changes will:

  • Guarantee eventType.schedulingType is fetched for computing isFixed.
  • Include scheduleId on both create and update to keep data consistent.
  • Only emit deleteMany when there are hosts to remove, avoiding an empty-OR error.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Only process hosts if they are provided
if (hosts && Array.isArray(hosts)) {
const oldHostsSet = new Set(eventType.hosts.map((oldHost) => oldHost.userId));
const newHostsSet = new Set(hosts.map((oldHost) => oldHost.userId));
const existingHosts = hosts.filter((newHost) => oldHostsSet.has(newHost.userId));
const newHosts = hosts.filter((newHost) => !oldHostsSet.has(newHost.userId));
const removedHosts = eventType.hosts.filter((oldHost) => !newHostsSet.has(oldHost.userId));
const existingHosts = hosts.filter((newHost) => oldHostsSet.has(newHost.userId));
const newHosts = hosts.filter((newHost) => !oldHostsSet.has(newHost.userId));
const removedHosts = eventType.hosts.filter((oldHost) => !newHostsSet.has(oldHost.userId));
data.hosts = {
deleteMany: {
OR: removedHosts.map((host) => ({
userId: host.userId,
eventTypeId: id,
})),
},
create: newHosts.map((host) => {
return {
...host,
isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
groupId: host.groupId,
};
}),
update: existingHosts.map((host) => ({
where: {
userId_eventTypeId: {
data.hosts = {
deleteMany: {
OR: removedHosts.map((host) => ({
userId: host.userId,
eventTypeId: id,
},
},
data: {
isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
scheduleId: host.scheduleId ?? null,
groupId: host.groupId,
})),
},
})),
};
create: newHosts.map((host) => {
return {
...host,
isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
groupId: host.groupId,
};
}),
update: existingHosts.map((host) => ({
where: {
userId_eventTypeId: {
userId: host.userId,
eventTypeId: id,
},
},
data: {
isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
scheduleId: host.scheduleId ?? null,
groupId: host.groupId,
},
})),
};
}
// … around line 102 in update.handler.ts …
const eventType = await ctx.prisma.eventType.findUniqueOrThrow({
where: { id },
select: {
title: true,
locations: true,
description: true,
schedulingType: true, // ← newly added
hostGroups: true,
optionalGuestTeamMembers: true,
// …other fields as before
},
});
// … later, inside the `if (hosts && Array.isArray(hosts)) { … }` block …
const oldHostsSet = new Set(eventType.hosts.map(oldHost => oldHost.userId));
const newHostsSet = new Set(hosts.map(newHost => newHost.userId));
const existingHosts = hosts.filter(newHost => oldHostsSet.has(newHost.userId));
const newHosts = hosts.filter(newHost => !oldHostsSet.has(newHost.userId));
const removedHosts = eventType.hosts.filter(oldHost => !newHostsSet.has(oldHost.userId));
// Compute the schedulingType to use when input.schedulingType is omitted
const effectiveSchedulingType = (input.schedulingType ?? eventType.schedulingType) as SchedulingType;
// Build a single hosts-update payload instead of inline data.hosts = { … }
const hostUpdates: Prisma.EventTypeUpdateInput["hosts"] = {
create: newHosts.map(host => ({
...host,
isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
scheduleId: host.scheduleId ?? null, // ← newly included
groupId: host.groupId,
})),
update: existingHosts.map(host => ({
where: {
userId_eventTypeId: {
userId: host.userId,
eventTypeId: id,
},
},
data: {
isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
priority: host.priority ?? 2,
weight: host.weight ?? 100,
scheduleId: host.scheduleId ?? null,
groupId: host.groupId,
},
})),
// Only emit deleteMany when there are hosts to remove
...(removedHosts.length > 0
? {
deleteMany: {
OR: removedHosts.map(host => ({
userId: host.userId,
eventTypeId: id,
})),
},
}
: {}),
};
data.hosts = hostUpdates;
- // original inline assignment; remove this entire block:
- data.hosts = {
- deleteMany: {
- OR: removedHosts.map(host => ({
- userId: host.userId,
- eventTypeId: id,
- })),
- },
- create: newHosts.map(host => ({
- // as before, without scheduleId
- })),
- update: existingHosts.map(host => ({
- // as before
- })),
- };

@husniabad husniabad force-pushed the feature/add-team-member-as-optional-guest branch from fd33b44 to 3f546c4 Compare August 25, 2025 17:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (4)

102-193: Select schedulingType in the initial eventType fetch to support correct isFixed computation

You compute isFixed based on the effective scheduling type, but schedulingType isn’t selected here. Without it, effectiveSchedulingType falls back incorrectly and can yield wrong isFixed flags.

Apply this diff:

       select: {
         title: true,
         locations: true,
         description: true,
+        schedulingType: true,
         seatsPerTimeSlot: true,
         recurringEvent: true,

98-99: Don’t default optionalGuestTeamMembers; it blocks “clear all” and conflates no-op vs. replace

Undefined must mean “no change”, and [] must mean “clear all”. The current default to [] prevents clients from clearing previously saved optional guests.

Apply this diff:

-    hostGroups,
-    optionalGuestTeamMembers = [],
+    hostGroups,
+    optionalGuestTeamMembers,
     ...rest

453-474: Enable clearing, avoid double-writes, and tighten team membership check for optional guests

  • Gate should run when the field is provided (even if empty) so clients can clear all.
  • Use a Set for O(1) membership checks.
  • Replace entire relation in one go with set: [...] (handles both replace and clear) instead of set: [] + connect: [...].

This fixes a functional bug (cannot clear) and reduces redundant writes.

Apply this diff:

-    if (optionalGuestTeamMembers && optionalGuestTeamMembers.length > 0) {
-      // Deduplicate IDs to prevent duplicate connections
-      const ids = optionalGuestTeamMembers.map(({ id }) => id);
-      const uniqueIds = Array.from(new Set(ids));
-      if (uniqueIds.length !== ids.length) {
-        throw new TRPCError({
-          code: "BAD_REQUEST",
-          message: "Duplicate optional guest team members are not allowed.",
-        });
-      }
-      if (optionalGuestTeamMembers.every((each) => teamMemberIds.includes(each.id))) {
-        data.optionalGuestTeamMembers = {
-          set: [],
-          connect: uniqueIds.map((id) => ({ id })),
-        };
-      } else {
-        throw new TRPCError({
-          code: "FORBIDDEN",
-          message: "You can only assign team members as optional guests.",
-        });
-      }
-    }
+    if (optionalGuestTeamMembers !== undefined) {
+      // Deduplicate IDs to prevent duplicate connections
+      const ids = optionalGuestTeamMembers.map(({ id }) => id);
+      const uniqueIds = Array.from(new Set(ids));
+      if (uniqueIds.length !== ids.length) {
+        throw new TRPCError({
+          code: "BAD_REQUEST",
+          message: "Duplicate optional guest team members are not allowed.",
+        });
+      }
+      const teamMemberIdSet = new Set(teamMemberIds);
+      if (!uniqueIds.every((id) => teamMemberIdSet.has(id))) {
+        throw new TRPCError({
+          code: "FORBIDDEN",
+          message: "You can only assign team members as optional guests.",
+        });
+      }
+      // Replace the entire set; also clears when uniqueIds is []
+      data.optionalGuestTeamMembers = {
+        set: uniqueIds.map((id) => ({ id })),
+      };
+    }

484-525: Fix isFixed calculation, include scheduleId on create, and guard deleteMany when empty

  • isFixed currently derives from data.schedulingType, which may be undefined if schedulingType isn’t updated in this request, yielding wrong values. Use the effective type: input.schedulingType ?? eventType.schedulingType.
  • Missing scheduleId on create (present on update) creates inconsistent host rows.
  • deleteMany with OR: [] can error on some Prisma versions; add conditionally.

These are correctness issues that can break host assignment.

Apply this diff:

-    // Only process hosts if they are provided
-    if (hosts && Array.isArray(hosts)) {
-      const oldHostsSet = new Set(eventType.hosts.map((oldHost) => oldHost.userId));
-      const newHostsSet = new Set(hosts.map((oldHost) => oldHost.userId));
-
-      const existingHosts = hosts.filter((newHost) => oldHostsSet.has(newHost.userId));
-      const newHosts = hosts.filter((newHost) => !oldHostsSet.has(newHost.userId));
-      const removedHosts = eventType.hosts.filter((oldHost) => !newHostsSet.has(oldHost.userId));
-
-      data.hosts = {
-        deleteMany: {
-          OR: removedHosts.map((host) => ({
-            userId: host.userId,
-            eventTypeId: id,
-          })),
-        },
-        create: newHosts.map((host) => {
-          return {
-            ...host,
-            isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
-            priority: host.priority ?? 2,
-            weight: host.weight ?? 100,
-            groupId: host.groupId,
-          };
-        }),
-        update: existingHosts.map((host) => ({
-          where: {
-            userId_eventTypeId: {
-              userId: host.userId,
-              eventTypeId: id,
-            },
-          },
-          data: {
-            isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
-            priority: host.priority ?? 2,
-            weight: host.weight ?? 100,
-            scheduleId: host.scheduleId ?? null,
-            groupId: host.groupId,
-          },
-        })),
-      };
-    }
+    // Only process hosts if they are provided
+    if (hosts && Array.isArray(hosts)) {
+      const effectiveSchedulingType = (input.schedulingType ?? eventType.schedulingType) as SchedulingType;
+      const oldHostsSet = new Set(eventType.hosts.map((h) => h.userId));
+      const newHostsSet = new Set(hosts.map((h) => h.userId));
+
+      const existingHosts = hosts.filter((h) => oldHostsSet.has(h.userId));
+      const newHosts = hosts.filter((h) => !oldHostsSet.has(h.userId));
+      const removedHosts = eventType.hosts.filter((h) => !newHostsSet.has(h.userId));
+
+      const hostUpdates: Prisma.EventTypeUpdateInput["hosts"] = {
+        create: newHosts.map((host) => ({
+          ...host,
+          isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
+          priority: host.priority ?? 2,
+          weight: host.weight ?? 100,
+          scheduleId: host.scheduleId ?? null,
+          groupId: host.groupId,
+        })),
+        update: existingHosts.map((host) => ({
+          where: {
+            userId_eventTypeId: {
+              userId: host.userId,
+              eventTypeId: id,
+            },
+          },
+          data: {
+            isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
+            priority: host.priority ?? 2,
+            weight: host.weight ?? 100,
+            scheduleId: host.scheduleId ?? null,
+            groupId: host.groupId,
+          },
+        })),
+        ...(removedHosts.length > 0
+          ? {
+              deleteMany: {
+                OR: removedHosts.map((host) => ({
+                  userId: host.userId,
+                  eventTypeId: id,
+                })),
+              },
+            }
+          : {}),
+      };
+      data.hosts = hostUpdates;
+    }
🧹 Nitpick comments (4)
packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (3)

1318-1333: Good shift to “functional equivalence” for chunking; add DST-crossing test cases for stronger justification

The reframing aligns with our guidance to prefer native Date in hot paths. To fully justify the 1-hour tolerance, consider adding at least one case that actually crosses a DST boundary with non-UTC offsets (e.g., America/New_York: 2024-03-10 and 2024-11-03). That ensures we’re not masking issues and proves why the tolerance exists.

Apply this focused augmentation to the test cases array:

   const testCases = [
     {
       dateFrom: "2024-01-01T00:00:00Z",
       dateTo: "2024-04-01T00:00:00Z", // 91 days - requires chunking
       name: "91 days (minimal chunking)",
     },
     {
       dateFrom: "2024-01-01T00:00:00Z",
       dateTo: "2024-07-01T00:00:00Z", // 182 days - multiple chunks
       name: "182 days (multiple chunks)",
     },
+    // Cross a US DST transition (spring forward)
+    {
+      dateFrom: "2024-02-15T00:00:00-05:00",
+      dateTo: "2024-05-15T00:00:00-04:00",
+      name: "Spans DST start (America/New_York)",
+    },
+    // Cross a US DST transition (fall back)
+    {
+      dateFrom: "2024-09-01T00:00:00-04:00",
+      dateTo: "2024-12-15T00:00:00-05:00",
+      name: "Spans DST end (America/New_York)",
+    },
   ];

1400-1401: Prefer debug-level logging in tests to reduce noise

Info-level logs add noise to CI output. Drop this to debug so failures remain readable.

-      log.info(`${testCase.name} - Generated ${newChunks.length} functionally equivalent chunks`);
+      log.debug(`${testCase.name} - Generated ${newChunks.length} functionally equivalent chunks`);

1384-1398: Refine chunking test tolerance and update sanity-check command

The 1-hour blanket tolerance may conceal genuine off-by-one-hour issues when working purely with “Z” (UTC) timestamps. Instead:

  • Compute the tolerance dynamically:
    • If all start/end strings end with “Z” (Zulu), require exact equality (0 ms tolerance).
    • Otherwise allow up to 1 hour (60 * 60 * 1000 ms) for genuine offset/DST differences.
  • Additionally assert there are no unintended gaps or overlaps between consecutive chunks.

Targeted diff (inside packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts around lines 1384–1398):

       // Verify functionally equivalent chunking results
       expect(newChunks).toHaveLength(oldChunks.length);
 
-      for (let i = 0; i < oldChunks.length; i++) {
-        // Allow for small timezone differences (up to 1 hour) due to DST handling differences
-        const oldStart = new Date(oldChunks[i].start).getTime();
-        const newStart = new Date(newChunks[i].start).getTime();
-        const oldEnd = new Date(oldChunks[i].end).getTime();
-        const newEnd = new Date(newChunks[i].end).getTime();
-        const startDiff = Math.abs(oldStart - newStart);
-        const endDiff = Math.abs(oldEnd - newEnd);
-        // Allow up to 1 hour difference for timezone/DST handling
-        expect(startDiff).toBeLessThanOrEqual(60 * 60 * 1000); // 1 hour in ms
-        expect(endDiff).toBeLessThanOrEqual(60 * 60 * 1000); // 1 hour in ms
-      }
+      let prevNewEnd: number | null = null;
+      const oneHourMs = 60 * 60 * 1000;
+      const oneMinuteMs = 60 * 1000;
+
+      for (let i = 0; i < oldChunks.length; i++) {
+        const oldStart = new Date(oldChunks[i].start).getTime();
+        const newStart = new Date(newChunks[i].start).getTime();
+        const oldEnd = new Date(oldChunks[i].end).getTime();
+        const newEnd = new Date(newChunks[i].end).getTime();
+
+        // If all timestamps use Zulu (“Z”), require exact match; otherwise allow up to 1 hour
+        const isZulu =
+          oldChunks[i].start.endsWith("Z") &&
+          oldChunks[i].end.endsWith("Z") &&
+          newChunks[i].start.endsWith("Z") &&
+          newChunks[i].end.endsWith("Z");
+        const toleranceMs = isZulu ? 0 : oneHourMs;
+
+        expect(Math.abs(oldStart - newStart)).toBeLessThanOrEqual(toleranceMs);
+        expect(Math.abs(oldEnd - newEnd)).toBeLessThanOrEqual(toleranceMs);
+
+        // Ensure no overlaps and no gaps larger than the intended 1-minute resolution
+        if (prevNewEnd !== null) {
+          const gap = newStart - prevNewEnd;
+          expect(gap).toBeGreaterThanOrEqual(0);
+          expect(gap).toBeLessThanOrEqual(oneMinuteMs);
+        }
+        prevNewEnd = newEnd;
+      }

You can sanity-check this locally by running the package’s tests directly. For example:

pnpm --filter @calcom/app-store-googlecalendar run test -- -t "chunking logic should produce functionally equivalent results"

If this command needs adjustment in your workspace setup (e.g., using pnpm -F or yarn workspace), please adapt accordingly and confirm all assertions pass without unexpected DST-related discrepancies.

packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (1)

450-451: Membership checks look correct; minor perf/readability nit

Using includes() repeatedly over an array is fine here, but a Set for teamMemberIds would be more consistent with the optional guest block and O(1). Optional tweak only.

Example:

-    const teamMemberIds = await membershipRepo.listAcceptedTeamMemberIds({ teamId });
+    const teamMemberIds = await membershipRepo.listAcceptedTeamMemberIds({ teamId });
+    const teamMemberIdSet = new Set(teamMemberIds);
@@
-    if (hosts && !hosts.every((host) => teamMemberIds.includes(host.userId)) && !eventType.team?.parentId) {
+    if (hosts && !hosts.every((host) => teamMemberIdSet.has(host.userId)) && !eventType.team?.parentId) {
       throw new TRPCError({
         code: "FORBIDDEN",
       });
     }

Also applies to: 478-482

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fd33b44 and 3f546c4.

📒 Files selected for processing (3)
  • packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (2 hunks)
  • packages/features/eventtypes/components/tabs/advanced/OptionalTeamGuestSetting.tsx (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/features/eventtypes/components/tabs/advanced/OptionalTeamGuestSetting.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
🧠 Learnings (2)
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{ts,tsx} : Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js `.utc()` in hot paths like loops

Applied to files:

  • packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts
📚 Learning: 2025-08-21T13:44:06.784Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
🧬 Code graph analysis (1)
packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (1)
packages/platform/libraries/index.ts (2)
  • TRPCError (56-56)
  • SchedulingType (30-30)
🔇 Additional comments (1)
packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (1)

681-684: Good: Minimal select via Prisma.validator keeps return payload lean

Selecting only slug and schedulingType is appropriate and uses select (not include) per our Prisma query guidelines.

Comment on lines 98 to 99
optionalGuestTeamMembers = [],
...rest
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Quick self-check script: verify field gating and set semantics are applied

Run this to sanity-check that defaults are removed, the guard uses !== undefined, and set: is used for optionalGuestTeamMembers.

Also applies to: 453-474, 484-525


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Ensure no default assignment remains
rg -nP 'optionalGuestTeamMembers\s*=\s*\[\]' packages/trpc/server/routers/viewer/eventTypes/update.handler.ts || echo "OK: no default []"

# 2) Ensure guard is '!== undefined'
rg -n 'optionalGuestTeamMembers\s*!==\s*undefined' packages/trpc/server/routers/viewer/eventTypes/update.handler.ts

# 3) Ensure prisma write uses a single set:
rg -n 'optionalGuestTeamMembers.+set:' -C2 packages/trpc/server/routers/viewer/eventTypes/update.handler.ts

# 4) Ensure hosts block computes effectiveSchedulingType and guards deleteMany:
rg -n 'effectiveSchedulingType' -C2 packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
rg -n 'deleteMany' -n -C2 packages/trpc/server/routers/viewer/eventTypes/update.handler.ts

Length of output: 287


Remove default assignment for optionalGuestTeamMembers

Your snippet still has a default of = [] on line 98, so the change to remove default values hasn’t been applied. This must be addressed before merging:

  • In packages/trpc/server/routers/viewer/eventTypes/update.handler.ts at line 98, remove the = [] default on the optionalGuestTeamMembers parameter.
  • After removing the default, verify that you guard updates with a strict undefined check, i.e.
    if (optionalGuestTeamMembers !== undefined) {
      // prisma update using { set: optionalGuestTeamMembers }
    }
  • Ensure your Prisma update only uses a single set: operation for optionalGuestTeamMembers.
  • Likewise, confirm in the hosts block that you compute effectiveSchedulingType correctly and guard any deleteMany calls behind a check (e.g., if (effectiveSchedulingType === …)).

Please apply these fixes to align with the intended field-gating and set semantics.

🤖 Prompt for AI Agents
In packages/trpc/server/routers/viewer/eventTypes/update.handler.ts around lines
98-99, remove the default assignment "= []" from the optionalGuestTeamMembers
parameter and instead treat it as possibly undefined; update the handler so any
Prisma update for optionalGuestTeamMembers is only executed when
optionalGuestTeamMembers !== undefined, using a single set: operation inside
that guarded update; also in the hosts block compute effectiveSchedulingType
before making changes and wrap any deleteMany calls behind a strict check of
effectiveSchedulingType (e.g., only call deleteMany when effectiveSchedulingType
matches the condition) so the update only modifies fields when explicitly
provided.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (2)

102-193: Select schedulingType in the initial eventType query to support effective isFixed computation

eventType.schedulingType is needed for the effective calculation when input.schedulingType is omitted. Add it to the select.

   const eventType = await ctx.prisma.eventType.findUniqueOrThrow({
     where: { id },
     select: {
       title: true,
       locations: true,
       description: true,
+      schedulingType: true,
       seatsPerTimeSlot: true,
       recurringEvent: true,

493-534: Compute effective schedulingType, include scheduleId on create, and guard deleteMany to avoid OR: []

isFixed is derived from data.schedulingType, which may be undefined when schedulingType isn’t updated in this request. Also, deleteMany with OR: [] may error, and scheduleId is set on update but not on create.

Apply this diff:

-    // Only process hosts if they are provided
-    if (hosts && Array.isArray(hosts)) {
-      const oldHostsSet = new Set(eventType.hosts.map((oldHost) => oldHost.userId));
-      const newHostsSet = new Set(hosts.map((oldHost) => oldHost.userId));
+    // Only process hosts if they are provided
+    if (hosts && Array.isArray(hosts)) {
+      const oldHostsSet = new Set(eventType.hosts.map((oldHost) => oldHost.userId));
+      const newHostsSet = new Set(hosts.map((oldHost) => oldHost.userId));
@@
-      data.hosts = {
-        deleteMany: {
-          OR: removedHosts.map((host) => ({
-            userId: host.userId,
-            eventTypeId: id,
-          })),
-        },
-        create: newHosts.map((host) => {
-          return {
-            ...host,
-            isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
-            priority: host.priority ?? 2,
-            weight: host.weight ?? 100,
-            groupId: host.groupId,
-          };
-        }),
-        update: existingHosts.map((host) => ({
-          where: {
-            userId_eventTypeId: {
-              userId: host.userId,
-              eventTypeId: id,
-            },
-          },
-          data: {
-            isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
-            priority: host.priority ?? 2,
-            weight: host.weight ?? 100,
-            scheduleId: host.scheduleId ?? null,
-            groupId: host.groupId,
-          },
-        })),
-      };
+      const effectiveSchedulingType = (input.schedulingType ?? eventType.schedulingType) as SchedulingType;
+      const hostUpdates: Prisma.EventTypeUpdateInput["hosts"] = {
+        create: newHosts.map((host) => ({
+          ...host,
+          isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
+          priority: host.priority ?? 2,
+          weight: host.weight ?? 100,
+          scheduleId: host.scheduleId ?? null,
+          groupId: host.groupId,
+        })),
+        update: existingHosts.map((host) => ({
+          where: {
+            userId_eventTypeId: {
+              userId: host.userId,
+              eventTypeId: id,
+            },
+          },
+          data: {
+            isFixed: effectiveSchedulingType === SchedulingType.COLLECTIVE || host.isFixed,
+            priority: host.priority ?? 2,
+            weight: host.weight ?? 100,
+            scheduleId: host.scheduleId ?? null,
+            groupId: host.groupId,
+          },
+        })),
+        ...(removedHosts.length > 0
+          ? {
+              deleteMany: {
+                OR: removedHosts.map((host) => ({
+                  userId: host.userId,
+                  eventTypeId: id,
+                })),
+              },
+            }
+          : {}),
+      };
+      data.hosts = hostUpdates;
🧹 Nitpick comments (1)
packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (1)

583-589: Remove console.log noise (and potential PII) from server path

Use logger.debug with minimal structured fields or drop entirely. Avoid logging arrays of links or full payloads.

-  console.log("multiplePrivateLinks", multiplePrivateLinks);
+  // logger.debug({ eventTypeId: id, multiplePrivateLinksCount: multiplePrivateLinks?.length ?? 0 }, "multiplePrivateLinks update");
@@
-  console.log("connectedLinks", connectedLinks);
+  // logger.debug({ eventTypeId: id, connectedLinksCount: connectedLinks.length }, "connected links fetched");
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3f546c4 and b6c02ee.

📒 Files selected for processing (1)
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
🧠 Learnings (2)
📚 Learning: 2025-08-17T22:00:16.329Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts:117-126
Timestamp: 2025-08-17T22:00:16.329Z
Learning: In calcom/cal.com PR #22995, packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts, the enabled input parameter in the update endpoint is intentionally not forwarded to aiService.updateAgentConfiguration() as the enabled/disabled agent functionality is not required at the moment (per maintainer Udit-takkar). Future reviews should not flag this as missing functionality unless requirements change.

Applied to files:

  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
📚 Learning: 2025-08-21T13:44:06.784Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
🧬 Code graph analysis (1)
packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (1)
packages/platform/libraries/index.ts (2)
  • TRPCError (56-56)
  • SchedulingType (30-30)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (2)
packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (2)

98-99: Good extraction: keeps optionalGuestTeamMembers out of ...rest

Destructuring optionalGuestTeamMembers separately prevents unintended persistence via ...rest. LGTM.


690-693: Typed select via Prisma.validator is a nice improvement

This keeps the update projection type-safe and includes schedulingType for downstream logic. LGTM.

@husniabad husniabad force-pushed the feature/add-team-member-as-optional-guest branch from b6c02ee to 1c59531 Compare August 25, 2025 18:37
@husniabad
Copy link
Contributor Author

Hi @kart1ka I have removed the changes done by me on the file apps/web/lib/csp.ts since it has been fixed from the source, Also I fixed some bugs including CodeRabbitAi suggestion
please check them out

@husniabad husniabad requested a review from kart1ka August 25, 2025 19:05
@github-actions
Copy link
Contributor

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label Sep 17, 2025
Adds a setting to event types to automatically invite selected team members as optional attendees to meetings. This does not check their availability. Closes CAL-5091
@husniabad husniabad force-pushed the feature/add-team-member-as-optional-guest branch from 1c59531 to 1865404 Compare September 21, 2025 18:52
@husniabad
Copy link
Contributor Author

Good work @husniabad!

Hi @kart1ka and @emrysal this PR is ready, and I resolved merge conflicts again, could you please review it!

@github-actions github-actions bot removed the Stale label Sep 22, 2025
@dhairyashiil dhairyashiil marked this pull request as draft November 21, 2025 21:03
@devin-ai-integration devin-ai-integration bot added the Low priority Created by Linear-GitHub Sync label Jan 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙋 Bounty claim community Created by Linear-GitHub Sync event-types area: event types, event-types ✨ feature New feature or request Low priority Created by Linear-GitHub Sync ❗️ migrations contains migration files size/XL teams area: teams, round robin, collective, managed event-types $25

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CAL-5091] additional settings: "add team member as optional guest

4 participants