Skip to content

Conversation

livebitz1
Copy link

@livebitz1 livebitz1 commented Oct 11, 2025

Fixes #24417

Summary

  • Removed deprecated Next.js Link legacyBehavior usage across the codebase and migrated affected components to the new Link API to resolve ref-forwarding, DOM nesting, and hydration/runtime errors observed while running the application locally.
  • Fixed runtime errors (e.g. null parentNode), improved SSR determinism, and preserved accessibility/interactive behavior for core UI components (Dropdown, Button, List, Stepper, etc.).

Impact / Scope

  • UI-only, non-breaking changes. No API or DB changes. Small typing and lint fixes included.

Motivation
Using legacyBehavior produced inconsistent server/client markup, broken refs, and nested interactive elements (anchor inside button), leading to hydration warnings and runtime exceptions. Migrating to the new Link API prevents these problems and prepares the codebase for React 19 / Next.js 15.

Files changed (high level)

  • packages/ui/components/dropdown/Dropdown.tsx
  • packages/ui/components/button/Button.tsx
  • packages/ui/components/list/List.tsx
  • packages/ui/components/form/step/Stepper.tsx
  • packages/lib/turndownService.ts
  • packages/features/shell/CalAiBanner.tsx
  • packages/app-store/_components/AppNotInstalledMessage.tsx
  • apps/web/modules/bookings/views/bookings-single-view.tsx
  • apps/web/components/apps/make/Setup.tsx
  • apps/web/components/apps/zapier/Setup.tsx
  • apps/web/modules/auth/forgot-password/[id]/forgot-password-single-view.tsx
  • plus small lint/type fixes across related files

Key changes

  • Removed all uses of Link legacyBehavior and updated Link usage to the new API (pass className/props directly to Link).
  • Fixed ref forwarding and removed unsafe any casts in Dropdown and Button components.
  • Separated List into SSR-safe rendering and a ListLinkItem to avoid nested interactive elements.
  • Hardened turndownService to avoid traversing null parent nodes.
  • Deferred CalAiBanner visibility to client mount to prevent hydration mismatches.
  • Cleaned up ESLint warnings and added targeted rule disables where necessary.

Manual QA steps (recommended)

  1. Prepare
    • yarn install && yarn dedupe
  2. Local checks
    • yarn dev # or build with yarn build
    • yarn lint
    • yarn test
  3. Smoke tests (verify in both dev and production builds)
    • Dropdown menus: open/close, keyboard navigation, focus behavior
    • Button components rendering as Links: ensure no nested anchors/buttons and keyboard/Enter works
    • List + ListLinkItem: SSR output matches client render and navigation works
    • Stepper: next/back flow
    • Bookings single view, Forgot-password view, AppNotInstalledMessage, Make & Zapier setup pages: links/navigation work
    • CalAiBanner: no hydration mismatch on initial page load
  4. Edge cases
    • Keyboard-only navigation and screen-reader checks for major components
    • Confirm prior runtime errors (null parentNode, ref issues) no longer appear in logs/console

What reviewers should verify

  • PR title follows Conventional Commits (starts with fix:) — required for CI
  • No remaining usage of legacyBehavior
  • Correct ref forwarding and typing (no unsafe any casts)
  • No nested interactive elements introduced by the changes
  • No hydration mismatch warnings in browser console
  • CI (typecheck / lint / tests) passes
  • Behavioral parity with previous UI (keyboard/focus/ARIA preserved)

Commands (copy to terminal)

Update PR title (run once)

gh pr edit 24417 --title "fix(ui): migrate off Next.js — fix hydration & ref-forwarding issues"

Update PR body from this file (run once)

gh pr edit 24417 --body-file PR_DESCRIPTION.md

Optional: add reviewers/labels

gh pr edit 24417 --add-reviewer maintainer1 --add-reviewer maintainer2 --add-label "area:ui" --add-label "type:bug"

Local checks

yarn install && yarn dedupe
yarn build
yarn lint
yarn test

Visual demo (strongly recommended)

  • Attach a console screenshot showing the hydration warning (before) and a short GIF (5–10s) showing dropdown/button behavior before vs after. Upload via the PR web UI.

Checklist (DO NOT REMOVE)

  • I have self-reviewed the code
  • I have added tests where applicable, or noted why not applicable
  • I have run lint and fixed warnings where possible
  • I have added a visual demo (recommended) or documented how to reproduce

Notes

@livebitz1 livebitz1 requested review from a team as code owners October 11, 2025 22:00
@github-actions github-actions bot added ui area: UI, frontend, button, form, input 🐛 bug Something isn't working labels Oct 11, 2025
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Oct 11, 2025
Copy link
Contributor

Hey there and thank you for opening this pull request! 👋🏼

We require pull request titles to follow the Conventional Commits specification and it looks like your proposed title needs to be adjusted.

Details:

No release type found in pull request title "Migrate off Next.js — fix hydration & ref-forwarding issues". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

Available types:
 - feat: A new feature
 - fix: A bug fix
 - docs: Documentation only changes
 - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
 - refactor: A code change that neither fixes a bug nor adds a feature
 - perf: A code change that improves performance
 - test: Adding missing tests or correcting existing tests
 - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
 - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
 - chore: Other changes that don't modify src or test files
 - revert: Reverts a previous commit

Copy link

vercel bot commented Oct 11, 2025

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

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Oct 11, 2025

Walkthrough

This change migrates Next.js Link usage off legacyBehavior across the codebase: Button now supports href by rendering Next.js Link, Dropdown's ButtonOrLink is refactored with forwardRef, Stepper and several views replace legacy Link wrappers, ListItem no longer accepts href (ListItemProps disallows it), CalAiBanner defers localStorage checks to useEffect to avoid hydration mismatches, and turndownService adds null guards and safer node traversal. Adds PR_DESCRIPTION.md, ISSUE.md, and ISSUE_BODY.md and pins @radix-ui/react-slot to 1.1.2 in package.json.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The PR includes substantial refactoring in packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts and a dependency resolution change in package.json that are unrelated to migrating off Next.js Link legacyBehavior. These changes fall outside the linked issue objectives, which focus solely on UI component updates and hydration/ref-forwarding fixes. Please remove or isolate the unrelated backend refactoring in handleChildrenEventTypes.ts and the dependency resolution change in package.json into a separate PR to keep this migration focused on Link legacyBehavior updates.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly specifies the migration off Next.js Link legacyBehavior and highlights the hydration and ref-forwarding fixes, which directly reflect the primary changes in the pull request.
Linked Issues Check ✅ Passed The changes remove all uses of legacyBehavior, update Link usage to the new API across components, implement proper ref forwarding in Button and Dropdown, prevent nested interactive elements in List and Stepper, harden turndownService against null parent nodes, and include lint/type fixes consistent with the linked issue objectives.
Description Check ✅ Passed The pull request description clearly summarizes the migration off legacy Link behavior, outlines scope, motivation, and QA steps, directly relating to the changes in the diff.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • 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

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot dosubot bot added the 🧹 Improvements Improvements to existing features. Mostly UX/UI label Oct 11, 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

Caution

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

⚠️ Outside diff range comments (3)
packages/app-store/_components/AppNotInstalledMessage.tsx (1)

19-26: Nested interactive elements violate the PR's accessibility goals.

The current implementation wraps a Button inside a Link, creating nested interactive elements (an anchor containing a button). This pattern:

  • Violates HTML validity (interactive elements shouldn't be nested)
  • Creates accessibility issues for keyboard navigation and screen readers
  • Contradicts the PR's stated objective to "prevent nested interactive elements (anchors/buttons inside each other)"

Per the PR summary, Button now supports an href prop that internally renders a Link. Use that pattern instead.

Apply this diff:

         <div className="mt-5">
-          <Link href={`/apps/${appName}`} className="inline-block">
-            <Button type="button" color="secondary">
-              {t("go_to_app_store")}
-              <Icon name="arrow-up-right" className="ml-1" size={20} />
-            </Button>
-          </Link>
+          <Button href={`/apps/${appName}`} type="button" color="secondary">
+            {t("go_to_app_store")}
+            <Icon name="arrow-up-right" className="ml-1" size={20} />
+          </Button>
         </div>
apps/web/modules/auth/forgot-password/[id]/forgot-password-single-view.tsx (1)

48-54: Remove nested interactive elements (Link > button).

The Link wrapping a button element creates nested interactive elements, which violates accessibility guidelines and can break keyboard navigation and screen reader behavior.

Apply this diff to fix the issue by using just the Link element with appropriate styling:

-        <Link href="/auth/forgot-password" className="flex w-full justify-center">
-          <button
-            type="button"
-            className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
-            {t("try_again")}
-          </button>
-        </Link>
+        <Link
+          href="/auth/forgot-password"
+          className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
+          {t("try_again")}
+        </Link>
packages/ui/components/button/Button.tsx (1)

323-368: Tooltip support broken for links.

The Wrapper component (which provides tooltip functionality) only wraps the button element (lines 359-368) but not the Link element (lines 323-340). This means tooltips won't display when href is provided.

Apply this diff to wrap both Link and button in the Wrapper:

  const linkClass = classNames(buttonClasses({ color, size, loading, variant }), props.className);

+  const element = props.href ? (
-  if (props.href) {
-    return (
       <Link
         data-testid="link-component"
         href={props.href}
         shallow={shallow}
         // Link forwards refs to the underlying anchor automatically with the new Link API
         ref={forwardedRef as React.Ref<HTMLAnchorElement>}
         className={linkClass}
         onClick={
           disabled
             ? (e: React.MouseEvent) => e.preventDefault()
             : (props.onClick as unknown as React.MouseEventHandler<HTMLAnchorElement>)
         }>
         {content}
       </Link>
+    )
-    );
-  }
-
-  const element = React.createElement(
+  ) : React.createElement(
     "button",
     {
       ...passThroughProps,
       disabled,
       type: !isLink ? type : undefined,
       ref: forwardedRef,
       className: linkClass,
       onClick: disabled
         ? (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
             e.preventDefault();
           }
         : props.onClick,
     },
     content
   );

   return (
     <Wrapper
       data-testid="wrapper"
       tooltip={props.tooltip}
       tooltipSide={tooltipSide}
       tooltipOffset={tooltipOffset}
       tooltipClassName={tooltipClassName}>
       {element}
     </Wrapper>
   );
 });
🧹 Nitpick comments (3)
packages/ui/components/button/Button.tsx (2)

335-335: Avoid unsafe type casting through unknown.

The onClick handler uses as unknown as casting which completely bypasses TypeScript's type safety. This pattern can hide type errors.

Apply this diff to use a more type-safe approach:

         onClick={
           disabled
             ? (e: React.MouseEvent) => e.preventDefault()
-            : (props.onClick as unknown as React.MouseEventHandler<HTMLAnchorElement>)
+            : (props.onClick as React.MouseEventHandler<HTMLAnchorElement> | undefined)
         }>

347-347: Remove redundant condition.

The check !isLink ? type : undefined is redundant since this code path only executes when !props.href (which means !isLink).

Apply this diff:

       ...passThroughProps,
       disabled,
-      type: !isLink ? type : undefined,
+      type: type,
       ref: forwardedRef,
packages/ui/components/dropdown/Dropdown.tsx (1)

157-159: No incompatible prop usage found; consider refactoring DropdownItem to use a discriminated union for props in Dropdown.tsx (lines 157–159, 167–169) to enforce correct attribute sets and avoid unchecked spreads.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 eae2711 and 71bad74.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (13)
  • PR_DESCRIPTION.md (1 hunks)
  • apps/web/components/apps/make/Setup.tsx (2 hunks)
  • apps/web/components/apps/zapier/Setup.tsx (3 hunks)
  • apps/web/modules/auth/forgot-password/[id]/forgot-password-single-view.tsx (1 hunks)
  • apps/web/modules/bookings/views/bookings-single-view.tsx (7 hunks)
  • package.json (1 hunks)
  • packages/app-store/_components/AppNotInstalledMessage.tsx (1 hunks)
  • packages/features/shell/CalAiBanner.tsx (2 hunks)
  • packages/lib/turndownService.ts (1 hunks)
  • packages/ui/components/button/Button.tsx (3 hunks)
  • packages/ui/components/dropdown/Dropdown.tsx (2 hunks)
  • packages/ui/components/form/step/Stepper.tsx (1 hunks)
  • packages/ui/components/list/List.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.tsx

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

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/ui/components/form/step/Stepper.tsx
  • apps/web/components/apps/make/Setup.tsx
  • apps/web/modules/bookings/views/bookings-single-view.tsx
  • packages/app-store/_components/AppNotInstalledMessage.tsx
  • packages/ui/components/list/List.tsx
  • packages/ui/components/button/Button.tsx
  • apps/web/modules/auth/forgot-password/[id]/forgot-password-single-view.tsx
  • packages/features/shell/CalAiBanner.tsx
  • apps/web/components/apps/zapier/Setup.tsx
  • packages/ui/components/dropdown/Dropdown.tsx
**/*.{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/ui/components/form/step/Stepper.tsx
  • apps/web/components/apps/make/Setup.tsx
  • apps/web/modules/bookings/views/bookings-single-view.tsx
  • packages/app-store/_components/AppNotInstalledMessage.tsx
  • packages/ui/components/list/List.tsx
  • packages/ui/components/button/Button.tsx
  • apps/web/modules/auth/forgot-password/[id]/forgot-password-single-view.tsx
  • packages/lib/turndownService.ts
  • packages/features/shell/CalAiBanner.tsx
  • apps/web/components/apps/zapier/Setup.tsx
  • packages/ui/components/dropdown/Dropdown.tsx
**/*.{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/ui/components/form/step/Stepper.tsx
  • apps/web/components/apps/make/Setup.tsx
  • apps/web/modules/bookings/views/bookings-single-view.tsx
  • packages/app-store/_components/AppNotInstalledMessage.tsx
  • packages/ui/components/list/List.tsx
  • packages/ui/components/button/Button.tsx
  • apps/web/modules/auth/forgot-password/[id]/forgot-password-single-view.tsx
  • packages/lib/turndownService.ts
  • packages/features/shell/CalAiBanner.tsx
  • apps/web/components/apps/zapier/Setup.tsx
  • packages/ui/components/dropdown/Dropdown.tsx
**/*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/lib/turndownService.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/lib/turndownService.ts
🧬 Code graph analysis (3)
apps/web/components/apps/make/Setup.tsx (1)
packages/ui/components/button/Button.tsx (1)
  • Button (221-369)
packages/features/shell/CalAiBanner.tsx (1)
packages/lib/webstorage.ts (1)
  • localStorage (6-36)
apps/web/components/apps/zapier/Setup.tsx (1)
packages/ui/components/button/Button.tsx (1)
  • Button (221-369)
⏰ 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). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (12)
packages/ui/components/form/step/Stepper.tsx (1)

29-42: LGTM! Clean refactor of Stepper Link rendering.

The simplified approach using a single Link element with dynamic className and proper aria-current attribute is cleaner and more maintainable than the previous multi-branch conditional rendering. The accessibility features are properly preserved.

packages/lib/turndownService.ts (1)

41-58: LGTM! Robust null safety improvements.

The refactored isShiftEnter function now properly handles null/undefined nodes, traverses to the nearest element node, and includes defensive checks for parent and childNodes. This addresses the runtime null parentNode errors mentioned in the PR objectives.

PR_DESCRIPTION.md (1)

1-114: LGTM! Comprehensive PR documentation.

The PR description provides excellent context for the migration, including motivation, scope, manual QA steps, and a reviewer checklist. This documentation will help ensure thorough review and testing of the changes.

packages/features/shell/CalAiBanner.tsx (1)

14-24: LGTM! Hydration-safe visibility management.

The refactor defers localStorage checks to useEffect, starting with a hidden state (false) to ensure deterministic SSR output. The effect properly guards against SSR context and sets visibility based on pathname and localStorage. This prevents hydration mismatch warnings.

apps/web/modules/bookings/views/bookings-single-view.tsx (3)

800-807: LGTM! Clean Link usage without nested elements.

The login and reschedule links now use direct Link components with proper styling, removing unnecessary nested spans. This maintains accessibility and aligns with the new Link API pattern.

Also applies to: 830-839


1107-1110: LGTM! RescheduledToLink refactored correctly.

The component now renders a direct Link with an inline icon, removing the previous nested structure. The flex layout with gap provides clean spacing while maintaining accessibility.


381-381: Verify removal of old variable references

Please confirm there are no remaining occurrences of canCancelAndReschedule, canCancel, or canReschedule in apps/web/modules/bookings/views/bookings-single-view.tsx.

packages/ui/components/list/List.tsx (2)

1-2: LGTM! Proper "use client" directive.

The "use client" directive is correctly placed at the top of the file, which is necessary for using React hooks like useLocale in Next.js app directory.


35-57: LGTM! Clean separation of ListItem and ListLinkItem.

The refactor removes href support from ListItem (enforcing href?: never in types) and ensures it always renders as a plain li element. This prevents nested interactive elements and provides clear separation: use ListItem for static list items, ListLinkItem for navigable items.

package.json (1)

142-143: Pinned @radix-ui/[email protected] exists and has no known vulnerabilities.

apps/web/components/apps/make/Setup.tsx (1)

136-138: LGTM!

The migration to Button[href] correctly leverages the updated Button component API, eliminating the need for Link wrappers and addressing legacy behavior issues.

apps/web/components/apps/zapier/Setup.tsx (1)

13-13: LGTM!

The explicit comment documenting the migration strategy and the consistent use of Button[href] align with the PR objectives and Button component updates.

Also applies to: 142-144

@@ -1,5 +1,6 @@
"use client";

/* eslint-disable */
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 | 🟠 Major

Use targeted ESLint rule disables instead of blanket disable.

The blanket eslint-disable comment disables all linting for the entire file, which can hide legitimate issues and makes the codebase harder to maintain.

Replace with targeted rule disables only where necessary, for example:

-/* eslint-disable */
+/* eslint-disable react-hooks/exhaustive-deps */

Or better yet, fix the specific linting issues and remove the need for disables altogether.

📝 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
/* eslint-disable */
/* eslint-disable react-hooks/exhaustive-deps */
🤖 Prompt for AI Agents
In apps/web/modules/bookings/views/bookings-single-view.tsx around line 3 the
file uses a blanket "/* eslint-disable */" which disables all linting; remove
that top-level disable, run ESLint to identify the specific violations, fix them
where possible, and if a disable is still required use targeted comments (either
inline "// eslint-disable-next-line <rule>" for a single line or "/*
eslint-disable <rule1>, <rule2> */" scoped narrowly) only around the offending
code so linting remains active for the rest of the file.

Comment on lines +148 to +174
export const ButtonOrLink = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonOrLinkProps>(
({ href, children, ...props }, forwardedRef) => {
const isLink = typeof href !== "undefined";

if (isLink) {
// Forward ref and anchor props to Next's Link. Use explicit casts to the correct HTML attribute types
// to avoid `any` and satisfy ESLint rules.
return (
<Link
href={href as string}
ref={forwardedRef as Ref<HTMLAnchorElement>}
{...(props as AnchorHTMLAttributes<HTMLAnchorElement>)}>
{children}
</Link>
);
}

if (isLink) {
return (
<Link href={href} legacyBehavior>
{content}
</Link>
<button
ref={forwardedRef as Ref<HTMLButtonElement>}
{...(props as ButtonHTMLAttributes<HTMLButtonElement>)}>
{children}
</button>
);
}

return content;
}
);
ButtonOrLink.displayName = "ButtonOrLink";
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 | 🟠 Major

Type casts bypass safety; consider discriminated union.

Multiple explicit type casts (as string, as Ref<...>, as AnchorHTMLAttributes<...>) bypass TypeScript's type checking. Additionally, the current intersection type (lines 142-146) allows invalid prop combinations (e.g., both type and target simultaneously).

Consider refactoring to use a discriminated union:

-type ButtonOrLinkProps = ButtonHTMLAttributes<HTMLButtonElement> &
-  AnchorHTMLAttributes<HTMLAnchorElement> & {
-    href?: string;
-    children?: React.ReactNode;
-  };
+type ButtonOrLinkProps =
+  | ({ href: string } & AnchorHTMLAttributes<HTMLAnchorElement> & { children?: React.ReactNode })
+  | ({ href?: never } & ButtonHTMLAttributes<HTMLButtonElement> & { children?: React.ReactNode });

 export const ButtonOrLink = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonOrLinkProps>(
   ({ href, children, ...props }, forwardedRef) => {
-    const isLink = typeof href !== "undefined";
-
-    if (isLink) {
-      // Forward ref and anchor props to Next's Link. Use explicit casts to the correct HTML attribute types
-      // to avoid `any` and satisfy ESLint rules.
+    if (href) {
       return (
         <Link
-          href={href as string}
-          ref={forwardedRef as Ref<HTMLAnchorElement>}
-          {...(props as AnchorHTMLAttributes<HTMLAnchorElement>)}>
+          href={href}
+          ref={forwardedRef as Ref<HTMLAnchorElement>}
+          {...props}>
           {children}
         </Link>
       );
     }

     return (
       <button
         ref={forwardedRef as Ref<HTMLButtonElement>}
-        {...(props as ButtonHTMLAttributes<HTMLButtonElement>)}>
+        {...props}>
         {children}
       </button>
     );
   }
 );

This approach provides compile-time guarantees that appropriate props are passed to each element type.

📝 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
export const ButtonOrLink = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonOrLinkProps>(
({ href, children, ...props }, forwardedRef) => {
const isLink = typeof href !== "undefined";
if (isLink) {
// Forward ref and anchor props to Next's Link. Use explicit casts to the correct HTML attribute types
// to avoid `any` and satisfy ESLint rules.
return (
<Link
href={href as string}
ref={forwardedRef as Ref<HTMLAnchorElement>}
{...(props as AnchorHTMLAttributes<HTMLAnchorElement>)}>
{children}
</Link>
);
}
if (isLink) {
return (
<Link href={href} legacyBehavior>
{content}
</Link>
<button
ref={forwardedRef as Ref<HTMLButtonElement>}
{...(props as ButtonHTMLAttributes<HTMLButtonElement>)}>
{children}
</button>
);
}
return content;
}
);
ButtonOrLink.displayName = "ButtonOrLink";
type ButtonOrLinkProps =
| ({ href: string } & AnchorHTMLAttributes<HTMLAnchorElement> & { children?: React.ReactNode })
| ({ href?: never } & ButtonHTMLAttributes<HTMLButtonElement> & { children?: React.ReactNode });
export const ButtonOrLink = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonOrLinkProps>(
({ href, children, ...props }, forwardedRef) => {
if (href) {
return (
<Link
href={href}
ref={forwardedRef as Ref<HTMLAnchorElement>}
{...props}>
{children}
</Link>
);
}
return (
<button
ref={forwardedRef as Ref<HTMLButtonElement>}
{...props}>
{children}
</button>
);
}
);
ButtonOrLink.displayName = "ButtonOrLink";

@livebitz1 livebitz1 changed the title Migrate off Next.js — fix hydration & ref-forwarding issues fix(ui): migrate off Next.js <Link legacyBehavior> — fix hydration & ref-forwarding issues Oct 12, 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: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 71bad74 and b237a12.

📒 Files selected for processing (3)
  • ISSUE.md (1 hunks)
  • ISSUE_BODY.md (1 hunks)
  • PR_DESCRIPTION.md (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • PR_DESCRIPTION.md
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
ISSUE.md

3-3: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

⏰ 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). (4)
  • GitHub Check: Type check / check-types
  • GitHub Check: Linters / lint
  • GitHub Check: Tests / Unit
  • GitHub Check: Codacy Static Code Analysis

Comment on lines +3 to +74
### Issue Summary
One-sentence explanation: Next.js `Link` components using `legacyBehavior` caused DOM nesting, ref-forwarding and hydration/runtime errors (e.g. `null parentNode`); this PR migrates `Link` usage to the new API and fixes the related issues.

---

### Steps to Reproduce
1. Prepare the repository and dependencies:
- `yarn install && yarn dedupe`
2. Start the app in dev or production build:
- `yarn dev` (or `yarn build && yarn start`)
3. Open pages that use Link / interactive components such as:
- Bookings single view, Forgot-password single view, AppNotInstalledMessage, Make & Zapier setup pages, pages with Dropdowns/Buttons-as-Links/List/Stepper
4. Interact and observe:
- Watch the browser console for hydration warnings and runtime errors when opening dropdowns or navigating via keyboard.
5. Reproduce specific runtime error path:
- Trigger flows that convert HTML → markdown (turndownService) or render server/client inconsistent Link markup to reproduce `Cannot read properties of null (reading 'parentNode')` or hydration mismatch.

Any other relevant information: this is a bug because `legacyBehavior` produced inconsistent server vs client HTML structure and unsafe ref usage, causing developer-facing console warnings and potential user-facing UI breakage. Expected behavior is deterministic SSR → client render with correct element types, correct ref forwarding, and preserved accessibility (keyboard focus, ARIA).

---

### Actual Results
- Hydration mismatch warnings appear in the browser console on initial page load.
- Runtime errors such as `Cannot read properties of null (reading 'parentNode')` from `turndownService` in certain flows.
- Broken ref forwarding and nested interactive elements (anchor inside button or vice-versa) leading to unexpected keyboard behavior and accessibility regressions.
- ESLint warnings related to unsafe `any` casts and unused imports in affected files.

---

### Expected Results
- No hydration mismatch warnings on page load.
- No runtime exceptions from `turndownService` or component render paths.
- Correct ref forwarding (use of `forwardRef` where needed) and no nested interactive elements.
- Keyboard interaction and focus behavior remain correct and accessible.
- Clean lint/type output for the modified files.

---

### Technical details
- Environment used for testing: macOS (local dev), Node.js 18+ recommended, Yarn
- Commands used:
- `yarn install && yarn dedupe`
- `yarn dev`
- `yarn build && yarn start`
- `yarn lint`
- `yarn test`
- Branch with fixes: `fix/next-link-hydration`
- Key files changed:
- `packages/ui/components/dropdown/Dropdown.tsx`
- `packages/ui/components/button/Button.tsx`
- `packages/ui/components/list/List.tsx`
- `packages/ui/components/form/step/Stepper.tsx`
- `packages/lib/turndownService.ts`
- `packages/features/shell/CalAiBanner.tsx`
- `packages/app-store/_components/AppNotInstalledMessage.tsx`
- `apps/web/modules/bookings/views/bookings-single-view.tsx`
- `apps/web/components/apps/make/Setup.tsx`
- `apps/web/components/apps/zapier/Setup.tsx`
- `apps/web/modules/auth/forgot-password/[id]/forgot-password-single-view.tsx`

---

### Evidence
- Manual tests performed:
- Dev and production builds: confirmed hydration warnings present before the change and resolved after migration to the new `Link` API.
- Interacted with Dropdowns, List items, Buttons-as-Links, and Stepper to validate keyboard navigation and no nested anchors/buttons.
- Reproduced and fixed the `null parentNode` error path in `turndownService`.
- Attachments to include with the issue/PR:
- Console screenshot showing hydration warning(s) (before)
- Short GIF showing dropdown/button behavior before vs after (recommended)
- Relevant console stack trace(s) for the runtime exception(s)
- Testing notes: run the smoke test checklist in `PR_DESCRIPTION.md` and verify CI (typecheck/lint/tests) passes.
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 | 🟡 Minor

Fix heading hierarchy to satisfy markdownlint

After the top-level # heading, the next heading jumps straight to ###, triggering MD001. Please promote the section headings to H2 so the hierarchy increments by one level.

-### Issue Summary
+## Issue Summary
@@
-### Steps to Reproduce
+## Steps to Reproduce
@@
-### Actual Results
+## Actual Results
@@
-### Expected Results
+## Expected Results
@@
-### Technical details
+## Technical details
@@
-### Evidence
+## Evidence
📝 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
### Issue Summary
One-sentence explanation: Next.js `Link` components using `legacyBehavior` caused DOM nesting, ref-forwarding and hydration/runtime errors (e.g. `null parentNode`); this PR migrates `Link` usage to the new API and fixes the related issues.
---
### Steps to Reproduce
1. Prepare the repository and dependencies:
- `yarn install && yarn dedupe`
2. Start the app in dev or production build:
- `yarn dev` (or `yarn build && yarn start`)
3. Open pages that use Link / interactive components such as:
- Bookings single view, Forgot-password single view, AppNotInstalledMessage, Make & Zapier setup pages, pages with Dropdowns/Buttons-as-Links/List/Stepper
4. Interact and observe:
- Watch the browser console for hydration warnings and runtime errors when opening dropdowns or navigating via keyboard.
5. Reproduce specific runtime error path:
- Trigger flows that convert HTML → markdown (turndownService) or render server/client inconsistent Link markup to reproduce `Cannot read properties of null (reading 'parentNode')` or hydration mismatch.
Any other relevant information: this is a bug because `legacyBehavior` produced inconsistent server vs client HTML structure and unsafe ref usage, causing developer-facing console warnings and potential user-facing UI breakage. Expected behavior is deterministic SSR → client render with correct element types, correct ref forwarding, and preserved accessibility (keyboard focus, ARIA).
---
### Actual Results
- Hydration mismatch warnings appear in the browser console on initial page load.
- Runtime errors such as `Cannot read properties of null (reading 'parentNode')` from `turndownService` in certain flows.
- Broken ref forwarding and nested interactive elements (anchor inside button or vice-versa) leading to unexpected keyboard behavior and accessibility regressions.
- ESLint warnings related to unsafe `any` casts and unused imports in affected files.
---
### Expected Results
- No hydration mismatch warnings on page load.
- No runtime exceptions from `turndownService` or component render paths.
- Correct ref forwarding (use of `forwardRef` where needed) and no nested interactive elements.
- Keyboard interaction and focus behavior remain correct and accessible.
- Clean lint/type output for the modified files.
---
### Technical details
- Environment used for testing: macOS (local dev), Node.js 18+ recommended, Yarn
- Commands used:
- `yarn install && yarn dedupe`
- `yarn dev`
- `yarn build && yarn start`
- `yarn lint`
- `yarn test`
- Branch with fixes: `fix/next-link-hydration`
- Key files changed:
- `packages/ui/components/dropdown/Dropdown.tsx`
- `packages/ui/components/button/Button.tsx`
- `packages/ui/components/list/List.tsx`
- `packages/ui/components/form/step/Stepper.tsx`
- `packages/lib/turndownService.ts`
- `packages/features/shell/CalAiBanner.tsx`
- `packages/app-store/_components/AppNotInstalledMessage.tsx`
- `apps/web/modules/bookings/views/bookings-single-view.tsx`
- `apps/web/components/apps/make/Setup.tsx`
- `apps/web/components/apps/zapier/Setup.tsx`
- `apps/web/modules/auth/forgot-password/[id]/forgot-password-single-view.tsx`
---
### Evidence
- Manual tests performed:
- Dev and production builds: confirmed hydration warnings present before the change and resolved after migration to the new `Link` API.
- Interacted with Dropdowns, List items, Buttons-as-Links, and Stepper to validate keyboard navigation and no nested anchors/buttons.
- Reproduced and fixed the `null parentNode` error path in `turndownService`.
- Attachments to include with the issue/PR:
- Console screenshot showing hydration warning(s) (before)
- Short GIF showing dropdown/button behavior before vs after (recommended)
- Relevant console stack trace(s) for the runtime exception(s)
- Testing notes: run the smoke test checklist in `PR_DESCRIPTION.md` and verify CI (typecheck/lint/tests) passes.
## Issue Summary
One-sentence explanation: Next.js `Link` components using `legacyBehavior` caused DOM nesting, ref-forwarding and hydration/runtime errors (e.g. `null parentNode`); this PR migrates `Link` usage to the new API and fixes the related issues.
---
## Steps to Reproduce
1. Prepare the repository and dependencies:
- `yarn install && yarn dedupe`
2. Start the app in dev or production build:
- `yarn dev` (or `yarn build && yarn start`)
3. Open pages that use Link / interactive components such as:
- Bookings single view, Forgot-password single view, AppNotInstalledMessage, Make & Zapier setup pages, pages with Dropdowns/Buttons-as-Links/List/Stepper
4. Interact and observe:
- Watch the browser console for hydration warnings and runtime errors when opening dropdowns or navigating via keyboard.
5. Reproduce specific runtime error path:
- Trigger flows that convert HTML → markdown (turndownService) or render server/client inconsistent Link markup to reproduce `Cannot read properties of null (reading 'parentNode')` or hydration mismatch.
Any other relevant information: this is a bug because `legacyBehavior` produced inconsistent server vs client HTML structure and unsafe ref usage, causing developer-facing console warnings and potential user-facing UI breakage. Expected behavior is deterministic SSR → client render with correct element types, correct ref forwarding, and preserved accessibility (keyboard focus, ARIA).
---
## Actual Results
- Hydration mismatch warnings appear in the browser console on initial page load.
- Runtime errors such as `Cannot read properties of null (reading 'parentNode')` from `turndownService` in certain flows.
- Broken ref forwarding and nested interactive elements (anchor inside button or vice-versa) leading to unexpected keyboard behavior and accessibility regressions.
- ESLint warnings related to unsafe `any` casts and unused imports in affected files.
---
## Expected Results
- No hydration mismatch warnings on page load.
- No runtime exceptions from `turndownService` or component render paths.
- Correct ref forwarding (use of `forwardRef` where needed) and no nested interactive elements.
- Keyboard interaction and focus behavior remain correct and accessible.
- Clean lint/type output for the modified files.
---
## Technical details
- Environment used for testing: macOS (local dev), Node.js 18+ recommended, Yarn
- Commands used:
- `yarn install && yarn dedupe`
- `yarn dev`
- `yarn build && yarn start`
- `yarn lint`
- `yarn test`
- Branch with fixes: `fix/next-link-hydration`
- Key files changed:
- `packages/ui/components/dropdown/Dropdown.tsx`
- `packages/ui/components/button/Button.tsx`
- `packages/ui/components/list/List.tsx`
- `packages/ui/components/form/step/Stepper.tsx`
- `packages/lib/turndownService.ts`
- `packages/features/shell/CalAiBanner.tsx`
- `packages/app-store/_components/AppNotInstalledMessage.tsx`
- `apps/web/modules/bookings/views/bookings-single-view.tsx`
- `apps/web/components/apps/make/Setup.tsx`
- `apps/web/components/apps/zapier/Setup.tsx`
- `apps/web/modules/auth/forgot-password/[id]/forgot-password-single-view.tsx`
---
## Evidence
- Manual tests performed:
- Dev and production builds: confirmed hydration warnings present before the change and resolved after migration to the new `Link` API.
- Interacted with Dropdowns, List items, Buttons-as-Links, and Stepper to validate keyboard navigation and no nested anchors/buttons.
- Reproduced and fixed the `null parentNode` error path in `turndownService`.
- Attachments to include with the issue/PR:
- Console screenshot showing hydration warning(s) (before)
- Short GIF showing dropdown/button behavior before vs after (recommended)
- Relevant console stack trace(s) for the runtime exception(s)
- Testing notes: run the smoke test checklist in `PR_DESCRIPTION.md` and verify CI (typecheck/lint/tests) passes.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

3-3: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 Prompt for AI Agents
In ISSUE.md around lines 3 to 74, the heading hierarchy jumps from the top-level
H1 to H3 headings causing markdownlint MD001; update the section headings (e.g.,
"Issue Summary", "Steps to Reproduce", "Actual Results", "Expected Results",
"Technical details", "Evidence") from H3 (###) to H2 (##) so each heading level
increments by one after the initial H1, preserving existing text and
indentation.

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 (3)
packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts (3)

1-1: File-scoped ESLint disable is too broad; narrow it

Avoid disabling rules for the whole file. After typing baseData and fixing Zod masks, you can drop this. If needed, use line-scoped disables just where unavoidable.

-/* eslint-disable @typescript-eslint/no-explicit-any, no-unsafe-optional-chaining */
+// Consider removing global disables and using line-scoped disables only where strictly needed.

122-136: Remove as any in Zod pick/omit; use a typed mask

Replace the as any masks with a typed mask that aligns with the Zod object’s shape, or minimally add line-scoped disables.

Example without any:

const managedKeys = Object.keys(allManagedEventTypeProps) as Array<keyof typeof _ManagedEventTypeModel['shape']>;
const managedMask = Object.fromEntries(managedKeys.map((k) => [k, true])) as {
  [K in keyof typeof _ManagedEventTypeModel['shape']]?: true;
};

const allManagedEventTypePropsZod = _ManagedEventTypeModel.pick(managedMask);
const managedEventTypeValues = allManagedEventTypePropsZod
  .omit({ locations: true, scheduleId: true, destinationCalendar: true }) // build unlocked mask explicitly
  .parse(eventType);

const unlockedMask = { locations: true, scheduleId: true, destinationCalendar: true } as const;
const unlockedEventTypeValues = allManagedEventTypePropsZod.pick(unlockedMask).parse(eventType);

If keeping current approach, limit to:

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const allManagedEventTypePropsZod = _ManagedEventTypeModel.pick(allManagedEventTypeProps as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const managedEventTypeValues = allManagedEventTypePropsZod.omit(unlockedManagedEventTypeProps as any).parse(eventType);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const unlockedEventTypeValues = allManagedEventTypePropsZod.pick(unlockedManagedEventTypeProps as any).parse(eventType);

90-98: Clean up unused param and prefer named export

  • _updatedValues is unused. Remove it from the function params (and from the props type if not needed) to reduce noise.
  • Prefer named export over default, per repo guidelines. As per coding guidelines.
-export default async function handleChildrenEventTypes({
+export async function handleChildrenEventTypes({
   eventTypeId: parentId,
   oldEventType,
   updatedEventType,
   children,
   prisma,
   profileId,
-  updatedValues: _updatedValues,
 }: handleChildrenEventTypesProps) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 b237a12 and 807c51b.

📒 Files selected for processing (2)
  • packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts (5 hunks)
  • packages/ui/components/address/Select.tsx (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/ui/components/address/Select.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/features/ee/managed-event-types/lib/handleChildrenEventTypes.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/features/ee/managed-event-types/lib/handleChildrenEventTypes.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/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts
🧠 Learnings (1)
📚 Learning: 2025-09-12T07:15:58.056Z
Learnt from: hbjORbj
PR: calcom/cal.com#23475
File: packages/features/credentials/handleDeleteCredential.ts:5-10
Timestamp: 2025-09-12T07:15:58.056Z
Learning: When EventTypeAppMetadataSchema is used only in z.infer<typeof EventTypeAppMetadataSchema> type annotations or type casts, it can be imported as a type-only import since this usage is purely at the TypeScript type level and doesn't require the runtime value.

Applied to files:

  • packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts
🧬 Code graph analysis (1)
packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts (1)
packages/prisma/zod-utils.ts (2)
  • allManagedEventTypeProps (627-696)
  • unlockedManagedEventTypeProps (700-704)
⏰ 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). (4)
  • GitHub Check: Tests / Unit
  • GitHub Check: Type check / check-types
  • GitHub Check: Linters / lint
  • GitHub Check: Codacy Static Code Analysis

Comment on lines +170 to 213
// build data object to avoid duplicate property definitions in object literal
data: (() => {
const baseData: any = {
profileId: profileId ?? null,
...managedValuesWithoutExplicit,
...unlockedEventTypeValues,
// pre-genned as allowed null
locations: Array.isArray(unlockedEventTypeValues.locations)
? unlockedEventTypeValues.locations
: undefined,
},
bookingLimits:
(managedEventTypeValues.bookingLimits as unknown as Prisma.InputJsonObject) ?? undefined,
recurringEvent:
(managedEventTypeValues.recurringEvent as unknown as Prisma.InputJsonValue) ?? undefined,
metadata: (managedEventTypeValues.metadata as Prisma.InputJsonValue) ?? undefined,
bookingFields: (managedEventTypeValues.bookingFields as Prisma.InputJsonValue) ?? undefined,
durationLimits: (managedEventTypeValues.durationLimits as Prisma.InputJsonValue) ?? undefined,
eventTypeColor: (managedEventTypeValues.eventTypeColor as Prisma.InputJsonValue) ?? undefined,
onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false,
userId,
users: {
connect: [{ id: userId }],
},
parentId,
hidden: children?.find((ch) => ch.owner.id === userId)?.hidden ?? false,
workflows: currentWorkflowIds && {
create: currentWorkflowIds.map((wfId) => ({ workflowId: wfId })),
},
/**
* RR Segment isn't applicable for managed event types.
*/
rrSegmentQueryValue: undefined,
assignRRMembersUsingSegment: false,
useEventLevelSelectedCalendars: false,
restrictionScheduleId: null,
useBookerTimezone: false,
allowReschedulingCancelledBookings:
managedEventTypeValues.allowReschedulingCancelledBookings ?? false,
},
bookingLimits:
(managedEventTypeValues.bookingLimits as unknown as Prisma.InputJsonObject) ?? undefined,
recurringEvent:
(managedEventTypeValues.recurringEvent as unknown as Prisma.InputJsonValue) ?? undefined,
metadata: (managedEventTypeValues.metadata as Prisma.InputJsonValue) ?? undefined,
bookingFields: (managedEventTypeValues.bookingFields as Prisma.InputJsonValue) ?? undefined,
durationLimits: (managedEventTypeValues.durationLimits as Prisma.InputJsonValue) ?? undefined,
eventTypeColor: (managedEventTypeValues.eventTypeColor as Prisma.InputJsonValue) ?? undefined,
onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false,
userId,
users: {
connect: [{ id: userId }],
},
parentId,
hidden: children?.find((ch) => ch.owner.id === userId)?.hidden ?? false,
workflows: currentWorkflowIds && {
create: currentWorkflowIds.map((wfId) => ({ workflowId: wfId })),
},
/**
* RR Segment isn't applicable for managed event types.
*/
rrSegmentQueryValue: undefined,
assignRRMembersUsingSegment: false,
useEventLevelSelectedCalendars: false,
restrictionScheduleId: null,
useBookerTimezone: false,
allowReschedulingCancelledBookings:
managedEventTypeValues.allowReschedulingCancelledBookings ?? false,
};
// set instantMeetingScheduleId explicitly after building baseData to avoid duplicate definitions
baseData.instantMeetingScheduleId = eventType.instantMeetingScheduleId ?? undefined;
return baseData;
})(),
});
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 | 🔴 Critical

🧩 Analysis chain

instantMeetingScheduleId is never loaded; create payload always sets it to undefined

You set baseData.instantMeetingScheduleId from eventType, but eventType is fetched with select: allManagedEventTypeProps, which doesn’t include instantMeetingScheduleId. Result: field is always undefined in creates.

Fix by selecting it explicitly (keep it out of managed picks) and type the payload:

TypeScript (outside this hunk):

// ensure it's selected
const eventType = await prisma.eventType.findFirst({
  where: { id: parentId },
  select: { ...allManagedEventTypeProps, instantMeetingScheduleId: true },
});

Apply within this hunk:

-            const baseData: any = {
+            const baseData: Prisma.EventTypeCreateInput = {
               profileId: profileId ?? null,
               ...managedValuesWithoutExplicit,
               ...unlockedEventTypeValues,
               // pre-genned as allowed null
               locations: Array.isArray(unlockedEventTypeValues.locations)
                 ? unlockedEventTypeValues.locations
                 : undefined,
@@
-              workflows: currentWorkflowIds && {
-                create: currentWorkflowIds.map((wfId) => ({ workflowId: wfId })),
-              },
+              workflows: currentWorkflowIds?.length
+                ? { create: currentWorkflowIds.map((wfId) => ({ workflowId: wfId })) }
+                : undefined,
@@
-            baseData.instantMeetingScheduleId = eventType.instantMeetingScheduleId ?? undefined;
+            baseData.instantMeetingScheduleId = eventType.instantMeetingScheduleId ?? undefined;

Verification script to confirm the field exists in the repo:


🏁 Script executed:

#!/bin/bash
# Look for the field in Prisma model and usages
rg -nP -C2 '\binstantMeetingScheduleId\b'

Length of output: 13202


🏁 Script executed:

#!/bin/bash
rg -n 'allManagedEventTypeProps' -C5

Length of output: 12699


🏁 Script executed:

#!/bin/bash
rg -n 'eslint-disable' -C2 packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts

Length of output: 151


🏁 Script executed:

#!/bin/bash
rg -n 'export const allManagedEventTypeProps' -A200 packages/prisma/zod-utils.ts

Length of output: 7243


Select and propagate instantMeetingScheduleId in the create payload
The eventType is fetched with select: allManagedEventTypeProps, which doesn’t include instantMeetingScheduleId, so baseData.instantMeetingScheduleId is always undefined. Include it explicitly and tighten your typings:

// ensure it’s selected
const eventType = await prisma.eventType.findFirst({
  where: { id: parentId },
  select: { ...allManagedEventTypeProps, instantMeetingScheduleId: true },
});
- const baseData: any = {
+ const baseData: Prisma.EventTypeCreateInput = {
    profileId: profileId ?? null,
    … 
-   workflows: currentWorkflowIds && {
-     create: currentWorkflowIds.map((wfId) => ({ workflowId: wfId })),
-   },
+   workflows: currentWorkflowIds?.length
+     ? { create: currentWorkflowIds.map((wfId) => ({ workflowId: wfId })) }
+     : undefined,
  };
- baseData.instantMeetingScheduleId = eventType.instantMeetingScheduleId ?? undefined;
+ baseData.instantMeetingScheduleId = eventType.instantMeetingScheduleId ?? undefined;
🤖 Prompt for AI Agents
In packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts
around lines 170 to 213, the code assigns baseData.instantMeetingScheduleId from
eventType but the earlier prisma query did not select instantMeetingScheduleId
so it will always be undefined; update the prisma.eventType.findFirst/select to
include instantMeetingScheduleId: true (e.g., spread allManagedEventTypeProps
and add instantMeetingScheduleId: true), and tighten the TypeScript typings for
the fetched eventType to include instantMeetingScheduleId (and handle possible
null eventType) so baseData.instantMeetingScheduleId can be populated correctly.

Copy link
Contributor

@keithwillcode keithwillcode left a comment

Choose a reason for hiding this comment

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

Please fix yarn.lock. Too many things removed from it.
Also please remove the ISSUE.md, ISSUE_BODY.md and PR_DESCRIPTION.md files

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

Labels

🐛 bug Something isn't working community Created by Linear-GitHub Sync 🧹 Improvements Improvements to existing features. Mostly UX/UI size/XXL ui area: UI, frontend, button, form, input

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate off Next.js <Link legacyBehavior> — hydration, ref-forwarding, and runtime fixes

2 participants