Repository: jaronheard/soonlist-turbo
Pull request #508: Add timezone support and user timezone preference
Author: login: codegen-sh[bot]
Created: 2025-03-25T03:16:33Z | Merged: 2025-03-25T06:49:53Z

## Description

This PR addresses the timezone issue in the Soonlist Expo app where new events are hardcoded to "America/Los_Angeles" (Pacific Time) instead of using the user's device timezone. This explains why users in EST are seeing events in PT despite being in EST.

## Changes

1. **Use device timezone by default:**
   - Updated `useCreateEvent.ts` to use the user's device timezone instead of hardcoding "America/Los_Angeles"
   - This ensures that new events are created in the user's local timezone by default

2. **Added user timezone preference:**
   - Added `userTimezone` to the app store to persist the user's preferred timezone
   - Added a timezone selector in the account settings screen
   - This allows users to explicitly set their preferred timezone for all new events

3. **Improved timezone handling:**
   - Updated the event creation flow to use the user's preferred timezone from the store
   - Added a new selector hook `useUserTimezone()` for easy access to the user's timezone preference

## Testing

- Create a new event via text, URL, or image - it should use your device timezone by default
- Change the timezone in settings and create a new event - it should use your selected timezone
- The timezone setting should persist between app restarts

## Related Linear Issue
Time-zone support (ID: a92c497a-d478-46e1-b711-f9fd81b4b23f)


Diff:
diff --git a/apps/expo/src/app/_layout.tsx b/apps/expo/src/app/_layout.tsx
index 6e6a36b49..e69af25da 100644
--- a/apps/expo/src/app/_layout.tsx
+++ b/apps/expo/src/app/_layout.tsx
@@ -1,4 +1,4 @@
-import { Platform, Text, View } from "react-native";
+import { Platform, StyleSheet, Text, View } from "react-native";
 import appsFlyer from "react-native-appsflyer";
 import {
   SafeAreaProvider,
@@ -31,11 +31,21 @@ import { useCalendar } from "~/hooks/useCalendar";
 import { useIntentHandler } from "~/hooks/useIntentHandler";
 import { useMediaPermissions } from "~/hooks/useMediaPermissions";
 import { useOTAUpdates } from "~/hooks/useOTAUpdates";
+import { useTimezoneAlert } from "~/hooks/useTimezoneAlert";
 import { useAppStore } from "~/store";
 import Config from "~/utils/config";
+import { getUserTimeZone } from "~/utils/dates";
 import { logDebug, logError } from "~/utils/errorLogging";
 import { getKeyChainAccessGroup } from "~/utils/getKeyChainAccessGroup";
 
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    justifyContent: "center",
+    alignItems: "center",
+  },
+});
+
 export function ErrorBoundary({ error, retry }: ErrorBoundaryProps) {
   const insets = useSafeAreaInsets();
 
@@ -115,12 +125,18 @@ appsFlyer.initSdk(
 
 function RootLayout() {
   const clerkPublishableKey = Config.clerkPublishableKey;
+  const { setUserTimezone } = useAppStore();
+
+  useEffect(() => {
+    // Initialize user timezone on app start
+    setUserTimezone(getUserTimeZone());
+  }, [setUserTimezone]);
 
   if (!clerkPublishableKey) {
     return (
-      <Text>
-        No Clerk Publishable Key found. Please check your environment.
-      </Text>
+      <View style={styles.container}>
+        <Text>Missing Clerk Publishable Key</Text>
+      </View>
     );
   }
 
@@ -278,6 +294,7 @@ function RootLayoutContent() {
 
   // The share extension logic now specifically leads to /new
   useIntentHandler();
+  useTimezoneAlert();
 
   return (
     <View style={{ flex: 1 }}>
diff --git a/apps/expo/src/app/settings/account.tsx b/apps/expo/src/app/settings/account.tsx
index 194fc6807..4e2dac7e5 100644
--- a/apps/expo/src/app/settings/account.tsx
+++ b/apps/expo/src/app/settings/account.tsx
@@ -22,6 +22,7 @@ import { toast } from "sonner-native";
 import { z } from "zod";
 
 import { Button } from "~/components/Button";
+import { TimezoneSelectNative } from "~/components/TimezoneSelectNative";
 import { UserProfileFlair } from "~/components/UserProfileFlair";
 import { useSignOut } from "~/hooks/useSignOut";
 import { useRevenueCat } from "~/providers/RevenueCatProvider";
@@ -57,7 +58,11 @@ export default function EditProfileScreen() {
     { enabled: !!user?.username },
   );
   const queryClient = useQueryClient();
-  const { resetOnboarding: resetOnboardingStore } = useAppStore();
+  const {
+    resetOnboarding: resetOnboardingStore,
+    userTimezone,
+    setUserTimezone,
+  } = useAppStore();
 
   const {
     control,
@@ -519,6 +524,23 @@ export default function EditProfileScreen() {
               </Button>
             )}
 
+            <View className="mt-8">
+              <Text className="text-lg font-semibold">Preferences</Text>
+              <View className="mt-4">
+                <Text className="mb-2 text-base font-medium">
+                  Default Timezone
+                </Text>
+                <Text className="mb-2 text-sm text-neutral-500">
+                  This timezone will be used for all new events you create.
+                </Text>
+                <TimezoneSelectNative
+                  value={userTimezone}
+                  onValueChange={setUserTimezone}
+                  placeholder="Select a timezone"
+                />
+              </View>
+            </View>
+
             <View className="mt-12">
               <Text className="text-lg font-semibold">Subscription</Text>
               {(() => {
diff --git a/apps/expo/src/components/TimezoneSelectNative.tsx b/apps/expo/src/components/TimezoneSelectNative.tsx
index 6cc34f6d0..2dc3bf286 100644
--- a/apps/expo/src/components/TimezoneSelectNative.tsx
+++ b/apps/expo/src/components/TimezoneSelectNative.tsx
@@ -17,6 +17,7 @@ import {
 import { Check, ChevronDown, Search, X } from "lucide-react-native";
 import moment from "moment-timezone";
 
+import { getUserTimeZone } from "~/utils/dates";
 import { logError } from "../utils/errorLogging";
 
 // Type for timezone data
@@ -63,10 +64,10 @@ const formatGMTOffset = (offsetMinutes: number): string => {
 // Get the current timezone using moment-timezone
 const getCurrentTimezone = (): string => {
   try {
-    return moment.tz.guess();
+    return moment.tz.guess() || getUserTimeZone();
   } catch (error) {
     logError("Error getting timezone", error);
-    return "America/Los_Angeles"; // Default to US West Coast
+    return getUserTimeZone();
   }
 };
 
diff --git a/apps/expo/src/hooks/useCreateEvent.ts b/apps/expo/src/hooks/useCreateEvent.ts
index 6d6ab7b59..0723d4606 100644
--- a/apps/expo/src/hooks/useCreateEvent.ts
+++ b/apps/expo/src/hooks/useCreateEvent.ts
@@ -5,7 +5,7 @@ import * as MediaLibrary from "expo-media-library";
 import { toast } from "sonner-native";
 
 import { useRevenueCat } from "~/providers/RevenueCatProvider";
-import { useAppStore } from "~/store";
+import { useAppStore, useUserTimezone } from "~/store";
 import { api } from "~/utils/api";
 import { logError } from "~/utils/errorLogging";
 
@@ -29,6 +29,8 @@ export function useCreateEvent() {
   const { customerInfo, showProPaywallIfNeeded } = useRevenueCat();
   const hasUnlimited = customerInfo?.entitlements.active.unlimited;
   const utils = api.useUtils();
+  const userTimezone = useUserTimezone();
+
   const eventFromUrl =
     api.ai.eventFromUrlThenCreateThenNotification.useMutation({
       onSuccess: () => {
@@ -78,7 +80,7 @@ export function useCreateEvent() {
           userId,
           username,
           lists: [],
-          timezone: "America/Los_Angeles",
+          timezone: userTimezone,
           visibility: "private",
         })) as CreateEventResult;
         return result.success && result.eventId ? result.eventId : undefined;
@@ -181,7 +183,7 @@ export function useCreateEvent() {
             userId,
             username,
             lists: [],
-            timezone: "America/Los_Angeles",
+            timezone: userTimezone,
             visibility: "private",
           })) as CreateEventResult;
 
@@ -206,7 +208,7 @@ export function useCreateEvent() {
           userId,
           username,
           lists: [],
-          timezone: "America/Los_Angeles",
+          timezone: userTimezone,
           visibility: "private",
         })) as CreateEventResult;
         return result.success && result.eventId ? result.eventId : undefined;
@@ -222,6 +224,7 @@ export function useCreateEvent() {
       eventFromImage,
       eventFromRaw,
       setIsImageLoading,
+      userTimezone,
     ],
   );
 
diff --git a/apps/expo/src/hooks/useTimezoneAlert.ts b/apps/expo/src/hooks/useTimezoneAlert.ts
new file mode 100644
index 000000000..198b6dfa2
--- /dev/null
+++ b/apps/expo/src/hooks/useTimezoneAlert.ts
@@ -0,0 +1,74 @@
+import type { AppStateStatus } from "react-native";
+import { useEffect, useRef } from "react";
+import { Alert, AppState } from "react-native";
+
+import { useAppStore, useUserTimezone } from "~/store";
+import { getUserTimeZone } from "~/utils/dates";
+
+export function useTimezoneAlert() {
+  const userTimezone = useUserTimezone();
+  const { setUserTimezone, hasShownTimezoneAlert, setHasShownTimezoneAlert } =
+    useAppStore();
+  const prevUserTimezoneRef = useRef(userTimezone);
+
+  // Reset alert state when user timezone changes
+  useEffect(() => {
+    if (prevUserTimezoneRef.current !== userTimezone) {
+      setHasShownTimezoneAlert(false);
+      prevUserTimezoneRef.current = userTimezone;
+    }
+  }, [userTimezone, setHasShownTimezoneAlert]);
+
+  useEffect(() => {
+    const checkTimezone = () => {
+      const systemTimezone = getUserTimeZone();
+
+      if (
+        userTimezone &&
+        systemTimezone !== userTimezone &&
+        !hasShownTimezoneAlert
+      ) {
+        Alert.alert(
+          "Timezone Mismatch",
+          `Your device timezone (${systemTimezone}) is different from your selected timezone (${userTimezone}). Would you like to update to your device timezone?`,
+          [
+            {
+              text: "Keep Current",
+              style: "cancel",
+              onPress: () => setHasShownTimezoneAlert(true),
+            },
+            {
+              text: "Update",
+              onPress: () => {
+                setUserTimezone(systemTimezone);
+                setHasShownTimezoneAlert(true);
+              },
+            },
+          ],
+        );
+      }
+    };
+
+    // Check on mount and when app comes to foreground
+    checkTimezone();
+
+    // Re-check timezone when app becomes active
+    const subscription = AppState.addEventListener(
+      "change",
+      (nextAppState: AppStateStatus) => {
+        if (nextAppState === "active") {
+          checkTimezone();
+        }
+      },
+    );
+
+    return () => {
+      subscription.remove();
+    };
+  }, [
+    userTimezone,
+    setUserTimezone,
+    hasShownTimezoneAlert,
+    setHasShownTimezoneAlert,
+  ]);
+}
diff --git a/apps/expo/src/store.ts b/apps/expo/src/store.ts
index e7335fe31..1364db1ca 100644
--- a/apps/expo/src/store.ts
+++ b/apps/expo/src/store.ts
@@ -5,6 +5,7 @@ import { createJSONStorage, persist } from "zustand/middleware";
 
 import type { OnboardingData, OnboardingStep } from "~/types/onboarding";
 import type { RouterOutputs } from "~/utils/api";
+import { getUserTimeZone } from "./utils/dates";
 
 export interface RecentPhoto {
   id: string;
@@ -43,6 +44,12 @@ interface AppState {
   setIsCalendarModalVisible: (isVisible: boolean) => void;
   setShowAllCalendars: (show: boolean) => void;
 
+  // Timezone-related state
+  userTimezone: string;
+  hasShownTimezoneAlert: boolean;
+  setUserTimezone: (timezone: string) => void;
+  setHasShownTimezoneAlert: (hasShown: boolean) => void;
+
   // Event input state for /add route
   addEventState: AddEventInputState;
   // Event input state for /new route (share extension)
@@ -113,12 +120,17 @@ export const useAppStore = create<AppState>()(
       isCalendarModalVisible: false,
       showAllCalendars: false,
       userPriority: null,
+      userTimezone: getUserTimeZone(),
+      hasShownTimezoneAlert: false,
 
       setFilter: (filter) => set({ filter }),
       setIntentParams: (params) => set({ intentParams: params }),
       setIsCalendarModalVisible: (isVisible) =>
         set({ isCalendarModalVisible: isVisible }),
       setShowAllCalendars: (show) => set({ showAllCalendars: show }),
+      setUserTimezone: (timezone) => set({ userTimezone: timezone }),
+      setHasShownTimezoneAlert: (hasShown) =>
+        set({ hasShownTimezoneAlert: hasShown }),
 
       // Initialize event input state for both routes
       addEventState: {
@@ -326,6 +338,8 @@ export const useAppStore = create<AppState>()(
           hasMediaPermission: false,
           hasFullPhotoAccess: false,
           userPriority: null,
+          userTimezone: getUserTimeZone(),
+          hasShownTimezoneAlert: false,
           onboardingData: {},
           currentOnboardingStep: null,
         }),
@@ -341,3 +355,4 @@ export const useAppStore = create<AppState>()(
 export const useRecentPhotos = () => useAppStore((state) => state.recentPhotos);
 export const useHasMediaPermission = () =>
   useAppStore((state) => state.hasMediaPermission);
+export const useUserTimezone = () => useAppStore((state) => state.userTimezone);
diff --git a/packages/api/src/routers/aiHelpers.ts b/packages/api/src/routers/aiHelpers.ts
index deeba0b4e..496ec7779 100644
--- a/packages/api/src/routers/aiHelpers.ts
+++ b/packages/api/src/routers/aiHelpers.ts
@@ -342,7 +342,7 @@ export async function createEventAndNotify(
 
   let { startTime, endTime, timeZone } = firstEvent;
   if (!timeZone) {
-    timeZone = "America/Los_Angeles";
+    timeZone = input.timezone;
   }
   if (!startTime) {
     startTime = "00:00";
diff --git a/packages/cal/src/prompts.ts b/packages/cal/src/prompts.ts
index 853b57606..4001855b0 100644
--- a/packages/cal/src/prompts.ts
+++ b/packages/cal/src/prompts.ts
@@ -296,10 +296,12 @@ Ensure that the generated metadata values are concise, relevant, and adhere to t
 
 const formatOffsetAsIANASoft = (offset: string) => {
   const timezone = soft(offset)[0];
-  return timezone?.iana || "America/Los_Angeles";
+  return timezone?.iana || Intl.DateTimeFormat().resolvedOptions().timeZone;
 };
 
-export const getPrompt = (timezone = "America/Los_Angeles") => {
+export const getPrompt = (
+  timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
+) => {
   const timezoneIANA = formatOffsetAsIANASoft(timezone);
   const now = Temporal.Now.instant().toZonedDateTimeISO(timezoneIANA);
   const date = now.toString();


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

Added comprehensive timezone support to handle event creation and user preferences, replacing hardcoded Pacific Time with dynamic timezone handling.

- Added `userTimezone` state management in `/apps/expo/src/store.ts` with persistence via AsyncStorage
- Implemented timezone selector UI in account settings using `TimezoneSelectNative` component
- Updated event creation flows in `useCreateEvent.ts` to use user's preferred timezone instead of hardcoded "America/Los_Angeles"
- Added `getUserTimeZone()` utility in `/apps/expo/src/utils/dates.ts` to detect device timezone as default

<sub>💡 (3/5) Reply to the bot's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!</sub>

<sub>3 file(s) reviewed, no 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_508)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T03:32:18Z
user: login: greptile-apps[bot]
body: 
## PR Summary
(updates since last review)


This PR continues the timezone support implementation with additional functionality and UI improvements.

- Added timezone mismatch detection in `useTimezoneAlert.ts` to prompt users when device and selected timezones differ
- Improved timezone initialization in `_layout.tsx` to set user timezone on app start
- Enhanced `TimezoneSelectNative` component with searchable modal interface and proper accessibility features
- Updated AI helpers and prompts to use dynamic timezone handling instead of hardcoded values
- Fixed timezone formatting in `prompts.ts` using `formatOffsetAsIANASoft` for better compatibility

The changes look well-structured with proper error handling and state management, though there might be edge cases to consider around timezone validation and UI interactions.

<sub>6 file(s) reviewed, 3 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_508)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T06:35:24Z
user: login: jaronheard
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-25T06:41:56Z
user: login: greptile-apps[bot]
body: 
## PR Summary
(updates since last review)


This PR continues to enhance timezone support with additional refinements and alert handling.

- Added timezone alert state persistence in `store.ts` with `hasShownTimezoneAlert` flag to prevent repeated alerts
- Implemented timezone mismatch detection in `useTimezoneAlert.ts` that checks when app becomes active
- Added timezone initialization in `_layout.tsx` using `getUserTimeZone()` on app start
- Enhanced timezone formatting in `dates.ts` with proper IANA timezone handling and offset calculations

The changes look solid but consider adding error boundaries around timezone detection to handle edge cases where system timezone is unavailable.

<sub>2 file(s) reviewed, 1 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_508)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T06:43:13Z

Review comments:
diff_hunk: @@ -0,0 +1,65 @@
+import type { AppStateStatus } from "react-native";
+import { useEffect, useRef, useState } from "react";
+import { Alert, AppState } from "react-native";
+
+import { useAppStore, useUserTimezone } from "~/store";
+import { getUserTimeZone } from "~/utils/dates";
+
+export function useTimezoneAlert() {
+  const userTimezone = useUserTimezone();
+  const { setUserTimezone } = useAppStore();
+  const [hasShownAlert, setHasShownAlert] = useState(false);
+  const prevUserTimezoneRef = useRef(userTimezone);
path: apps/expo/src/hooks/useTimezoneAlert.ts
user: login: greptile-apps[bot]
body: style: hasShownAlert state will reset on component remount, potentially showing the alert multiple times in the same session
created_at: 2025-03-25T06:34:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -63,10 +64,10 @@ const formatGMTOffset = (offsetMinutes: number): string => {
 // Get the current timezone using moment-timezone
 const getCurrentTimezone = (): string => {
   try {
-    return moment.tz.guess();
+    return moment.tz.guess() || getUserTimeZone();
   } catch (error) {
path: apps/expo/src/components/TimezoneSelectNative.tsx
user: login: greptile-apps[bot]
body: logic: Consider handling the case where both moment.tz.guess() and getUserTimeZone() fail - currently could return undefined

```suggestion
  try {
    return moment.tz.guess() || getUserTimeZone() || 'UTC';
  } catch (error) {
```
created_at: 2025-03-25T06:34:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,10 +296,12 @@ Ensure that the generated metadata values are concise, relevant, and adhere to t
 
 const formatOffsetAsIANASoft = (offset: string) => {
   const timezone = soft(offset)[0];
-  return timezone?.iana || "America/Los_Angeles";
+  return timezone?.iana || Intl.DateTimeFormat().resolvedOptions().timeZone;
 };
 
-export const getPrompt = (timezone = "America/Los_Angeles") => {
+export const getPrompt = (
+  timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
+) => {
path: packages/cal/src/prompts.ts
user: login: greptile-apps[bot]
body: style: Same timezone resolution is repeated - consider extracting to a shared utility function to avoid duplication
created_at: 2025-03-25T06:35:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,10 +296,12 @@ Ensure that the generated metadata values are concise, relevant, and adhere to t
 
 const formatOffsetAsIANASoft = (offset: string) => {
   const timezone = soft(offset)[0];
-  return timezone?.iana || "America/Los_Angeles";
+  return timezone?.iana || Intl.DateTimeFormat().resolvedOptions().timeZone;
 };
 
-export const getPrompt = (timezone = "America/Los_Angeles") => {
+export const getPrompt = (
+  timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
+) => {
path: packages/cal/src/prompts.ts
user: login: jaronheard
body: eh this is ok
created_at: 2025-03-25T06:41:55Z
author_association: OWNER
diff_hunk: @@ -326,6 +339,7 @@ export const useAppStore = create<AppState>()(
           hasMediaPermission: false,
           hasFullPhotoAccess: false,
           userPriority: null,
+          userTimezone: getUserTimeZone(),
path: apps/expo/src/store.ts
user: login: greptile-apps[bot]
body: logic: resetStore() should also reset hasShownTimezoneAlert to false when resetting userTimezone

```suggestion
          userTimezone: getUserTimeZone(),
          hasShownTimezoneAlert: false,
```
created_at: 2025-03-25T06:42:53Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-03-25T03:16:36Z
body: [vc]: #12oS7vGRRcpdmdJn+FYZn4EU8wIaNONyGPQJjitwHls=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzb29ubGlzdC10dXJibyIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3Nvb25saXN0L3Nvb25saXN0LXR1cmJvL0RMZWE0ZXVzeXRBN3gyRm9VUk4zcHdyaldRNFgiLCJwcmV2aWV3VXJsIjoiIiwibmV4dENvbW1pdFN0YXR1cyI6IlNLSVBQRUQiLCJyb290RGlyZWN0b3J5IjoiYXBwcy93ZWIifV19
**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/DLea4eusytA7x2FoURN3pwrjWQ4X)) |  |  | Mar 25, 2025 6:47am |
</details>


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-25T03:16:40Z
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=508):

- 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
user: login: jaronheard
created_at: 2025-03-25T06:33:57Z
body: @greptile-app re-review
author_association: OWNER
user: login: jaronheard
created_at: 2025-03-25T06:42:24Z
body: @greptile-apps rereview
author_association: OWNER