Repository: jaronheard/soonlist-turbo
Pull request #516: Adjust empty state based on hasUnlimited
Author: login: codegen-sh[bot]
Created: 2025-03-26T22:19:39Z | Merged: 2025-03-26T23:03:13Z

This PR implements the requested changes to show a different empty state in UserEventsList based on whether a user has unlimited access.

## Changes:

1. Updated the `UserEventsList` component to:
   - Accept a new `hasUnlimited` prop (defaulting to false)
   - Show a different empty state when `hasUnlimited` is false:
     - "Try free now" heading
     - "Found an issue? Message us" subheading
     - A "Message us" button that links to Intercom

2. Updated all files that use the `UserEventsList` component to pass the `hasUnlimited` prop:
   - feed.tsx
   - past.tsx
   - discover.tsx
   - demo-feed.tsx (always passes true for demo mode)

## Testing:

The empty state will now show:
- For users without unlimited: "Try free now" with a link to message support
- For users with unlimited: The original "Save events instantly" message

This addresses the issue described in TIM-940.


Diff:
diff --git a/apps/expo/src/app/(onboarding)/onboarding/demo-feed.tsx b/apps/expo/src/app/(onboarding)/onboarding/demo-feed.tsx
index c2bfd7732..2a181bae7 100644
--- a/apps/expo/src/app/(onboarding)/onboarding/demo-feed.tsx
+++ b/apps/expo/src/app/(onboarding)/onboarding/demo-feed.tsx
@@ -149,6 +149,7 @@ export default function DemoFeedScreen() {
           isFetchingNextPage={false}
           stats={stats}
           demoMode
+          hasUnlimited={true} // Demo mode always shows unlimited version
         />
       </View>
 
diff --git a/apps/expo/src/app/(tabs)/discover.tsx b/apps/expo/src/app/(tabs)/discover.tsx
index d2f5530f0..1e4012086 100644
--- a/apps/expo/src/app/(tabs)/discover.tsx
+++ b/apps/expo/src/app/(tabs)/discover.tsx
@@ -8,11 +8,15 @@ import AddEventButton from "~/components/AddEventButton";
 import LoadingSpinner from "~/components/LoadingSpinner";
 import SaveButton from "~/components/SaveButton";
 import UserEventsList from "~/components/UserEventsList";
+import { useRevenueCat } from "~/providers/RevenueCatProvider";
 import { api } from "~/utils/api";
 import { getPlanStatusFromUser } from "~/utils/plan";
 
 export default function Page() {
   const { user, isLoaded, isSignedIn } = useUser();
+  const { customerInfo } = useRevenueCat();
+  const hasUnlimited =
+    customerInfo?.entitlements.active.unlimited?.isActive ?? false;
 
   const eventsQuery = api.event.getDiscoverInfinite.useInfiniteQuery(
     {
@@ -23,13 +27,11 @@ export default function Page() {
     },
   );
 
-  // Move this hook up before any conditional returns
   const savedEventIdsQuery = api.event.getSavedIdsForUser.useQuery(
     {
       userName: user?.username ?? "",
     },
     {
-      // Add enabled option in the second parameter to prevent query when user is not signed in
       enabled: isLoaded && isSignedIn && !!user && !!user.username,
     },
   );
@@ -96,6 +98,7 @@ export default function Page() {
             isFetchingNextPage={eventsQuery.isFetchingNextPage}
             ActionButton={SaveButtonWrapper}
             showCreator="always"
+            hasUnlimited={hasUnlimited}
           />
           <AddEventButton />
         </View>
diff --git a/apps/expo/src/app/(tabs)/feed.tsx b/apps/expo/src/app/(tabs)/feed.tsx
index 4fb2ef3ae..d03ab526e 100644
--- a/apps/expo/src/app/(tabs)/feed.tsx
+++ b/apps/expo/src/app/(tabs)/feed.tsx
@@ -11,6 +11,7 @@ import type { RouterOutputs } from "~/utils/api";
 import AddEventButton from "~/components/AddEventButton";
 import LoadingSpinner from "~/components/LoadingSpinner";
 import UserEventsList from "~/components/UserEventsList";
+import { useRevenueCat } from "~/providers/RevenueCatProvider";
 import { useAppStore } from "~/store";
 import { api } from "~/utils/api";
 import { logError } from "../../utils/errorLogging";
@@ -50,8 +51,10 @@ function GoButton({
 function MyFeed() {
   const { user, isLoaded, isSignedIn } = useUser();
   const { hasCompletedOnboarding } = useAppStore();
+  const { customerInfo } = useRevenueCat();
+  const hasUnlimited =
+    customerInfo?.entitlements.active.unlimited?.isActive ?? false;
 
-  // Fetch user data from database to check onboarding status
   const userQuery = api.user.getById.useQuery(
     { id: user?.id ?? "" },
     { enabled: isLoaded && isSignedIn && !!user.id },
@@ -85,7 +88,6 @@ function MyFeed() {
 
   const events = eventsQuery.data?.pages.flatMap((page) => page.events) ?? [];
 
-  // Track any pending AI mutations
   const pendingAIMutations = useMutationState({
     filters: {
       mutationKey: ["ai"],
@@ -107,7 +109,6 @@ function MyFeed() {
     return <Redirect href="/sign-in" />;
   }
 
-  // Check if onboarding is completed in either app state OR database
   const dbHasCompletedOnboarding = !!userQuery.data?.onboardingCompletedAt;
   if (!hasCompletedOnboarding && !dbHasCompletedOnboarding) {
     return <Redirect href="/(onboarding)/onboarding" />;
@@ -129,6 +130,7 @@ function MyFeed() {
             showCreator="otherUsers"
             stats={statsQuery.data}
             promoCard={{ type: "addEvents" }}
+            hasUnlimited={hasUnlimited}
           />
           <AddEventButton />
         </View>
diff --git a/apps/expo/src/app/(tabs)/past.tsx b/apps/expo/src/app/(tabs)/past.tsx
index be3886bc2..3947ceed1 100644
--- a/apps/expo/src/app/(tabs)/past.tsx
+++ b/apps/expo/src/app/(tabs)/past.tsx
@@ -6,10 +6,14 @@ import { useUser } from "@clerk/clerk-expo";
 import AddEventButton from "~/components/AddEventButton";
 import LoadingSpinner from "~/components/LoadingSpinner";
 import UserEventsList from "~/components/UserEventsList";
+import { useRevenueCat } from "~/providers/RevenueCatProvider";
 import { api } from "~/utils/api";
 
 export default function PastEvents() {
   const { user, isLoaded, isSignedIn } = useUser();
+  const { customerInfo } = useRevenueCat();
+  const hasUnlimited =
+    customerInfo?.entitlements.active.unlimited?.isActive ?? false;
 
   const eventsQuery = api.event.getEventsForUser.useInfiniteQuery(
     {
@@ -60,6 +64,7 @@ export default function PastEvents() {
             showCreator="otherUsers"
             isRefetching={eventsQuery.isRefetching}
             isFetchingNextPage={eventsQuery.isFetchingNextPage}
+            hasUnlimited={hasUnlimited}
           />
           <AddEventButton />
         </View>
diff --git a/apps/expo/src/app/settings/account.tsx b/apps/expo/src/app/settings/account.tsx
index 4e2dac7e5..c00bd1aa2 100644
--- a/apps/expo/src/app/settings/account.tsx
+++ b/apps/expo/src/app/settings/account.tsx
@@ -546,7 +546,8 @@ export default function EditProfileScreen() {
               {(() => {
                 if (!user) return null;
                 const hasUnlimited =
-                  customerInfo?.entitlements.active.unlimited;
+                  customerInfo?.entitlements.active.unlimited?.isActive ??
+                  false;
 
                 const stripeSubscription =
                   customerInfo?.originalPurchaseDate &&
diff --git a/apps/expo/src/components/AddEventButton.tsx b/apps/expo/src/components/AddEventButton.tsx
index 224c0a9fd..34ff54dbd 100644
--- a/apps/expo/src/components/AddEventButton.tsx
+++ b/apps/expo/src/components/AddEventButton.tsx
@@ -14,7 +14,8 @@ import { logError } from "../utils/errorLogging";
 export default function AddEventButton() {
   const { resetAddEventState, setImagePreview, setInput } = useAppStore();
   const { customerInfo, showProPaywallIfNeeded } = useRevenueCat();
-  const hasUnlimited = customerInfo?.entitlements.active.unlimited ?? false;
+  const hasUnlimited =
+    customerInfo?.entitlements.active.unlimited?.isActive ?? false;
 
   const handlePress = useCallback(async () => {
     // If user doesn't have pro, show paywall
diff --git a/apps/expo/src/components/SubscriptionGuard.tsx b/apps/expo/src/components/SubscriptionGuard.tsx
index 1cf4dca79..5ede1a052 100644
--- a/apps/expo/src/components/SubscriptionGuard.tsx
+++ b/apps/expo/src/components/SubscriptionGuard.tsx
@@ -5,7 +5,8 @@ import { useAppStore } from "~/store";
 
 export function SubscriptionGuard({ children }: { children: React.ReactNode }) {
   const { customerInfo, showProPaywallIfNeeded } = useRevenueCat();
-  const hasUnlimited = customerInfo?.entitlements.active.unlimited;
+  const hasUnlimited =
+    customerInfo?.entitlements.active.unlimited?.isActive ?? false;
   const hasCompletedOnboarding = useAppStore(
     (state) => state.hasCompletedOnboarding,
   );
diff --git a/apps/expo/src/components/UserEventsList.tsx b/apps/expo/src/components/UserEventsList.tsx
index 06c89022f..6b00c9ef4 100644
--- a/apps/expo/src/components/UserEventsList.tsx
+++ b/apps/expo/src/components/UserEventsList.tsx
@@ -13,6 +13,7 @@ import { Image } from "expo-image";
 import * as MediaLibrary from "expo-media-library";
 import { router } from "expo-router";
 import { useUser } from "@clerk/clerk-expo";
+import Intercom from "@intercom/intercom-react-native";
 import { useMutationState, useQueryClient } from "@tanstack/react-query";
 import {
   Copy,
@@ -331,6 +332,7 @@ interface UserEventsListProps {
   };
   promoCard?: PromoCardProps;
   demoMode?: boolean;
+  hasUnlimited?: boolean;
 }
 
 export default function UserEventsList(props: UserEventsListProps) {
@@ -345,6 +347,7 @@ export default function UserEventsList(props: UserEventsListProps) {
     stats,
     promoCard,
     demoMode,
+    hasUnlimited = false,
   } = props;
   const { user } = useUser();
   const queryClient = useQueryClient();
@@ -369,6 +372,14 @@ export default function UserEventsList(props: UserEventsListProps) {
   const isAddingEvent =
     pendingAIMutations.filter((mutation) => mutation === "pending").length > 0;
 
+  const presentIntercom = async () => {
+    try {
+      await Intercom.present();
+    } catch (error) {
+      logError("Error presenting Intercom", error);
+    }
+  };
+
   const renderEmptyState = () => {
     if ((isAddingEvent || isRefetching) && collapsedEvents.length === 0) {
       return (
@@ -378,6 +389,38 @@ export default function UserEventsList(props: UserEventsListProps) {
       );
     }
 
+    if (!hasUnlimited) {
+      return (
+        <View className="flex-1 items-center justify-center px-6">
+          <Image
+            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+            source={require("../assets/icon.png")}
+            style={{
+              width: 64,
+              height: 64,
+              marginBottom: 16,
+              borderRadius: 8,
+            }}
+            contentFit="contain"
+            cachePolicy="disk"
+            transition={100}
+          />
+          <Text className="mb-2 rounded-lg text-center text-2xl font-bold text-neutral-1">
+            Try free now
+          </Text>
+          <Pressable
+            className="mb-4 text-center text-base text-neutral-2"
+            onPress={presentIntercom}
+          >
+            <Text className="text-neutral-2">
+              Funds an issue?{" "}
+              <Text className="text-interactive-1">Message us</Text>
+            </Text>
+          </Pressable>
+        </View>
+      );
+    }
+
     return (
       <View className="flex-1 items-center justify-center px-6">
         <Image
diff --git a/apps/expo/src/hooks/useCreateEvent.ts b/apps/expo/src/hooks/useCreateEvent.ts
index 0723d4606..171dca148 100644
--- a/apps/expo/src/hooks/useCreateEvent.ts
+++ b/apps/expo/src/hooks/useCreateEvent.ts
@@ -27,7 +27,8 @@ interface CreateEventResult {
 export function useCreateEvent() {
   const { setIsImageLoading } = useAppStore();
   const { customerInfo, showProPaywallIfNeeded } = useRevenueCat();
-  const hasUnlimited = customerInfo?.entitlements.active.unlimited;
+  const hasUnlimited =
+    customerInfo?.entitlements.active.unlimited?.isActive ?? false;
   const utils = api.useUtils();
   const userTimezone = useUserTimezone();
 
@@ -219,7 +220,7 @@ export function useCreateEvent() {
     [
       hasUnlimited,
       showProPaywallIfNeeded,
-      customerInfo?.entitlements.active.unlimited,
+      customerInfo?.entitlements.active.unlimited?.isActive,
       eventFromUrl,
       eventFromImage,
       eventFromRaw,


Reviews:
user: login: greptile-apps[bot]
body: 
## PR Summary

This PR adds a `hasUnlimited` prop to UserEventsList to display different empty states based on user subscription status, with integration of RevenueCat for subscription checks and Intercom for support messaging.

- Added `hasUnlimited` prop (default: false) to `UserEventsList` in `/apps/expo/src/components/UserEventsList.tsx` with conditional empty state rendering
- Integrated RevenueCat subscription checks in feed, past, and discover tabs to properly pass `hasUnlimited` status
- Fixed typo in empty state UI text ("Funds an issue?" should be "Found an issue?")
- Added Intercom integration for support messaging when users don't have unlimited access
- Set `hasUnlimited={true}` in demo-feed for consistent unlimited feature demonstration

<sub>5 file(s) reviewed, 2 comment(s)</sub>
<sub>[Edit PR Review Bot Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=soonlist-turbo_516)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T22:51:07Z
user: login: jaronheard
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-26T22:55:24Z
user: login: jaronheard
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-26T22:55:38Z
user: login: jaronheard
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-26T23:02:27Z

Review comments:
diff_hunk: @@ -129,6 +129,7 @@ function MyFeed() {
             showCreator="otherUsers"
             stats={statsQuery.data}
             promoCard={{ type: "addEvents" }}
+            hasUnlimited={!!hasUnlimited}
path: apps/expo/src/app/(tabs)/feed.tsx
user: login: greptile-apps[bot]
body: style: Double boolean coercion (!!) is unnecessary here since hasUnlimited is already a boolean

```suggestion
            hasUnlimited={hasUnlimited}
```
created_at: 2025-03-26T22:50:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -378,6 +389,38 @@ export default function UserEventsList(props: UserEventsListProps) {
       );
     }
 
+    if (!hasUnlimited) {
+      return (
+        <View className="flex-1 items-center justify-center px-6">
+          <Image
+            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+            source={require("../assets/icon.png")}
+            style={{
+              width: 64,
+              height: 64,
+              marginBottom: 16,
+              borderRadius: 8,
+            }}
+            contentFit="contain"
+            cachePolicy="disk"
+            transition={100}
+          />
+          <Text className="mb-2 rounded-lg text-center text-2xl font-bold text-neutral-1">
+            Try free now
+          </Text>
+          <Pressable
+            className="mb-4 text-center text-base text-neutral-2"
+            onPress={presentIntercom}
+          >
+            <Text className="text-neutral-2">
+              Funds an issue?{" "}
path: apps/expo/src/components/UserEventsList.tsx
user: login: greptile-apps[bot]
body: syntax: 'Funds an issue?' should be 'Found an issue?'

```suggestion
              Found an issue?{" "}
```
created_at: 2025-03-26T22:50:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,6 +129,7 @@ function MyFeed() {
             showCreator="otherUsers"
             stats={statsQuery.data}
             promoCard={{ type: "addEvents" }}
+            hasUnlimited={!!hasUnlimited}
path: apps/expo/src/app/(tabs)/feed.tsx
user: login: jaronheard
body: typechecker says otherwise
created_at: 2025-03-26T22:55:24Z
author_association: OWNER
diff_hunk: @@ -378,6 +389,38 @@ export default function UserEventsList(props: UserEventsListProps) {
       );
     }
 
+    if (!hasUnlimited) {
+      return (
+        <View className="flex-1 items-center justify-center px-6">
+          <Image
+            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+            source={require("../assets/icon.png")}
+            style={{
+              width: 64,
+              height: 64,
+              marginBottom: 16,
+              borderRadius: 8,
+            }}
+            contentFit="contain"
+            cachePolicy="disk"
+            transition={100}
+          />
+          <Text className="mb-2 rounded-lg text-center text-2xl font-bold text-neutral-1">
+            Try free now
+          </Text>
+          <Pressable
+            className="mb-4 text-center text-base text-neutral-2"
+            onPress={presentIntercom}
+          >
+            <Text className="text-neutral-2">
+              Funds an issue?{" "}
path: apps/expo/src/components/UserEventsList.tsx
user: login: jaronheard
body: my copy, my intention
created_at: 2025-03-26T22:55:38Z
author_association: OWNER
diff_hunk: @@ -129,6 +129,7 @@ function MyFeed() {
             showCreator="otherUsers"
             stats={statsQuery.data}
             promoCard={{ type: "addEvents" }}
+            hasUnlimited={!!hasUnlimited}
path: apps/expo/src/app/(tabs)/feed.tsx
user: login: jaronheard
body: ok found an issue here
created_at: 2025-03-26T23:02:27Z
author_association: OWNER

PR comments:
user: login: vercel[bot]
created_at: 2025-03-26T22:19:41Z
body: [vc]: #GkF2N3FPkPIEak7WZ/vsDY/VIcBJbF0Pch1F5Wvahio=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzb29ubGlzdC10dXJibyIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3Nvb25saXN0L3Nvb25saXN0LXR1cmJvLzlrVDFRWDN0MkdCM1o1bWp3NEtBU0p1ZWtSM1YiLCJwcmV2aWV3VXJsIjoiIiwibmV4dENvbW1pdFN0YXR1cyI6IlNLSVBQRUQiLCJyb290RGlyZWN0b3J5IjoiYXBwcy93ZWIifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **soonlist-turbo** | ⬜️ Skipped ([Inspect](https://vercel.com/soonlist/soonlist-turbo/9kT1QX3t2GB3Z5mjw4KASJuekR3V)) |  |  | Mar 26, 2025 11:02pm |
</details>


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-26T22:19:46Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Bot user detected.
> 
> To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=jaronheard/soonlist-turbo&utm_content=516):

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

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

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR