Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 62 additions & 4 deletions apps/web/modules/bookings/components/AvailableTimeSlots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import { useNonEmptyScheduleDays } from "@calcom/features/schedules/lib/use-sche
import { useSlotsForAvailableDates } from "@calcom/features/schedules/lib/use-schedule/useSlotsForDate";
import { PUBLIC_INVALIDATE_AVAILABLE_SLOTS_ON_BOOKING_FORM } from "@calcom/lib/constants";
import { localStorage } from "@calcom/lib/webstorage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
import classNames from "@calcom/ui/classNames";
import { Button } from "@calcom/ui/components/button";

import { AvailableTimesHeader } from "@calcom/web/modules/bookings/components/AvailableTimesHeader";
import type { useScheduleForEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
Expand Down Expand Up @@ -50,6 +52,7 @@ type AvailableTimeSlotsProps = {
unavailableTimeSlots: string[];
confirmButtonDisabled?: boolean;
onAvailableTimeSlotSelect: (time: string) => void;
onLoadNextRoundRobinChunk?: () => void;
};

/**
Expand All @@ -74,8 +77,10 @@ export const AvailableTimeSlots = ({
confirmButtonDisabled,
confirmStepClassNames,
onAvailableTimeSlotSelect,
onLoadNextRoundRobinChunk,
...props
}: AvailableTimeSlotsProps) => {
const { t } = useLocale();
const selectedDate = useBookerStoreContext((state) => state.selectedDate);

const setSeatedEventData = useBookerStoreContext((state) => state.setSeatedEventData);
Expand Down Expand Up @@ -108,6 +113,7 @@ export const AvailableTimeSlots = ({
};

const scheduleData = schedule?.data;
const roundRobinChunkInfo = scheduleData?.roundRobinChunkInfo;

const nonEmptyScheduleDays = useNonEmptyScheduleDays(scheduleData?.slots);
const nonEmptyScheduleDaysFromSelectedDate = nonEmptyScheduleDays.filter(
Expand Down Expand Up @@ -184,15 +190,60 @@ export const AvailableTimeSlots = ({
[overlayCalendarToggled, onTimeSelect, seatsPerTimeSlot, skipConfirmStep, toggleConfirmButton]
);

const showLoadMoreButton =
roundRobinChunkInfo?.hasMoreNonFixedHosts && onLoadNextRoundRobinChunk;
Comment on lines +193 to +194

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

showLoadMoreButton is truthy when roundRobinChunkInfo?.hasMoreNonFixedHosts && onLoadNextRoundRobinChunk: but both renderLoadMoreButton() and the sticky footer (line 303) are gated on showLoadMoreButton. However showLoadMoreButton is used in the scrollable container's className at line 274 with "pb-14". If onLoadNextRoundRobinChunk is defined but hasMoreNonFixedHosts is false, no button is shown and no padding is applied: correct. But the inverse case: if hasMoreNonFixedHosts is true and the callback is undefined (which can happen during SSR or if the parent forgets to pass the callback), the padding is never added but logically the button 'should' appear. More importantly, the Button inside renderLoadMoreButton() re-references onLoadNextRoundRobinChunk directly on line 230 without the nullish guard that showLoadMoreButton provides: if someone calls renderLoadMoreButton() outside this guard, a TypeError: onLoadNextRoundRobinChunk is not a function can occur.

Suggested fix
  const showLoadMoreButton =
    Boolean(roundRobinChunkInfo?.hasMoreNonFixedHosts && onLoadNextRoundRobinChunk);
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: apps/web/modules/bookings/components/AvailableTimeSlots.tsx
Lines: 193-194
Issue Type: functional-medium
Severity: medium

Issue Description:
`showLoadMoreButton` is truthy when `roundRobinChunkInfo?.hasMoreNonFixedHosts && onLoadNextRoundRobinChunk`: but both `renderLoadMoreButton()` and the sticky footer (line 303) are gated on `showLoadMoreButton`. However `showLoadMoreButton` is used in the scrollable container's className at line 274 with `"pb-14"`. If `onLoadNextRoundRobinChunk` is defined but `hasMoreNonFixedHosts` is `false`, no button is shown and no padding is applied: correct. But the inverse case: if `hasMoreNonFixedHosts` is `true` and the callback is `undefined` (which can happen during SSR or if the parent forgets to pass the callback), the padding is never added but logically the button 'should' appear. More importantly, the `Button` inside `renderLoadMoreButton()` re-references `onLoadNextRoundRobinChunk` directly on line 230 without the nullish guard that `showLoadMoreButton` provides: if someone calls `renderLoadMoreButton()` outside this guard, a `TypeError: onLoadNextRoundRobinChunk is not a function` can occur.

Current Code:
  const showLoadMoreButton =
    roundRobinChunkInfo?.hasMoreNonFixedHosts && onLoadNextRoundRobinChunk;

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue


const renderLoadMoreButton = () => {
if (!showLoadMoreButton) return null;

const totalLoadedHosts =
roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize +
roundRobinChunkInfo.loadedNonFixedHosts;
Comment on lines +199 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

The totalLoadedHosts calculation uses roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize + roundRobinChunkInfo.loadedNonFixedHosts. This is only valid when chunkOffset represents the 0-based index of the last loaded chunk. However, in manual chunking mode the server returns the requested chunk's offset directly, which is correct. But in sequential (auto) mode, the offset is the index of the chunk where availability was found: not necessarily the total number of chunks loaded. For example, if chunk 0 had no availability and chunk 1 did, chunkOffset=1 and loadedNonFixedHosts = chunkSize, so totalLoadedHosts = 1*chunkSize + chunkSize = 2*chunkSize, suggesting two chunks were loaded. This matches the auto-sequential behavior. But the progress bar will show an incorrect percentage if chunks have variable sizes (the last chunk may have fewer hosts than chunkSize).

Suggested fix
      const totalLoadedHosts =
        (roundRobinChunkInfo.chunkOffset + 1) * roundRobinChunkInfo.chunkSize;
      // Note: last chunk may be smaller; clamp to totalNonFixedHosts
      const clampedTotalLoadedHosts = Math.min(
        totalLoadedHosts,
        roundRobinChunkInfo.totalNonFixedHosts || roundRobinChunkInfo.totalHosts
      );
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: apps/web/modules/bookings/components/AvailableTimeSlots.tsx
Lines: 199-201
Issue Type: functional-medium
Severity: medium

Issue Description:
The `totalLoadedHosts` calculation uses `roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize + roundRobinChunkInfo.loadedNonFixedHosts`. This is only valid when `chunkOffset` represents the 0-based index of the **last loaded** chunk. However, in manual chunking mode the server returns the requested chunk's offset directly, which is correct. But in sequential (auto) mode, the offset is the index of the chunk where availability was found: not necessarily the total number of chunks loaded. For example, if chunk 0 had no availability and chunk 1 did, `chunkOffset=1` and `loadedNonFixedHosts = chunkSize`, so `totalLoadedHosts = 1*chunkSize + chunkSize = 2*chunkSize`, suggesting two chunks were loaded. This matches the auto-sequential behavior. But the progress bar will show an incorrect percentage if chunks have variable sizes (the last chunk may have fewer hosts than `chunkSize`).

Current Code:
      const totalLoadedHosts =
        roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize +
        roundRobinChunkInfo.loadedNonFixedHosts;

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Comment on lines +199 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

The progress calculation for the "Load more" button computes totalLoadedHosts using chunkOffset * chunkSize + loadedNonFixedHosts. This formula is incorrect: chunkOffset is a zero-based index of the current chunk, not the count of fully processed previous chunks. When the user is on chunk 0 (the first chunk), chunkOffset * chunkSize = 0, so totalLoadedHosts = loadedNonFixedHosts which is correct. But on chunk 1, totalLoadedHosts = 1 * chunkSize + loadedNonFixedHosts = chunkSize + chunkSize, double-counting. The correct formula to get the cumulative loaded count should be (chunkOffset + 1) * chunkSize capped at totalNonFixedHosts, or more simply (chunkOffset * chunkSize) + loadedNonFixedHosts: BUT only if loadedNonFixedHosts represents the count for the current chunk alone (which it does per the RoundRobinChunkInfo type). That part is actually correct. However, looking at the auto-chunking logic in util.ts: when hasMoreNonFixedHosts = true the user is shown the button, and chunkOffset is the index of the last loaded chunk. The calculation is in fact (chunkOffset * chunkSize) + loadedNonFixedHosts. For chunk 0 with 21 loaded: 0*50+21=21. For chunk 1 with 21 loaded: 1*50+21=71. This actually appears to be double-counting (50+21=71 when only 71 total were loaded, which happens to be numerically right but accidentally). The real bug is when loadedNonFixedHosts < chunkSize (last chunk): e.g., total=105, chunkSize=21, chunkOffset=4, loadedNonFixedHosts=21: 4*21+21=105 ✓. But on intermediate chunks, a user could receive progressPercentage > 100 if roundRobinChunkInfo becomes stale and totalHosts is 0, which is guarded by the totalHosts > 0 check. The Math.min(100, ...) caps it. The bigger concern is that the formula conflates chunkOffset (0-based index of the most recently fetched chunk) with the number of fully-fetched previous chunks. If in manual mode chunkOffset = 3 but chunks 0-2 were never fetched, the progress would report 3*chunkSize + loadedNonFixedHosts as if all 3 prior chunks were processed.

Suggested fix
      const totalLoadedHosts = Math.min(
        (roundRobinChunkInfo.chunkOffset + 1) * roundRobinChunkInfo.chunkSize,
        roundRobinChunkInfo.totalNonFixedHosts || roundRobinChunkInfo.totalHosts || 0
      );
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: apps/web/modules/bookings/components/AvailableTimeSlots.tsx
Lines: 199-201
Issue Type: functional-medium
Severity: medium

Issue Description:
The progress calculation for the "Load more" button computes `totalLoadedHosts` using `chunkOffset * chunkSize + loadedNonFixedHosts`. This formula is incorrect: `chunkOffset` is a zero-based index of the current chunk, not the count of fully processed previous chunks. When the user is on chunk 0 (the first chunk), `chunkOffset * chunkSize = 0`, so `totalLoadedHosts = loadedNonFixedHosts` which is correct. But on chunk 1, `totalLoadedHosts = 1 * chunkSize + loadedNonFixedHosts = chunkSize + chunkSize`, double-counting. The correct formula to get the cumulative loaded count should be `(chunkOffset + 1) * chunkSize` capped at `totalNonFixedHosts`, or more simply `(chunkOffset * chunkSize) + loadedNonFixedHosts`: BUT only if `loadedNonFixedHosts` represents the count for the current chunk alone (which it does per the `RoundRobinChunkInfo` type). That part is actually correct. However, looking at the auto-chunking logic in `util.ts`: when `hasMoreNonFixedHosts = true` the user is shown the button, and `chunkOffset` is the index of the last loaded chunk. The calculation is in fact `(chunkOffset * chunkSize) + loadedNonFixedHosts`. For chunk 0 with 21 loaded: `0*50+21=21`. For chunk 1 with 21 loaded: `1*50+21=71`. This actually appears to be double-counting (50+21=71 when only 71 total were loaded, which happens to be numerically right but accidentally). The real bug is when `loadedNonFixedHosts < chunkSize` (last chunk): e.g., total=105, chunkSize=21, chunkOffset=4, loadedNonFixedHosts=21: `4*21+21=105` ✓. But on intermediate chunks, a user could receive `progressPercentage > 100` if `roundRobinChunkInfo` becomes stale and `totalHosts` is 0, which is guarded by the `totalHosts > 0` check. The `Math.min(100, ...)` caps it. The bigger concern is that the formula conflates `chunkOffset` (0-based index of the most recently fetched chunk) with the number of fully-fetched previous chunks. If in manual mode `chunkOffset = 3` but chunks 0-2 were never fetched, the progress would report `3*chunkSize + loadedNonFixedHosts` as if all 3 prior chunks were processed.

Current Code:
      const totalLoadedHosts =
        roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize +
        roundRobinChunkInfo.loadedNonFixedHosts;

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Comment on lines +199 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

Progress percentage calculation is incorrect. chunkOffset * chunkSize computes the number of non-fixed hosts in previously completed chunks. Adding loadedNonFixedHosts (the count in the current chunk) would double-count the current chunk because chunkOffset already counts the current chunk index (0-based). For example, at offset=1 with chunkSize=21 and loadedNonFixedHosts=21, the formula gives 21*21+21=462 out of 105 total → 100%, but only 2 chunks of 5 have been loaded, so the true progress is ~40%.

The correct formula is: totalLoadedHosts = (chunkOffset) * chunkSize + loadedNonFixedHosts: but chunkOffset is 0-based, so it already equals the number of completed prior chunks. The current code at line 200 does exactly this (chunkOffset * chunkSize + loadedNonFixedHosts). The bug is subtle: when the server returns chunkOffset=1 it means the first index at which data was returned, i.e., 1 prior chunk has been fully processed: so the formula is actually correct for sequential mode but may give >100% in manual mode when chunkOffset jumps ahead. The real issue is the absence of a Math.min guard for the numerator.

Suggested fix
      const totalLoadedHosts = Math.min(
        roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize +
          roundRobinChunkInfo.loadedNonFixedHosts,
        roundRobinChunkInfo.totalNonFixedHosts || roundRobinChunkInfo.totalHosts || 0
      );
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: apps/web/modules/bookings/components/AvailableTimeSlots.tsx
Lines: 199-201
Issue Type: functional-medium
Severity: medium

Issue Description:
Progress percentage calculation is incorrect. `chunkOffset * chunkSize` computes the number of non-fixed hosts in *previously completed* chunks. Adding `loadedNonFixedHosts` (the count in the *current* chunk) would double-count the current chunk because `chunkOffset` already counts the current chunk index (0-based). For example, at offset=1 with chunkSize=21 and loadedNonFixedHosts=21, the formula gives 21*21+21=462 out of 105 total → 100%, but only 2 chunks of 5 have been loaded, so the true progress is ~40%.

The correct formula is: `totalLoadedHosts = (chunkOffset) * chunkSize + loadedNonFixedHosts`: but `chunkOffset` is 0-based, so it already equals the number of *completed prior* chunks. The current code at line 200 does exactly this (`chunkOffset * chunkSize + loadedNonFixedHosts`). The bug is subtle: when the server returns `chunkOffset=1` it means the *first index at which data was returned*, i.e., 1 prior chunk has been fully processed: so the formula is actually correct for sequential mode but may give >100% in manual mode when `chunkOffset` jumps ahead. The real issue is the absence of a `Math.min` guard for the numerator.

Current Code:
      const totalLoadedHosts =
        roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize +
        roundRobinChunkInfo.loadedNonFixedHosts;

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Comment on lines +193 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

totalLoadedHosts computation can overflow the actual total. When roundRobinChunkInfo.chunkOffset is 3 and chunkSize is 21, chunkOffset * chunkSize = 63 already larger than the total number of non-fixed hosts that were actually loaded. The issue: chunkOffset is the 0-based index of the current chunk, so previously loaded non-fixed hosts are chunkOffset * chunkSize, but the very first chunk is offset 0, meaning hosts loaded before the current chunk = chunkOffset * chunkSize, plus loadedNonFixedHosts in the current chunk. That formula looks correct: but only when loadedNonFixedHosts refers to hosts in the current chunk alone. Looking at the server code in util.ts, loadedNonFixedHosts: hostChunk.length is indeed per-chunk. So the formula actually skips all intermediate chunks' host counts and is likely to produce a progressPercentage that jumps non-linearly and can exceed 100 before Math.min. While Math.min(100, ...) clamps the display, the underlying counter logic is semantically wrong for non-manual (sequential) chunking after a second automatic batch fires.

Suggested fix
      // chunkOffset is 0-based; hosts loaded up to and including the current chunk:
      const totalLoadedHosts = Math.min(
        (roundRobinChunkInfo.chunkOffset + 1) * roundRobinChunkInfo.chunkSize,
        roundRobinChunkInfo.totalNonFixedHosts || roundRobinChunkInfo.totalHosts || 0
      );
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: apps/web/modules/bookings/components/AvailableTimeSlots.tsx
Lines: 193-201
Issue Type: functional-medium
Severity: medium

Issue Description:
`totalLoadedHosts` computation can overflow the actual total. When `roundRobinChunkInfo.chunkOffset` is 3 and `chunkSize` is 21, `chunkOffset * chunkSize` = 63 already larger than the total number of non-fixed hosts that were actually loaded. The issue: `chunkOffset` is the 0-based index of the *current* chunk, so previously loaded non-fixed hosts are `chunkOffset * chunkSize`, but the very first chunk is offset 0, meaning hosts loaded before the current chunk = `chunkOffset * chunkSize`, plus `loadedNonFixedHosts` in the current chunk. That formula looks correct: but only when `loadedNonFixedHosts` refers to hosts in the *current* chunk alone. Looking at the server code in `util.ts`, `loadedNonFixedHosts: hostChunk.length` is indeed per-chunk. So the formula actually skips all intermediate chunks' host counts and is likely to produce a `progressPercentage` that jumps non-linearly and can exceed 100 before `Math.min`. While `Math.min(100, ...)` clamps the display, the underlying counter logic is semantically wrong for non-manual (sequential) chunking after a second automatic batch fires.

Current Code:
      const totalLoadedHosts =
        roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize +
        roundRobinChunkInfo.loadedNonFixedHosts;

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

const totalHosts = roundRobinChunkInfo.totalNonFixedHosts || roundRobinChunkInfo.totalHosts || 0;
const progressPercentage =
totalHosts > 0 ? Math.min(100, Math.round((totalLoadedHosts / totalHosts) * 100)) : 0;
const circumference = 2 * Math.PI * 8;
const dashArray = (progressPercentage / 100) * circumference;

const ProgressIcon = (
<svg width="20" height="20" viewBox="0 0 20 20" className="text-emphasis" aria-hidden>
<circle cx="10" cy="10" r="8" stroke="rgba(255,255,255,0.35)" strokeWidth="2" fill="none" />
<circle
cx="10"
cy="10"
r="8"
stroke="#FFFFFF"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security Medium

Hard-coded color value #FFFFFF is used for the SVG progress ring stroke. This is a presentational concern but it means the button icon will be invisible on light-themed or high-contrast mode backgrounds. More importantly, using hard-coded hex literals rather than CSS custom-property tokens bypasses the application's theming contract and accessibility guarantees (prefers-contrast: more). The surrounding circle already uses a CSS class text-emphasis, suggesting the intent was to use the design token; the progress arc should follow suit.

Suggested change
stroke="#FFFFFF"
stroke="currentColor"
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: apps/web/modules/bookings/components/AvailableTimeSlots.tsx
Lines: 215-215
Issue Type: security-medium
Severity: medium

Issue Description:
Hard-coded color value `#FFFFFF` is used for the SVG progress ring stroke. This is a presentational concern but it means the button icon will be invisible on light-themed or high-contrast mode backgrounds. More importantly, using hard-coded hex literals rather than CSS custom-property tokens bypasses the application's theming contract and accessibility guarantees (`prefers-contrast: more`). The surrounding circle already uses a CSS class `text-emphasis`, suggesting the intent was to use the design token; the progress arc should follow suit.

Current Code:
          stroke="#FFFFFF"

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

strokeWidth="2"
fill="none"
strokeDasharray={`${dashArray} ${circumference}`}
strokeLinecap="round"
transform="rotate(-90 10 10)"
/>
</svg>
);

return (
<Button
type="button"
color="primary"
className="w-full justify-center gap-2"
onClick={onLoadNextRoundRobinChunk}
disabled={isLoading || schedule?.isFetching}
CustomStartIcon={ProgressIcon}
aria-label={t("round_robin_load_next_hosts")}>
{t("round_robin_load_next_hosts")}
</Button>
);
};
Comment on lines +196 to +237

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Performance Medium

renderLoadMoreButton is defined as a plain function inside the component body and re-creates the entire ProgressIcon JSX subtree (including circumference, dashArray calculations and two SVG circle nodes) on every render, regardless of whether the chunk info has changed. Since showLoadMoreButton already guards the container, the function should at minimum be wrapped in useMemo to avoid re-computing the SVG on every parent re-render. The recalculation is cheap individually but happens on every slot-hover or form-field interaction.

Suggested fix
  const loadMoreButton = useMemo(() => {
    if (!showLoadMoreButton) return null;

    const totalLoadedHosts =
      roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize +
      roundRobinChunkInfo.loadedNonFixedHosts;
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: apps/web/modules/bookings/components/AvailableTimeSlots.tsx
Lines: 196-237
Issue Type: performance-medium
Severity: medium

Issue Description:
`renderLoadMoreButton` is defined as a plain function inside the component body and re-creates the entire `ProgressIcon` JSX subtree (including `circumference`, `dashArray` calculations and two SVG circle nodes) on every render, regardless of whether the chunk info has changed. Since `showLoadMoreButton` already guards the container, the function should at minimum be wrapped in `useMemo` to avoid re-computing the SVG on every parent re-render. The recalculation is cheap individually but happens on every slot-hover or form-field interaction.

Current Code:
  const renderLoadMoreButton = () => {
    if (!showLoadMoreButton) return null;

    const totalLoadedHosts =
      roundRobinChunkInfo.chunkOffset * roundRobinChunkInfo.chunkSize +
      roundRobinChunkInfo.loadedNonFixedHosts;

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue


return (
<>
<div className="relative flex h-full flex-col">
<div className={classNames(`flex`, `${customClassNames?.availableTimeSlotsContainer}`)}>
{isLoading ? (
<div className="mb-3 h-8" />
) : (
slotsPerDay.length > 0 &&
slotsPerDay.map((slots) => {
// Check if this day is OOO - since OOO is date-level, just check the first slot
const isOOODay = slots.slots.length > 0 && slots.slots[0]?.away;
return (
<AvailableTimesHeader
Expand Down Expand Up @@ -220,9 +271,10 @@ export const AvailableTimeSlots = ({
className={classNames(
limitHeight && "no-scrollbar grow overflow-auto md:h-[400px]",
!limitHeight && "flex h-full w-full flex-row gap-4",
showLoadMoreButton && "pb-14",
`${customClassNames?.availableTimeSlotsContainer}`
)}>
{isLoading && // Shows exact amount of days as skeleton.
{isLoading &&
Array.from({ length: 1 + (extraDays ?? 0) }).map((_, i) => <AvailableTimesSkeleton key={i} />)}
{!isLoading &&
slotsPerDay.length > 0 &&
Expand All @@ -247,6 +299,12 @@ export const AvailableTimeSlots = ({
</div>
))}
</div>
</>

{showLoadMoreButton && (
<div className="sticky right-0 bottom-0 left-0 border-subtle border-t bg-default px-4 py-3">
{renderLoadMoreButton()}
</div>
)}
</div>
);
};
2 changes: 2 additions & 0 deletions apps/web/modules/bookings/components/Booker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const BookerComponent = ({
eventMetaChildren,
roundRobinHideOrgAndTeam,
showNoAvailabilityDialog,
onLoadNextRoundRobinChunk,
}: BookerProps & WrappedBookerProps) => {
const searchParams = useCompatSearchParams();
const isPlatformBookerEmbed = useIsPlatformBookerEmbed();
Expand Down Expand Up @@ -540,6 +541,7 @@ const BookerComponent = ({
watchedCfToken={watchedCfToken}
confirmButtonDisabled={confirmButtonDisabled}
confirmStepClassNames={customClassNames?.confirmStep}
onLoadNextRoundRobinChunk={onLoadNextRoundRobinChunk}
/>
</BookerSection>
</AnimatePresence>
Expand Down
24 changes: 23 additions & 1 deletion apps/web/modules/bookings/components/BookerWebWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useBookerLayout } from "@calcom/features/bookings/Booker/components/hoo
import { useBookingForm } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
import { useBookings } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
import { useCalendars } from "@calcom/features/bookings/Booker/components/hooks/useCalendars";
import { useRoundRobinChunking } from "@calcom/features/bookings/Booker/components/hooks/useRoundRobinChunking";
import { useSlots } from "@calcom/features/bookings/Booker/components/hooks/useSlots";
import { useVerifyCode } from "@calcom/features/bookings/Booker/components/hooks/useVerifyCode";
import { useVerifyEmail } from "@calcom/features/bookings/Booker/components/hooks/useVerifyEmail";
Expand Down Expand Up @@ -93,7 +94,10 @@ const BookerWebWrapperComponent = (props: BookerWebWrapperAtomProps) => {
});

const [dayCount] = useBookerStoreContext((state) => [state.dayCount, state.setDayCount], shallow);

const [roundRobinChunkSettings, setRoundRobinChunkSettings] = useBookerStoreContext(
(state) => [state.roundRobinChunkSettings, state.setRoundRobinChunkSettings],
shallow
);
const { data: session } = useSession();
const routerQuery = useRouterQuery();
const hasSession = !!session;
Expand Down Expand Up @@ -155,6 +159,21 @@ const BookerWebWrapperComponent = (props: BookerWebWrapperAtomProps) => {
useApiV2: props.useApiV2,
bookerLayout,
...(props.entity.orgSlug ? { orgSlug: props.entity.orgSlug } : {}),
roundRobinChunkSettings: roundRobinChunkSettings ?? undefined,
});
const { roundRobinChunkInfo, handleLoadNextRoundRobinChunk } =
useRoundRobinChunking({
roundRobinChunkInfo: schedule.data?.roundRobinChunkInfo,
isFetching: schedule.isFetching,
roundRobinChunkSettings,
setRoundRobinChunkSettings,
resetDeps: [
props.username,
props.eventSlug,
props.entity.orgSlug,
props.entity.eventTypeId,
event.data?.id,
],
Comment on lines +170 to +176

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

Once a user clicks "Load more availability", roundRobinChunkSettings stays in manual mode until this reset list changes. The problem is that the list only keys off event identity, while useScheduleForEvent also sends the manual chunk offset on every date/month/duration/team-member change (packages/features/bookings/Booker/utils/event.ts:125-126). A concrete failure case is: open chunk 3 for one day, then pick a different day: the next availability request still starts at chunk 3, so chunks 0-2 are skipped and the new day shows incomplete availability.

Suggested fix
      resetDeps: [
        props.username,
        props.eventSlug,
        props.entity.orgSlug,
        props.entity.eventTypeId,
        event.data?.id,
        selectedDate,
        props.month,
        props.duration,
        dayCount,
        props.teamMemberEmail,
      ],
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: apps/web/modules/bookings/components/BookerWebWrapper.tsx
Lines: 170-176
Issue Type: functional-medium
Severity: medium

Issue Description:
Once a user clicks "Load more availability", `roundRobinChunkSettings` stays in manual mode until this reset list changes. The problem is that the list only keys off event identity, while `useScheduleForEvent` also sends the manual chunk offset on every date/month/duration/team-member change (`packages/features/bookings/Booker/utils/event.ts:125-126`). A concrete failure case is: open chunk 3 for one day, then pick a different day: the next availability request still starts at chunk 3, so chunks 0-2 are skipped and the new day shows incomplete availability.

Current Code:
      resetDeps: [
        props.username,
        props.eventSlug,
        props.entity.orgSlug,
        props.entity.eventTypeId,
        event.data?.id,
      ],

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

});
const bookings = useBookings({
event,
Expand Down Expand Up @@ -256,6 +275,9 @@ const BookerWebWrapperComponent = (props: BookerWebWrapperAtomProps) => {
event={event}
bookerLayout={bookerLayout}
schedule={schedule}
onLoadNextRoundRobinChunk={
roundRobinChunkInfo?.hasMoreNonFixedHosts ? handleLoadNextRoundRobinChunk : undefined
}
verifyCode={verifyCode}
isPlatform={false}
areInstantMeetingParametersSet={areInstantMeetingParametersSet}
Expand Down
4 changes: 3 additions & 1 deletion apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -4357,5 +4357,7 @@
"error_enabling_feature": "Error enabling feature. Please try again.",
"set_organizer_as_contact_owner": "Set booking organizer as contact owner",
"overwrite_existing_contact_owner": "Overwrite existing contact owner",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS":"↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
"round_robin_load_next_hosts": "Load more availability",
"round_robin_reset_hosts": "Reset host selection",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
4 changes: 4 additions & 0 deletions packages/features/bookings/Booker/__tests__/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ const createMockStore = (initialState?: Partial<BookerStore>): StoreApi<BookerSt
allowUpdatingUrlParams: true,
verificationCode: null,
setVerificationCode: vi.fn(),
roundRobinChunkSettings: null,
setRoundRobinChunkSettings: vi.fn(),
roundRobinChunkInfo: null,
setRoundRobinChunkInfo: vi.fn(),
...initialState,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useCallback, useEffect } from "react";
import type { DependencyList } from "react";

import type { RoundRobinChunkSettings } from "@calcom/features/bookings/Booker/store";
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
import type { RoundRobinChunkInfo } from "@calcom/lib/types/roundRobinChunkInfo";

type UseRoundRobinChunkingOptions = {
roundRobinChunkInfo?: RoundRobinChunkInfo | null;
isFetching: boolean;
resetDeps?: DependencyList;
roundRobinChunkSettings: RoundRobinChunkSettings | null;
setRoundRobinChunkSettings: (settings: RoundRobinChunkSettings | null) => void;
};

export const useRoundRobinChunking = ({
roundRobinChunkInfo,
isFetching,
resetDeps = [],
roundRobinChunkSettings,
setRoundRobinChunkSettings,
}: UseRoundRobinChunkingOptions) => {
const setRoundRobinChunkInfo = useBookerStoreContext((state) => state.setRoundRobinChunkInfo);

useEffect(() => {
setRoundRobinChunkInfo(roundRobinChunkInfo ?? null);
}, [roundRobinChunkInfo, setRoundRobinChunkInfo]);
Comment on lines +25 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Robustness Medium

The effect that syncs roundRobinChunkInfo into the store runs on every render where roundRobinChunkInfo changes (including when it becomes undefined or null). Because the reset effect (lines 29-32) runs after this effect on initial mount (both fire on first mount), the store is first set then immediately cleared, resulting in roundRobinChunkInfo always being null for one render cycle. This leads to the 'Load more' button not appearing on the initial load even when there is more data to fetch. The sync effect should guard against the reset-on-mount issue by checking if the value is actually changing from a non-null to null state deliberately.

Suggested fix
  useEffect(() => {
    if (roundRobinChunkInfo !== undefined) {
      setRoundRobinChunkInfo(roundRobinChunkInfo ?? null);
    }
  }, [roundRobinChunkInfo, setRoundRobinChunkInfo]);
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 25-27
Issue Type: robustness-medium
Severity: medium

Issue Description:
The effect that syncs `roundRobinChunkInfo` into the store runs on every render where `roundRobinChunkInfo` changes (including when it becomes `undefined` or `null`). Because the reset effect (lines 29-32) runs after this effect on initial mount (both fire on first mount), the store is first set then immediately cleared, resulting in `roundRobinChunkInfo` always being `null` for one render cycle. This leads to the 'Load more' button not appearing on the initial load even when there is more data to fetch. The sync effect should guard against the reset-on-mount issue by checking if the value is actually changing from a non-null to null state deliberately.

Current Code:
  useEffect(() => {
    setRoundRobinChunkInfo(roundRobinChunkInfo ?? null);
  }, [roundRobinChunkInfo, setRoundRobinChunkInfo]);

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Comment on lines +25 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

The first useEffect writes roundRobinChunkInfo to the store unconditionally on every change. The second useEffect (the reset effect) also calls setRoundRobinChunkInfo(null). Because both effects run on the same render cycle when resetDeps changes, the order of execution between effects 1 and 2 is deterministic (effect 1 runs before effect 2 in declaration order), but effect 1 runs again after effect 2 resets to null: if roundRobinChunkInfo prop is still non-null from the previous schedule data, it will re-populate the store with stale chunk info from the old event/user. This creates a race where a user navigates to a new event type: the reset effect clears chunk info, but the sync effect immediately restores the stale chunk info from the previous schedule query (which hasn't been garbage-collected yet by react-query).

Suggested fix
  const isResettingRef = useRef(false);

  useEffect(() => {
    if (!isResettingRef.current) {
      setRoundRobinChunkInfo(roundRobinChunkInfo ?? null);
    }
    isResettingRef.current = false;
  }, [roundRobinChunkInfo, setRoundRobinChunkInfo]);

  useEffect(() => {
    isResettingRef.current = true;
    setRoundRobinChunkSettings(null);
    setRoundRobinChunkInfo(null);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 25-27
Issue Type: functional-medium
Severity: medium

Issue Description:
The first `useEffect` writes `roundRobinChunkInfo` to the store unconditionally on every change. The second `useEffect` (the reset effect) also calls `setRoundRobinChunkInfo(null)`. Because both effects run on the same render cycle when `resetDeps` changes, the order of execution between effects 1 and 2 is deterministic (effect 1 runs before effect 2 in declaration order), but effect 1 runs again after effect 2 resets to null: if `roundRobinChunkInfo` prop is still non-null from the previous schedule data, it will re-populate the store with stale chunk info from the old event/user. This creates a race where a user navigates to a new event type: the reset effect clears chunk info, but the sync effect immediately restores the stale chunk info from the previous schedule query (which hasn't been garbage-collected yet by react-query).

Current Code:
  useEffect(() => {
    setRoundRobinChunkInfo(roundRobinChunkInfo ?? null);
  }, [roundRobinChunkInfo, setRoundRobinChunkInfo]);

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue


useEffect(() => {
setRoundRobinChunkSettings(null);
setRoundRobinChunkInfo(null);
}, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);
Comment on lines +29 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Robustness High

The reset useEffect spreads resetDeps directly into the dependency array using [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]. This is explicitly forbidden by React's rules of hooks: the ESLint react-hooks/exhaustive-deps rule rejects dynamic/spread dependency arrays, and the React runtime does not guarantee stable identity for the spread items across renders. More critically, setRoundRobinChunkInfo and setRoundRobinChunkSettings are Zustand setters that are recreated whenever the store is recreated. During mount, both effects run sequentially: the first effect (line 25-27) sets the chunk info, and the reset effect immediately clears it. This causes the store to always be null on the first render, breaking initial chunk display. The reset effect should only run when the actual identity-relevant props (username, eventSlug, etc.) change, not on every render that includes a stable setter.

Suggested fix
  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(() => {
    setRoundRobinChunkSettings(null);
    setRoundRobinChunkInfo(null);
  // Intentionally spreading resetDeps — these are stable primitive values (strings/numbers/null)
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, resetDeps);
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 29-32
Issue Type: robustness-high
Severity: high

Issue Description:
The reset `useEffect` spreads `resetDeps` directly into the dependency array using `[setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]`. This is explicitly forbidden by React's rules of hooks: the ESLint `react-hooks/exhaustive-deps` rule rejects dynamic/spread dependency arrays, and the React runtime does not guarantee stable identity for the spread items across renders. More critically, `setRoundRobinChunkInfo` and `setRoundRobinChunkSettings` are Zustand setters that are recreated whenever the store is recreated. During mount, both effects run sequentially: the first effect (line 25-27) sets the chunk info, and the reset effect immediately clears it. This causes the store to always be null on the first render, breaking initial chunk display. The reset effect should only run when the actual identity-relevant props (username, eventSlug, etc.) change, not on every render that includes a stable setter.

Current Code:
  useEffect(() => {
    setRoundRobinChunkSettings(null);
    setRoundRobinChunkInfo(null);
  }, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Comment on lines +29 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional High

The reset useEffect spreads resetDeps into the dependency array using the spread operator (...resetDeps). This violates the React hooks rules: dependency arrays must be statically analyzable literals; spreading a dynamic array causes the effect to run on every render (since the identity of the spread values changes each render when an array literal is passed), or alternatively to be entirely ignored by React's linter, making the reset behavior unreliable. In BookerWebWrapper.tsx (line 170), the resetDeps array is created inline as a new array literal on every render, so this effect fires on every render, clearing both roundRobinChunkSettings and roundRobinChunkInfo immediately after they are set: effectively preventing the chunking feature from ever advancing beyond the first chunk. The setRoundRobinChunkInfo and setRoundRobinChunkSettings function references are stable (Zustand setters), but they are included in the deps alongside the spread, which compounds the issue.

Suggested fix
  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(() => {
    setRoundRobinChunkSettings(null);
    setRoundRobinChunkInfo(null);
    // resetDeps intentionally spread — callers must memoize this array
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 29-32
Issue Type: functional-high
Severity: high

Issue Description:
The reset `useEffect` spreads `resetDeps` into the dependency array using the spread operator (`...resetDeps`). This violates the React hooks rules: dependency arrays must be statically analyzable literals; spreading a dynamic array causes the effect to run on every render (since the identity of the spread values changes each render when an array literal is passed), or alternatively to be entirely ignored by React's linter, making the reset behavior unreliable. In `BookerWebWrapper.tsx` (line 170), the `resetDeps` array is created inline as a new array literal on every render, so this effect fires on every render, clearing both `roundRobinChunkSettings` and `roundRobinChunkInfo` immediately after they are set: effectively preventing the chunking feature from ever advancing beyond the first chunk. The `setRoundRobinChunkInfo` and `setRoundRobinChunkSettings` function references are stable (Zustand setters), but they are included in the deps alongside the spread, which compounds the issue.

Current Code:
  useEffect(() => {
    setRoundRobinChunkSettings(null);
    setRoundRobinChunkInfo(null);
  }, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Comment on lines +29 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional High

Spread syntax with dynamic arrays in useEffect dependency array is invalid React Hook usage. The eslint-plugin-react-hooks rule explicitly forbids spreading an array into a dependency array ([setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]). React requires dependency arrays to be static (fixed-length literal arrays) so it can run its static analysis. At runtime the spread still works as plain JavaScript, but since resetDeps can change length between renders (e.g., if the caller passes a different-length array), the effect will behave inconsistently and React will emit a lint error / may produce stale-closure bugs. The effect is supposed to reset chunk state whenever the event identity changes, but because of this dynamic spread the dependency list is not stable across renders. The correct pattern is to accept a stable primitive hash or use a dedicated useEffect in the caller.

Suggested fix
  // Derive a stable string key from the reset dependencies so the
  // hook rule is satisfied and length changes can't silently break things.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const resetKey = resetDeps.map(String).join("|");

  useEffect(() => {
    setRoundRobinChunkSettings(null);
    setRoundRobinChunkInfo(null);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [resetKey, setRoundRobinChunkInfo, setRoundRobinChunkSettings]);
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 29-32
Issue Type: functional-high
Severity: high

Issue Description:
Spread syntax with dynamic arrays in `useEffect` dependency array is invalid React Hook usage. The eslint-plugin-react-hooks rule explicitly forbids spreading an array into a dependency array (`[setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]`). React requires dependency arrays to be static (fixed-length literal arrays) so it can run its static analysis. At runtime the spread still works as plain JavaScript, but since `resetDeps` can change length between renders (e.g., if the caller passes a different-length array), the effect will behave inconsistently and React will emit a lint error / may produce stale-closure bugs. The effect is supposed to reset chunk state whenever the event identity changes, but because of this dynamic spread the dependency list is not stable across renders. The correct pattern is to accept a stable primitive hash or use a dedicated `useEffect` in the caller.

Current Code:
  useEffect(() => {
    setRoundRobinChunkSettings(null);
    setRoundRobinChunkInfo(null);
  }, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Comment on lines +29 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Robustness Medium

Spreading ...resetDeps into a useEffect dependency array creates a variable-length dep array. React tracks dependency array length between renders and will emit a warning if the count changes. More critically, eslint-plugin-react-hooks/exhaustive-deps cannot statically analyse spread syntax, so the linter silently stops enforcing exhaustiveness for the entire effect: any future accidental omission of a real dep won't be caught. The conventional fix is to flatten the list to a primitive cache-key string so the array length is always 2.

Suggested fix
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, JSON.stringify(resetDeps)]);
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 29-32
Issue Type: robustness-medium
Severity: medium

Issue Description:
Spreading `...resetDeps` into a `useEffect` dependency array creates a variable-length dep array. React tracks dependency array length between renders and will emit a warning if the count changes. More critically, `eslint-plugin-react-hooks/exhaustive-deps` cannot statically analyse spread syntax, so the linter silently stops enforcing exhaustiveness for the entire effect: any future accidental omission of a real dep won't be caught. The conventional fix is to flatten the list to a primitive cache-key string so the array length is always 2.

Current Code:
  }, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Comment on lines +29 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional High

The reset effect spreads resetDeps directly into the dependency array of useEffect, which violates the React hooks rules. The ESLint react-hooks/exhaustive-deps rule does not understand spread syntax in dependency arrays, and more critically the React runtime treats the spread as a series of positional arguments: if resetDeps length ever changes between renders the hook silently breaks (hooks must be called with the same number of arguments every render). Furthermore, including setRoundRobinChunkInfo and setRoundRobinChunkSettings (stable store functions) together with the actual reset deps means the reset fires on every render where the store reference changes (e.g. HMR, context re-initialization). In practice, the effect can fire immediately after the user clicks 'Load more', erasing the roundRobinChunkSettings they just set and preventing the next chunk from ever loading.

Suggested fix
  const resetDepsRef = useRef(resetDeps);
  useEffect(() => {
    resetDepsRef.current = resetDeps;
  });

  useEffect(() => {
    setRoundRobinChunkSettings(null);
    setRoundRobinChunkInfo(null);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, resetDeps); // resetDeps must have a stable length; see useRef pattern above if it can change
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 29-32
Issue Type: functional-high
Severity: high

Issue Description:
The reset effect spreads `resetDeps` directly into the dependency array of `useEffect`, which violates the React hooks rules. The ESLint `react-hooks/exhaustive-deps` rule does not understand spread syntax in dependency arrays, and more critically the React runtime treats the spread as a series of positional arguments: if `resetDeps` length ever changes between renders the hook silently breaks (hooks must be called with the same number of arguments every render). Furthermore, including `setRoundRobinChunkInfo` and `setRoundRobinChunkSettings` (stable store functions) together with the actual reset deps means the reset fires on every render where the store reference changes (e.g. HMR, context re-initialization). In practice, the effect can fire immediately after the user clicks 'Load more', erasing the `roundRobinChunkSettings` they just set and preventing the next chunk from ever loading.

Current Code:
  useEffect(() => {
    setRoundRobinChunkSettings(null);
    setRoundRobinChunkInfo(null);
  }, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Critical

Spreading resetDeps (a variable-length array) into the dependency array violates React's Rules of Hooks, which require dependency arrays to have a constant number of elements across renders. This causes React to silently skip effect cleanup or retain stale closures when resetDeps length changes, and breaks ESLint's exhaustive-deps rule. The fix is to include resetDeps as a single dependency rather than spreading it.

Suggested change
}, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);
}, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, resetDeps]);
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 32-32
Issue Type: functional-critical
Severity: critical

Issue Description:
Spreading `resetDeps` (a variable-length array) into the dependency array violates React's Rules of Hooks, which require dependency arrays to have a constant number of elements across renders. This causes React to silently skip effect cleanup or retain stale closures when `resetDeps` length changes, and breaks ESLint's exhaustive-deps rule. The fix is to include `resetDeps` as a single dependency rather than spreading it.

Current Code:
  }, [setRoundRobinChunkInfo, setRoundRobinChunkSettings, ...resetDeps]);

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue


const handleLoadNextRoundRobinChunk = useCallback(() => {
if (!roundRobinChunkInfo?.hasMoreNonFixedHosts || isFetching) return;
const currentOffset = roundRobinChunkSettings?.chunkOffset ?? roundRobinChunkInfo.chunkOffset ?? 0;
setRoundRobinChunkSettings({
manual: true,
chunkOffset: currentOffset + 1,
});
Comment on lines +34 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

The handleLoadNextRoundRobinChunk callback uses roundRobinChunkSettings?.chunkOffset ?? roundRobinChunkInfo.chunkOffset ?? 0 to determine the current offset, but the intent is to advance to the next chunk beyond what the API last returned. When the server responds with chunkOffset: N, the client state (roundRobinChunkSettings) may still hold the previous request offset (N-1) or null (first click). The fallback to roundRobinChunkInfo.chunkOffset is correct for the first click, but on subsequent clicks the stale roundRobinChunkSettings.chunkOffset is used instead of the server-confirmed roundRobinChunkInfo.chunkOffset, causing the offset to be calculated as (N-1) + 1 = N (re-requesting the same chunk) instead of N + 1. The correct base should always be the server-confirmed roundRobinChunkInfo.chunkOffset.

Suggested change
const handleLoadNextRoundRobinChunk = useCallback(() => {
if (!roundRobinChunkInfo?.hasMoreNonFixedHosts || isFetching) return;
const currentOffset = roundRobinChunkSettings?.chunkOffset ?? roundRobinChunkInfo.chunkOffset ?? 0;
setRoundRobinChunkSettings({
manual: true,
chunkOffset: currentOffset + 1,
});
const currentOffset = roundRobinChunkInfo.chunkOffset ?? 0;
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 34-40
Issue Type: functional-medium
Severity: medium

Issue Description:
The `handleLoadNextRoundRobinChunk` callback uses `roundRobinChunkSettings?.chunkOffset ?? roundRobinChunkInfo.chunkOffset ?? 0` to determine the current offset, but the intent is to advance to the *next* chunk beyond what the API last returned. When the server responds with `chunkOffset: N`, the client state (`roundRobinChunkSettings`) may still hold the *previous* request offset (N-1) or null (first click). The fallback to `roundRobinChunkInfo.chunkOffset` is correct for the first click, but on subsequent clicks the stale `roundRobinChunkSettings.chunkOffset` is used instead of the server-confirmed `roundRobinChunkInfo.chunkOffset`, causing the offset to be calculated as `(N-1) + 1 = N` (re-requesting the same chunk) instead of `N + 1`. The correct base should always be the server-confirmed `roundRobinChunkInfo.chunkOffset`.

Current Code:
    const currentOffset = roundRobinChunkSettings?.chunkOffset ?? roundRobinChunkInfo.chunkOffset ?? 0;

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

}, [roundRobinChunkInfo, roundRobinChunkSettings, isFetching, setRoundRobinChunkSettings]);
Comment on lines +34 to +41

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

handleLoadNextRoundRobinChunk computes the next offset as currentOffset + 1 where currentOffset = roundRobinChunkSettings?.chunkOffset ?? roundRobinChunkInfo.chunkOffset ?? 0. The fallback reads roundRobinChunkInfo.chunkOffset which is the offset of the currently loaded chunk from the server response. On first load (no manual chunking yet), roundRobinChunkSettings is null, so currentOffset = roundRobinChunkInfo.chunkOffset: the offset of the last auto-loaded chunk. The next request will use chunkOffset + 1. However, in sequential auto mode, the server sets chunkOffset to the index where it stopped (where availability was found), not the total number of chunks loaded. So if the server found availability at chunk 0 (offset 0), the user clicking 'Load More' sends chunkOffset: 1: which is the correct next chunk. This seems correct. But consider: if auto-mode processed chunks 0 and 1 (because chunk 0 had no availability) and stopped at chunk 1 with chunkOffset: 1, clicking 'Load More' sends chunkOffset: 2, skipping to chunk 2: also correct. The logic works. However, there is a subtle race: if the user clicks 'Load More' twice quickly before the first fetch completes, isFetching guard at line 35 should block the second click. But isFetching is derived from schedule.isFetching passed as a prop, and the store state update (via setRoundRobinChunkSettings) happens synchronously while isFetching may not yet be true for the next render cycle. This creates a narrow window where double-clicking could increment chunkOffset twice.

Suggested fix
  const handleLoadNextRoundRobinChunk = useCallback(() => {
    if (!roundRobinChunkInfo?.hasMoreNonFixedHosts || isFetching) return;
    // Use functional update pattern with a ref to prevent double-invocation races
    const currentOffset = roundRobinChunkSettings?.chunkOffset ?? roundRobinChunkInfo.chunkOffset ?? 0;
    setRoundRobinChunkSettings({
      manual: true,
      chunkOffset: currentOffset + 1,
    });
  }, [roundRobinChunkInfo, roundRobinChunkSettings, isFetching, setRoundRobinChunkSettings]);
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/components/hooks/useRoundRobinChunking.ts
Lines: 34-41
Issue Type: functional-medium
Severity: medium

Issue Description:
`handleLoadNextRoundRobinChunk` computes the next offset as `currentOffset + 1` where `currentOffset = roundRobinChunkSettings?.chunkOffset ?? roundRobinChunkInfo.chunkOffset ?? 0`. The fallback reads `roundRobinChunkInfo.chunkOffset` which is the offset of the **currently loaded** chunk from the server response. On first load (no manual chunking yet), `roundRobinChunkSettings` is `null`, so `currentOffset = roundRobinChunkInfo.chunkOffset`: the offset of the last auto-loaded chunk. The next request will use `chunkOffset + 1`. However, in sequential auto mode, the server sets `chunkOffset` to the index where it **stopped** (where availability was found), not the total number of chunks loaded. So if the server found availability at chunk 0 (offset 0), the user clicking 'Load More' sends `chunkOffset: 1`: which is the correct next chunk. This seems correct. But consider: if auto-mode processed chunks 0 and 1 (because chunk 0 had no availability) and stopped at chunk 1 with `chunkOffset: 1`, clicking 'Load More' sends `chunkOffset: 2`, skipping to chunk 2: also correct. The logic works. However, there is a subtle race: if the user clicks 'Load More' twice quickly before the first fetch completes, `isFetching` guard at line 35 should block the second click. But `isFetching` is derived from `schedule.isFetching` passed as a prop, and the store state update (via `setRoundRobinChunkSettings`) happens synchronously while `isFetching` may not yet be `true` for the next render cycle. This creates a narrow window where double-clicking could increment `chunkOffset` twice.

Current Code:
  const handleLoadNextRoundRobinChunk = useCallback(() => {
    if (!roundRobinChunkInfo?.hasMoreNonFixedHosts || isFetching) return;
    const currentOffset = roundRobinChunkSettings?.chunkOffset ?? roundRobinChunkInfo.chunkOffset ?? 0;
    setRoundRobinChunkSettings({
      manual: true,
      chunkOffset: currentOffset + 1,
    });
  }, [roundRobinChunkInfo, roundRobinChunkSettings, isFetching, setRoundRobinChunkSettings]);

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue


return {
roundRobinChunkSettings,
roundRobinChunkInfo: roundRobinChunkInfo ?? null,
isManualRoundRobinChunking: roundRobinChunkSettings?.manual ?? false,
handleLoadNextRoundRobinChunk,
};
};
19 changes: 15 additions & 4 deletions packages/features/bookings/Booker/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
import { useEffect } from "react";
import { createWithEqualityFn } from "zustand/traditional";



import dayjs from "@calcom/dayjs";
import { BOOKER_NUMBER_OF_DAYS_TO_LOAD } from "@calcom/lib/constants";
import type { RoundRobinChunkInfo } from "@calcom/lib/types/roundRobinChunkInfo";
import { BookerLayouts } from "@calcom/prisma/zod-utils";



import type { GetBookingType } from "../lib/get-booking";
import type { BookerState, BookerLayout } from "./types";
import { updateQueryParam, getQueryParam, removeQueryParam } from "./utils/query-param";
Expand Down Expand Up @@ -84,6 +81,11 @@ type SeatedEventData = {
showAvailableSeatsCount?: boolean | null;
};

export type RoundRobinChunkSettings = {
manual: boolean;
chunkOffset: number;
};

export type BookerStore = {
/**
* Event details. These are stored in store for easier
Expand Down Expand Up @@ -215,6 +217,10 @@ export type BookerStore = {
isPlatform?: boolean;
allowUpdatingUrlParams?: boolean;
defaultPhoneCountry?: CountryCode | null;
roundRobinChunkSettings: RoundRobinChunkSettings | null;
setRoundRobinChunkSettings: (settings: RoundRobinChunkSettings | null) => void;
roundRobinChunkInfo: RoundRobinChunkInfo | null;
setRoundRobinChunkInfo: (info: RoundRobinChunkInfo | null) => void;
};

/**
Expand Down Expand Up @@ -495,6 +501,11 @@ export const createBookerStore = () =>
},
isPlatform: false,
allowUpdatingUrlParams: true,
roundRobinChunkSettings: null,
setRoundRobinChunkSettings: (settings: RoundRobinChunkSettings | null) =>
set({ roundRobinChunkSettings: settings }),
roundRobinChunkInfo: null,
setRoundRobinChunkInfo: (info: RoundRobinChunkInfo | null) => set({ roundRobinChunkInfo: info }),
defaultPhoneCountry: null,
}));

Expand Down
1 change: 1 addition & 0 deletions packages/features/bookings/Booker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export type WrappedBookerPropsMain = {
isBookingDryRun?: boolean;
renderCaptcha?: boolean;
confirmButtonDisabled?: boolean;
onLoadNextRoundRobinChunk?: () => void;
};

export type WrappedBookerPropsForPlatform = WrappedBookerPropsMain & {
Expand Down
8 changes: 8 additions & 0 deletions packages/features/bookings/Booker/utils/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const useScheduleForEvent = ({
isTeamEvent,
useApiV2 = true,
bookerLayout,
roundRobinChunkSettings,
}: {
username?: string | null;
eventSlug?: string | null;
Expand All @@ -92,6 +93,10 @@ export const useScheduleForEvent = ({
extraDays: number;
columnViewExtraDays: { current: number };
};
roundRobinChunkSettings?: {
manual: boolean;
chunkOffset: number;
};
}) => {
const { timezone } = useBookerTime();
const [usernameFromStore, eventSlugFromStore, monthFromStore, durationFromStore] = useBookerStoreContext(
Expand All @@ -117,6 +122,8 @@ export const useScheduleForEvent = ({
teamMemberEmail,
useApiV2: useApiV2,
bookerLayout,
roundRobinManualChunking: roundRobinChunkSettings?.manual,
roundRobinChunkOffset: roundRobinChunkSettings?.chunkOffset,
Comment on lines +125 to +126

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

roundRobinChunkOffset is passed directly (potentially undefined) to useSchedule at line 126. Inside useSchedule (useSchedule.ts line 125), the condition typeof roundRobinChunkOffset === 'number' is used to spread it into the query input: so undefined is correctly omitted. However, when roundRobinChunkSettings has chunkOffset: 0, the condition roundRobinChunkSettings?.manual on line 125 is true (manual mode enabled), but chunkOffset is 0. The tRPC input spread at useSchedule.ts:124-125 uses ...(roundRobinManualChunking ? { roundRobinManualChunking: true } : {}) which correctly adds the flag, and ...(typeof roundRobinChunkOffset === 'number' ? { roundRobinChunkOffset } : {}): but roundRobinChunkOffset = 0 is falsy-equivalent, and typeof 0 === 'number' is true, so this is fine. The more subtle bug: if roundRobinChunkSettings is non-null but manual is false, then roundRobinManualChunking will be undefined (not passed), but roundRobinChunkOffset WILL be passed if it's a number. This is inconsistent: a non-manual chunk offset without the manual flag will be sent to the server, potentially causing unexpected behaviour.

Suggested fix
    roundRobinManualChunking: roundRobinChunkSettings?.manual ?? undefined,
    roundRobinChunkOffset: roundRobinChunkSettings?.manual ? roundRobinChunkSettings.chunkOffset : undefined,
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/utils/event.ts
Lines: 125-126
Issue Type: functional-medium
Severity: medium

Issue Description:
`roundRobinChunkOffset` is passed directly (potentially `undefined`) to `useSchedule` at line 126. Inside `useSchedule` (useSchedule.ts line 125), the condition `typeof roundRobinChunkOffset === 'number'` is used to spread it into the query input: so `undefined` is correctly omitted. However, when `roundRobinChunkSettings` has `chunkOffset: 0`, the condition `roundRobinChunkSettings?.manual` on line 125 is `true` (manual mode enabled), but `chunkOffset` is `0`. The tRPC input spread at `useSchedule.ts:124-125` uses `...(roundRobinManualChunking ? { roundRobinManualChunking: true } : {})` which correctly adds the flag, and `...(typeof roundRobinChunkOffset === 'number' ? { roundRobinChunkOffset } : {})`: but `roundRobinChunkOffset = 0` is falsy-equivalent, and `typeof 0 === 'number'` is `true`, so this is fine. The more subtle bug: if `roundRobinChunkSettings` is non-null but `manual` is `false`, then `roundRobinManualChunking` will be `undefined` (not passed), but `roundRobinChunkOffset` WILL be passed if it's a number. This is inconsistent: a non-manual chunk offset without the manual flag will be sent to the server, potentially causing unexpected behaviour.

Current Code:
    roundRobinManualChunking: roundRobinChunkSettings?.manual,
    roundRobinChunkOffset: roundRobinChunkSettings?.chunkOffset,

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

Comment on lines +125 to +126

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functional Medium

roundRobinManualChunking is passed as roundRobinChunkSettings?.manual which can be false (when settings are set with manual: false). In useSchedule.ts lines 124-125, when roundRobinManualChunking is falsy, the parameter is omitted from the input object: ...(roundRobinManualChunking ? { roundRobinManualChunking: true } : {}). This means a manual: false setting is silently dropped, which is the intended behavior. However, roundRobinChunkOffset is passed directly as roundRobinChunkSettings?.chunkOffset and in useSchedule.ts it's only conditionally spread: ...(typeof roundRobinChunkOffset === 'number' ? { roundRobinChunkOffset } : {}). When roundRobinChunkSettings is null (default), chunkOffset is undefined, so offset is correctly omitted. But if roundRobinChunkSettings has manual: false but a non-zero chunkOffset, the offset IS sent to the server without roundRobinManualChunking: true. The server would then receive an offset without the manual flag, causing it to use auto-chunking from offset N rather than from 0, potentially returning incorrect slot data.

Suggested fix
    roundRobinManualChunking: roundRobinChunkSettings?.manual || undefined,
    roundRobinChunkOffset: roundRobinChunkSettings?.manual ? roundRobinChunkSettings.chunkOffset : undefined,
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert bash developer with deep knowledge of security, performance, and best practices.

### Context

File: packages/features/bookings/Booker/utils/event.ts
Lines: 125-126
Issue Type: functional-medium
Severity: medium

Issue Description:
`roundRobinManualChunking` is passed as `roundRobinChunkSettings?.manual` which can be `false` (when settings are set with `manual: false`). In `useSchedule.ts` lines 124-125, when `roundRobinManualChunking` is falsy, the parameter is omitted from the input object: `...(roundRobinManualChunking ? { roundRobinManualChunking: true } : {})`. This means a `manual: false` setting is silently dropped, which is the intended behavior. However, `roundRobinChunkOffset` is passed directly as `roundRobinChunkSettings?.chunkOffset` and in `useSchedule.ts` it's only conditionally spread: `...(typeof roundRobinChunkOffset === 'number' ? { roundRobinChunkOffset } : {})`. When `roundRobinChunkSettings` is `null` (default), `chunkOffset` is `undefined`, so offset is correctly omitted. But if `roundRobinChunkSettings` has `manual: false` but a non-zero `chunkOffset`, the offset IS sent to the server without `roundRobinManualChunking: true`. The server would then receive an offset without the manual flag, causing it to use auto-chunking from offset N rather than from 0, potentially returning incorrect slot data.

Current Code:
    roundRobinManualChunking: roundRobinChunkSettings?.manual,
    roundRobinChunkOffset: roundRobinChunkSettings?.chunkOffset,

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike Create Issue

});

return {
Expand All @@ -125,6 +132,7 @@ export const useScheduleForEvent = ({
isError: schedule?.isError,
isSuccess: schedule?.isSuccess,
isLoading: schedule?.isLoading,
isFetching: schedule?.isFetching,
invalidate: schedule?.invalidate,
dataUpdatedAt: schedule?.dataUpdatedAt,
};
Expand Down
1 change: 0 additions & 1 deletion packages/features/bookings/lib/bookingCreateBodySchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const bookingCreateBodySchema = z.object({
rrHostSubsetIds: z.array(z.number()).nullish(),
crmAppSlug: z.string().nullish().optional(),
cfToken: z.string().nullish().optional(),

/**
* Holds the corrected responses of the Form for a booking, provided during rerouting
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ import type { GetAvailableSlotsResponse } from "@calcom/trpc/server/routers/view

export const QUERY_KEY = "get-available-slots";

type GetAvailableSlotsInputWithChunks = GetAvailableSlotsInput_2024_04_15 & {
roundRobinManualChunking?: boolean;
roundRobinChunkOffset?: number;
};

export const useApiV2AvailableSlots = ({
enabled,
...rest
}: GetAvailableSlotsInput_2024_04_15 & { enabled: boolean }) => {
}: GetAvailableSlotsInputWithChunks & { enabled: boolean }) => {
const availableSlots = useQuery({
queryKey: [
QUERY_KEY,
Expand All @@ -28,6 +33,8 @@ export const useApiV2AvailableSlots = ({
rest.skipContactOwner,
rest.teamMemberEmail,
rest.embedConnectVersion ?? false,
rest.roundRobinManualChunking ?? false,
rest.roundRobinChunkOffset ?? 0,
],
queryFn: () => {
return axios
Expand Down
Loading