Repository: sharan1303/study-chat-app
Pull request #27: Review-coderabbit-nitpick
Author: login: sharan1303
Created: 2025-04-01T01:35:07Z | Merged: 2025-04-01T17:13:13Z

- Removed unused content fields from resources to streamline data handling.
- Enhanced module deletion process with confirmation requirement and structured logging.
- Updated file size validation for resource uploads from 10MB to 50MB.
- Improved loading state accessibility and user experience in chat components.
- Added event listeners for module creation to refresh the module list dynamically.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Increased the file upload size limit to 50MB for larger files.
  - Enhanced the authentication experience with improved theme consistency.
  - Updated the default chat model name to "Gemini 2.0 Flash".
  - Improved module management with real-time list refresh upon creation and safer deletion via explicit confirmation.
  - Added optimistic updates in the chat list for immediate visual feedback.
  - Improved accessibility for module search and a more accurate theme toggle state.
  - Introduced a mechanism for real-time feedback during deletion operations in confirmation dialogs.
  - Enhanced logging and error handling for module deletion and file operations.

- **Style**
  - Introduced refined visual styling for authentication components in both dark and light modes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/app/[moduleName]/chat/[id]/page.tsx b/app/[moduleName]/chat/[id]/page.tsx
index 32d01d6..f935fe1 100644
--- a/app/[moduleName]/chat/[id]/page.tsx
+++ b/app/[moduleName]/chat/[id]/page.tsx
@@ -6,6 +6,18 @@ import { ChatPageLoading } from "@/components/Main/ClientChatPage";
 import prisma from "@/lib/prisma";
 import { decodeModuleSlug } from "@/lib/utils";
 
+/**
+ * Renders the module chat page with context tailored to the user's authentication status.
+ *
+ * This asynchronous component awaits route parameters and checks the user session. For unauthenticated users,
+ * it renders a basic chat interface without module details. When the user is authenticated, it retrieves the user record,
+ * decodes the module name, and fetches the corresponding module and chat data from the database. Module date fields are converted
+ * to ISO strings for client-side compatibility. If the user or module cannot be found, a 404 response is returned.
+ *
+ * @param props - Contains a promise that resolves to an object with the module’s encoded name and chat identifier.
+ *
+ * @returns A Suspense component that wraps the client chat page configured with the appropriate module and chat data.
+ */
 export default async function ModuleChatPage(props: {
   params: Promise<{ moduleName: string; id: string }>;
 }) {
@@ -82,7 +94,6 @@ export default async function ModuleChatPage(props: {
       : null,
     resources: moduleData.resources.map((resource) => ({
       ...resource,
-      content: resource.content || "",
       createdAt: resource.createdAt.toISOString(),
       updatedAt: resource.updatedAt.toISOString(),
     })),
diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts
index 7a8a476..7e7a9b9 100644
--- a/app/api/chat/route.ts
+++ b/app/api/chat/route.ts
@@ -1,13 +1,27 @@
 import { Message, streamText } from "ai";
 import { google } from "@ai-sdk/google";
 import { searchWithPerplexity } from "@/lib/search";
+
 import { getModuleContext } from "@/lib/modules";
+
 import { currentUser } from "@clerk/nextjs/server";
 import prisma from "@/lib/prisma";
 import { formatChatTitle, generateId } from "@/lib/utils";
 import { broadcastChatCreated, broadcastMessageCreated } from "@/lib/events";
-import { SESSION_ID_KEY } from "@/lib/session";
 
+/**
+ * Handles an HTTP POST request for chat interactions.
+ *
+ * This asynchronous function processes a chat request by extracting user messages, performing an optional search on the last user message,
+ * and constructing a system prompt tailored for either a module-specific or a general study assistant context. It streams an AI-generated response
+ * and, if enabled, saves the chat history by updating the database and broadcasting relevant events.
+ *
+ * The request body must be valid JSON, including keys such as messages, chatId, moduleId, sessionId, title, and an optional saveHistory flag.
+ * On success, it returns a streaming Response with a header indicating the AI model used. Errors in any step result in an appropriate JSON error
+ * response with corresponding HTTP status codes.
+ *
+ * @param request - The incoming HTTP request containing chat interaction data.
+ */
 export async function POST(request: Request) {
   try {
     const body = await request.json();
diff --git a/app/api/modules/clear/route.ts b/app/api/modules/clear/route.ts
index efb3e96..12f11b6 100644
--- a/app/api/modules/clear/route.ts
+++ b/app/api/modules/clear/route.ts
@@ -2,10 +2,24 @@ import { NextResponse } from "next/server";
 import { auth } from "@clerk/nextjs/server";
 import prisma from "@/lib/prisma";
 
-// DELETE - Remove all modules
-export async function DELETE() {
+/**
+ * Handles HTTP DELETE requests to remove all modules associated with the authenticated user.
+ *
+ * The function authenticates the user and checks for the presence of a confirmation query parameter
+ * ("?confirmed=true"). If the user is not authenticated, it returns a 401 error. If the confirmation is
+ * not provided, it returns a 400 error. When both conditions are met, it deletes all modules tied to the user,
+ * logs the deletion event using both a simple and structured JSON format, and returns a success response with
+ * the count of deleted modules. In case of an unexpected error during deletion, it returns a 500 error.
+ *
+ * @param request - The incoming HTTP request containing the URL with potential query parameters.
+ *
+ * @returns A JSON response indicating success with the count of deleted modules or an error message on failure.
+ */
+export async function DELETE(request: Request) {
   try {
     const { userId } = await auth();
+    const { searchParams } = new URL(request.url);
+    const confirmed = searchParams.get("confirmed") === "true";
 
     // Check if user is authenticated
     if (!userId) {
@@ -15,12 +29,30 @@ export async function DELETE() {
       );
     }
 
+    // Require confirmation for bulk deletion
+    if (!confirmed) {
+      return NextResponse.json(
+        { error: "Confirmation required to delete all modules. Add ?confirmed=true to confirm." },
+        { status: 400 }
+      );
+    }
+
     // Delete all modules belonging to the user
     const deleteResult = await prisma.module.deleteMany({
       where: { userId },
     });
 
     console.log(`Deleted ${deleteResult.count} modules for user ${userId}`);
+    
+    // Use structured logging for better production debugging
+    console.log(
+      JSON.stringify({
+        event: "modules_deleted",
+        userId,
+        count: deleteResult.count,
+        timestamp: new Date().toISOString(),
+      })
+    );
 
     return NextResponse.json({
       success: true,
diff --git a/app/api/resources/upload/route.ts b/app/api/resources/upload/route.ts
index 36e2968..2436e66 100644
--- a/app/api/resources/upload/route.ts
+++ b/app/api/resources/upload/route.ts
@@ -16,7 +16,7 @@ export const config = {
  *
  * This API endpoint verifies that the user is authenticated, then extracts a file and its associated metadata (title, moduleId, type)
  * from the submitted form data. It ensures that all required fields are present, checks that the referenced module exists and is owned by the user,
- * and validates that the file does not exceed a 10MB size limit.
+ * and validates that the file does not exceed a 50MB size limit.
  *
  * Upon successful validation, the file is assigned a unique filename and uploaded to Supabase storage. A signed URL valid for 24 hours is generated
  * (and normalized to use the correct path), and a new resource record is created in the database. The endpoint responds with a JSON object containing
@@ -72,8 +72,8 @@ export async function POST(request: NextRequest) {
     const fileType = file.type;
     const fileSize = file.size;
 
-    // Validate file size (10MB limit)
-    const MAX_FILE_SIZE = 50 * 1024 * 1024; // 10MB
+    // Validate file size (50MB limit)
+    const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
     if (fileSize > MAX_FILE_SIZE) {
       return NextResponse.json(
         { error: "File size exceeds limit (50MB)" },
@@ -106,7 +106,7 @@ export async function POST(request: NextRequest) {
     // Get the signed URL - make sure to use the 'sign' path format
     const { data: signedUrlData, error: signUrlError } = await supabase.storage
       .from("resources")
-      .createSignedUrl(filePath, 60 * 60 * 24); // 7 days for initial URL
+      .createSignedUrl(filePath, 60 * 60 * 24); // 24 hours for initial URL
 
     if (signUrlError) {
       console.error("Error creating signed URL:", signUrlError);
diff --git a/app/layout.tsx b/app/layout.tsx
index 94c11b5..7d37bf4 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,19 +1,18 @@
 import React from "react";
 import type { Metadata, Viewport } from "next";
 
-import { dark } from "@clerk/themes";
 import "./globals.css";
 import localFont from "next/font/local";
 
 import ClientSidebar from "@/components/Sidebar/ClientSidebar";
 
-import { ClerkProvider } from "@clerk/nextjs";
 import { Providers as AppProviders } from "@/context/providers";
 import { SidebarProvider } from "@/context/sidebar-context";
 import { SessionProvider } from "@/context/session-context";
 
 import AnonymousDataMigration from "@/components/dialogs/AnonymousDataMigration";
 import Header from "@/components/Main/Header";
+import { ClerkProviderWithTheme } from "@/components/Clerk/ClerkProviderWithTheme";
 
 // Session initializer - client component
 import { SessionInitializer } from "@/components/Main/SessionInitializer";
@@ -34,33 +33,33 @@ export const viewport: Viewport = {
   themeColor: "#ffffff",
 };
 
-// Clerk appearance config
-const clerkAppearance = {
-  baseTheme: [dark],
-  variables: {
-    colorInputBackground: "#ffffff",
-    colorInputText: "#000000",
-  },
-  elements: {
-    modalCloseButton: "absolute right-4 top-4",
-    card: "rounded-lg shadow-md",
-    formButtonPrimary:
-      "bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded",
-    footerActionLink: "text-blue-600 hover:text-blue-800 font-semibold",
-  },
-};
-
+/**
+ * Root layout component that sets up the application's provider hierarchy.
+ *
+ * Provider Structure (from outermost to innermost):
+ * 1. AppProviders - Sets up global app configuration and utilities
+ * 2. ClerkProviderWithTheme - Handles authentication and user management with theme support
+ * 3. SessionProvider - Manages user session state and persistence
+ * 4. SidebarProvider - Controls sidebar state (open/closed) and related functionality
+ *
+ * Additional Components:
+ * - SessionInitializer: Client-side component that initializes session data
+ * - AnonymousDataMigration: Handles migration of anonymous user data after sign-in
+ *
+ * @param {Object} props - Component props
+ * @param {React.ReactNode} props.children - Child components to be wrapped by providers
+ */
 export default function RootLayout({
   children,
 }: {
   children: React.ReactNode;
 }) {
   return (
-    <ClerkProvider appearance={clerkAppearance}>
-      <html lang="en" suppressHydrationWarning>
-        <body className={`${myFont.className} overflow-hidden flex`}>
-          <SessionProvider>
-            <AppProviders>
+    <html lang="en" suppressHydrationWarning>
+      <body className={`${myFont.className} overflow-hidden flex`}>
+        <AppProviders>
+          <ClerkProviderWithTheme>
+            <SessionProvider>
               <SidebarProvider defaultOpen={true}>
                 <ClientSidebar />
                 <main className="flex-1 pl-7 pt-0.5 relative">
@@ -69,11 +68,11 @@ export default function RootLayout({
                   <AnonymousDataMigration />
                 </main>
               </SidebarProvider>
-            </AppProviders>
-            <SessionInitializer />
-          </SessionProvider>
-        </body>
-      </html>
-    </ClerkProvider>
+              <SessionInitializer />
+            </SessionProvider>
+          </ClerkProviderWithTheme>
+        </AppProviders>
+      </body>
+    </html>
   );
 }
diff --git a/app/modules/loading.tsx b/app/modules/loading.tsx
index 18490a8..18a56bb 100644
--- a/app/modules/loading.tsx
+++ b/app/modules/loading.tsx
@@ -35,8 +35,9 @@ export default function Loading() {
           <div className="relative flex-1 max-w-md">
               <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
               <Input
-                placeholder={"Search modules..."}
-                className="pl-10"
+              placeholder="Search modules..."
+              className="pl-10"
+              aria-label="Search modules"
               />
           </div>
           <ButtonSkeleton variant="outline" className="opacity-100">
diff --git a/components/Clerk/ClerkProviderWithTheme.tsx b/components/Clerk/ClerkProviderWithTheme.tsx
new file mode 100644
index 0000000..d1291f3
--- /dev/null
+++ b/components/Clerk/ClerkProviderWithTheme.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import React from "react";
+import { ClerkProvider } from "@clerk/nextjs";
+import { useTheme } from "next-themes";
+import { dark } from "@clerk/themes";
+import "./clerk-styles.css"; /**
+ * Wraps the ClerkProvider with theme customization for authentication UI.
+ *
+ * This component uses the current UI theme to configure the appearance of Clerk's
+ * authentication components. It applies a dark theme if the resolved theme is "dark",
+ * and sets custom styles for elements such as the modal close button, primary form button,
+ * and footer action link. The children are rendered within the themed ClerkProvider.
+ *
+ * @param children - React nodes to render within the ClerkProvider.
+ */
+
+export function ClerkProviderWithTheme({
+  children,
+}: {
+  children: React.ReactNode;
+}) {
+  const { resolvedTheme } = useTheme();
+  const isDarkTheme = resolvedTheme === "dark";
+
+  const clerkAppearance = {
+    // Use dark theme for dark mode, omit baseTheme for light mode (default theme)
+    baseTheme: isDarkTheme ? [dark] : undefined,
+    
+    elements: {
+      modalCloseButton: "absolute right-4 top-4",
+      formButtonPrimary:
+        "bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded",
+      footerActionLink: isDarkTheme
+        ? "text-blue-400 hover:text-blue-300 font-semibold"
+        : "text-blue-600 hover:text-blue-800 font-semibold",
+    },
+  };
+
+  return <ClerkProvider appearance={clerkAppearance}>{children}</ClerkProvider>;
+}
diff --git a/components/Clerk/clerk-styles.css b/components/Clerk/clerk-styles.css
new file mode 100644
index 0000000..597e42e
--- /dev/null
+++ b/components/Clerk/clerk-styles.css
@@ -0,0 +1,41 @@
+/* Custom styles for Clerk components */
+/* Note: !important is used throughout this file because Clerk's components have high-specificity selectors that require overriding */
+
+
+/* Dark mode styles with high specificity */
+html[class~="dark"] .cl-card,
+html[class~="dark"] .cl-userButtonPopoverCard {
+  background-color: #1a1e2e !important; /* Dark navy blue */
+}
+
+/* Dark mode input field styles to match the card */
+html[class~="dark"] .cl-formFieldInput,
+html[class~="dark"] .cl-input,
+html[class~="dark"] .cl-formFieldInput input,
+html[class~="dark"] .cl-userButtonPopoverActionButton__username input {
+  background-color: #1f1f23 !important; /* Match card background */
+  color: white !important;
+  border-color: #374151 !important;
+}
+
+/* Light mode card background */
+.cl-card,
+.cl-userButtonPopoverCard {
+  background-color: #ffffff !important;
+}
+
+/* Light mode input field styles to match the card */
+html[class~="light"] .cl-formFieldInput,
+html[class~="light"] .cl-input,
+html[class~="light"] .cl-formFieldInput input,
+html[class~="light"] .cl-userButtonPopoverActionButton__username
+input {
+  background-color: #ffffff !important; /* Match card background */
+  color: black !important;
+  border-color: #e5e7eb !important;
+}
+
+/* Password visibility button in dark mode */
+html[class~="dark"] .cl-formFieldInputShowPasswordButton {
+  color: #9ca3af !important;
+}
diff --git a/components/Main/ClientChatPage.tsx b/components/Main/ClientChatPage.tsx
index 2678d8b..c234b7e 100644
--- a/components/Main/ClientChatPage.tsx
+++ b/components/Main/ClientChatPage.tsx
@@ -56,7 +56,20 @@ export function ChatPageLoading() {
   );
 }
 
-// Main chat component that uses hooks
+/**
+ * Renders a full-screen chat interface with message history, input, and an optional module header.
+ *
+ * This component manages the chat session by handling message submission with optimistic UI updates,
+ * dynamically updating the chat model based on API responses, and adjusting the browser URL for authenticated users.
+ * It also retrieves a session ID for anonymous users and provides clipboard functionality for copying responses.
+ *
+ * @param initialModuleDetails - Optional module details used to display context-specific header information.
+ * @param chatId - Unique identifier for the chat session.
+ * @param initialMessages - Array of messages to initialize the chat conversation.
+ * @param isAuthenticated - Indicates whether the user is authenticated (defaults to true).
+ *
+ * @returns The rendered chat interface as a React element.
+ */
 export default function ClientChatPage({
   initialModuleDetails,
   chatId,
@@ -194,7 +207,7 @@ export default function ClientChatPage({
     null
   );
   // Default model name - we'll try to retrieve it dynamically if possible
-  const [modelName, setModelName] = React.useState<string>("Gemini Flash");
+  const [modelName, setModelName] = React.useState<string>("Gemini 2.0 Flash");
 
   const copyToClipboard = React.useCallback(
     (text: string, messageId: string) => {
@@ -224,7 +237,10 @@ export default function ClientChatPage({
 
           // Add the user message with its styling
           result.push(
-            <div key={`user-${message.id}`} className="flex justify-end">
+            <div
+              key={`user-${message.id || index}`}
+              className="flex justify-end"
+            >
               <div className="flex items-center gap-2 max-w-full flex-row-reverse">
                 <div className="rounded-lg px-4 py-2 bg-primary text-primary-foreground break-words">
                   <div className="whitespace-pre-wrap">{message.content}</div>
@@ -237,26 +253,33 @@ export default function ClientChatPage({
           if (hasAIResponse) {
             result.push(
               <div
-                key={`ai-${nextMessage.id}`}
+                key={`ai-${nextMessage.id || `${index}-response`}`}
                 className="mt-4 text-gray-800 dark:text-gray-200 group relative"
               >
                 <div className="prose prose-sm dark:prose-invert max-w-none break-words">
-                  <ReactMarkdown key={`md-${nextMessage.id}`}>
+                  <ReactMarkdown
+                    key={`md-${nextMessage.id || `${index}-response-md`}`}
+                  >
                     {nextMessage.content}
                   </ReactMarkdown>
                 </div>
                 <div className="mt-2 mb-4 opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-4">
                   <button
-                    key={`btn-${nextMessage.id}`}
+                    key={`btn-${nextMessage.id || `${index}-response-btn`}`}
                     onClick={() =>
-                      copyToClipboard(nextMessage.content, nextMessage.id)
+                      copyToClipboard(
+                        nextMessage.content,
+                        nextMessage.id || `${index}-response`
+                      )
                     }
                     className="text-xs px-2 py-1 rounded bg-gray-200 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-700"
                     aria-label="Copy response"
                   >
                     {copiedMessageId === nextMessage.id ? (
                       <span
-                        key={`copied-${nextMessage.id}`}
+                        key={`copied-${
+                          nextMessage.id || `${index}-response-copied`
+                        }`}
                         className="flex items-center gap-1"
                       >
                         <Check className="h-4 w-4 text-green-500" />
@@ -264,7 +287,9 @@ export default function ClientChatPage({
                       </span>
                     ) : (
                       <span
-                        key={`copy-${nextMessage.id}`}
+                        key={`copy-${
+                          nextMessage.id || `${index}-response-copy`
+                        }`}
                         className="flex items-center gap-1"
                       >
                         <Copy className="h-4 w-4" />
@@ -273,7 +298,7 @@ export default function ClientChatPage({
                     )}
                   </button>
                   <div
-                    key={`model-${nextMessage.id}`}
+                    key={`model-${nextMessage.id || `${index}-response-model`}`}
                     className="text-xs text-muted-foreground"
                   >
                     Generated with {modelName}
@@ -286,24 +311,29 @@ export default function ClientChatPage({
           // Handle case where the first message is from the assistant
           result.push(
             <div
-              key={`assistant-${message.id}`}
+              key={`assistant-${message.id || index}`}
               className="text-gray-800 dark:text-gray-200 group relative"
             >
               <div className="prose prose-sm dark:prose-invert max-w-none break-words">
-                <ReactMarkdown key={`md-assistant-${message.id}`}>
+                <ReactMarkdown key={`md-assistant-${message.id || index}`}>
                   {message.content}
                 </ReactMarkdown>
               </div>
               <div className="mt-2 mb-4 opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-4">
                 <button
-                  key={`btn-assistant-${message.id}`}
-                  onClick={() => copyToClipboard(message.content, message.id)}
+                  key={`btn-assistant-${message.id || index}`}
+                  onClick={() =>
+                    copyToClipboard(
+                      message.content,
+                      message.id || `assistant-${index}`
+                    )
+                  }
                   className="text-xs px-2 py-1 rounded bg-gray-200 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-700"
                   aria-label="Copy response"
                 >
                   {copiedMessageId === message.id ? (
                     <span
-                      key={`copied-assistant-${message.id}`}
+                      key={`copied-assistant-${message.id || index}`}
                       className="flex items-center gap-1"
                     >
                       <Check className="h-4 w-4 text-green-500" />
@@ -311,7 +341,7 @@ export default function ClientChatPage({
                     </span>
                   ) : (
                     <span
-                      key={`copy-assistant-${message.id}`}
+                      key={`copy-assistant-${message.id || index}`}
                       className="flex items-center gap-1"
                     >
                       <Copy className="h-4 w-4" />
@@ -320,7 +350,7 @@ export default function ClientChatPage({
                   )}
                 </button>
                 <div
-                  key={`model-assistant-${message.id}`}
+                  key={`model-assistant-${message.id || index}`}
                   className="text-xs text-muted-foreground"
                 >
                   Generated with {modelName}
diff --git a/components/Modules/module-form.tsx b/components/Modules/module-form.tsx
index 99fe605..e1cc559 100644
--- a/components/Modules/module-form.tsx
+++ b/components/Modules/module-form.tsx
@@ -150,6 +150,15 @@ export const ModuleForm = ({
           // Show success message
           toast.success("Module created");
 
+          // Dispatch a global event for module creation to refresh the modules list
+          if (typeof window !== "undefined") {
+            window.dispatchEvent(
+              new CustomEvent("module-created", {
+                detail: createdModule,
+              })
+            );
+          }
+
           // No need to force a page reload - SSE will handle the sidebar update
         } catch (error: unknown) {
           console.error("Error creating module:", error);
diff --git a/components/Modules/modules-content.tsx b/components/Modules/modules-content.tsx
index b1b66aa..2f8c430 100644
--- a/components/Modules/modules-content.tsx
+++ b/components/Modules/modules-content.tsx
@@ -24,6 +24,7 @@ import { useSession } from "@/context/session-context";
 import { api } from "@/lib/api";
 import { ResourceTable } from "@/components/Resource/resource-table";
 import { ResourceTableSkeleton } from "@/components/Resource/resource-table-skeleton";
+import { toast } from "sonner";
 
 // Client component for module operations to be loaded in a Suspense boundary
 import ModuleOperations from "./module-operations";
@@ -113,6 +114,36 @@ export default function ModulesPageContent({
     }
   }, [isSignedIn, isLoaded, sessionId, sessionLoading, searchQuery]);
 
+  // Listen for module creation events to refresh the module list
+  useEffect(() => {
+    const handleModuleCreated = () => {
+      // Refresh modules when a new module is created
+      if (isLoaded && !sessionLoading) {
+        api
+          .getModules(searchQuery || undefined)
+          .then((data) => {
+            const modulesList = data.modules || [];
+            setModules(modulesList);
+            setFilteredModules(modulesList);
+            // If we're on the modules tab, show a toast notification
+            if (activeTab === "modules") {
+              toast.success("New module added");
+            }
+          })
+          .catch((error) => {
+            console.error("Error refreshing modules:", error);
+          });
+      }
+    };
+
+    // Listen for the module-created event
+    window.addEventListener("module-created", handleModuleCreated);
+
+    return () => {
+      window.removeEventListener("module-created", handleModuleCreated);
+    };
+  }, [isLoaded, sessionLoading, searchQuery, activeTab]);
+
   // Filter modules based on search query whenever searchQuery changes
   useEffect(() => {
     if (activeTab === "modules" && modules.length > 0) {
diff --git a/components/Resource/resource-row.tsx b/components/Resource/resource-row.tsx
index ed3d1ff..3d3a6b7 100644
--- a/components/Resource/resource-row.tsx
+++ b/components/Resource/resource-row.tsx
@@ -680,6 +680,7 @@ const ResourceRow = memo(
               setShowDeleteAlert(false)
             )
           }
+          isDeleting={isSaving}
         />
       </>
     );
diff --git a/components/Sidebar/ClientSidebar.tsx b/components/Sidebar/ClientSidebar.tsx
index 5061a3c..1098075 100644
--- a/components/Sidebar/ClientSidebar.tsx
+++ b/components/Sidebar/ClientSidebar.tsx
@@ -43,7 +43,15 @@ function SearchParamsWrapper({
   return <>{children(searchParams)}</>;
 }
 
-// Main sidebar component that accepts searchParams as a prop
+/**
+ * Renders the sidebar for the Study Chat application.
+ *
+ * This component displays the module list and chat history while managing real-time updates
+ * through server-sent events. It fetches modules and chats based on the current authentication state,
+ * handles optimistic UI updates for new chats, and navigates to appropriate pages upon module or chat interactions.
+ *
+ * @param searchParams - The URL search parameters used to determine the current module context.
+ */
 function ClientSidebarContent({
   searchParams,
 }: {
@@ -320,7 +328,38 @@ function ClientSidebarContent({
 
               // Directly update the specific chat in the list
               setChats((prevChats) => {
-                // Find the chat to update
+                // Check if there's an optimistic chat to update
+                const optimisticChatIndex = prevChats.findIndex(
+                  (chat) => chat._isOptimistic === true
+                );
+
+                // If we found an optimistic chat, replace it with the real one
+                if (optimisticChatIndex !== -1) {
+                  console.log(
+                    `Replacing optimistic chat with real ID: ${messageData.chatId}`
+                  );
+
+                  // Get the optimistic chat to preserve any needed data
+                  const optimisticChat = prevChats[optimisticChatIndex];
+
+                  // Create updated chat with real ID and data
+                  const updatedChat = {
+                    ...optimisticChat,
+                    id: messageData.chatId,
+                    title: messageData.chatTitle,
+                    updatedAt: messageData.updatedAt,
+                    _isOptimistic: false, // No longer optimistic
+                  };
+
+                  // Create a new array with the updated chat moved to the top
+                  return [
+                    updatedChat,
+                    ...prevChats.slice(0, optimisticChatIndex),
+                    ...prevChats.slice(optimisticChatIndex + 1),
+                  ];
+                }
+
+                // Find the chat to update by its real ID
                 const chatIndex = prevChats.findIndex(
                   (chat) => chat.id === messageData.chatId
                 );
diff --git a/components/dialogs/DeleteConfirmationDialog.tsx b/components/dialogs/DeleteConfirmationDialog.tsx
index 8c5640b..dbc4820 100644
--- a/components/dialogs/DeleteConfirmationDialog.tsx
+++ b/components/dialogs/DeleteConfirmationDialog.tsx
@@ -14,21 +14,27 @@ interface DeleteConfirmationDialogProps {
   setIsOpen: (isOpen: boolean) => void;
   resourceTitle: string;
   onDelete: () => void;
+  isDeleting: boolean;
 }
 
 /**
- * A dialog for confirming resource deletion.
+ * A dialog component that prompts the user to confirm the deletion of a resource.
  *
- * @param isOpen - Whether the dialog is currently open
- * @param setIsOpen - Function to set the open state
- * @param resourceTitle - The title of the resource being deleted
- * @param onDelete - Function to call when deletion is confirmed
+ * The dialog warns that the deletion is permanent and cannot be undone. It offers options to cancel or confirm the deletion.
+ * When a deletion is in progress, the confirm button is disabled and displays "Deleting...".
+ *
+ * @param isOpen - Controls the visibility of the dialog.
+ * @param setIsOpen - Callback to update the dialog's open state.
+ * @param resourceTitle - The title of the resource to be deleted, displayed in the confirmation message.
+ * @param onDelete - Handler invoked when the deletion is confirmed.
+ * @param isDeleting - Indicates whether the deletion operation is currently in progress.
  */
 export function DeleteConfirmationDialog({
   isOpen,
   setIsOpen,
   resourceTitle,
   onDelete,
+  isDeleting,
 }: DeleteConfirmationDialogProps) {
   return (
     <AlertDialog open={isOpen} onOpenChange={setIsOpen}>
@@ -45,8 +51,9 @@ export function DeleteConfirmationDialog({
           <AlertDialogAction
             onClick={onDelete}
             className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
+            disabled={isDeleting}
           >
-            Delete
+            {isDeleting ? "Deleting..." : "Delete"}
           </AlertDialogAction>
         </AlertDialogFooter>
       </AlertDialogContent>
diff --git a/components/ui/theme-toggle.tsx b/components/ui/theme-toggle.tsx
index cfcc2f0..19a5f0b 100644
--- a/components/ui/theme-toggle.tsx
+++ b/components/ui/theme-toggle.tsx
@@ -5,8 +5,20 @@ import { useEffect, useState } from "react";
 import { Button } from "@/components/ui/button";
 import { Sun, Moon } from "lucide-react";
 
+/**
+ * Renders a button that toggles the UI theme between light and dark modes.
+ *
+ * The component uses the next-themes hook to retrieve and update the current theme. It waits until
+ * the component has mounted to render the toggle button, preventing mismatches during server-side rendering.
+ *
+ * The button displays a Sun icon when the resolved theme is dark (indicating a switch to light mode) and a
+ * Moon icon when the resolved theme is light (indicating a switch to dark mode). Its title attribute reflects
+ * the action performed on click.
+ *
+ * @returns A button element that toggles the theme.
+ */
 export default function ThemeToggle() {
-  const { theme, setTheme } = useTheme();
+  const { theme, setTheme, resolvedTheme } = useTheme();
   const [mounted, setMounted] = useState(false);
 
   // After mounting, we can access the theme
@@ -23,9 +35,13 @@ export default function ThemeToggle() {
       variant="ghost"
       size="icon"
       onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
-      title={theme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
+      title={
+        resolvedTheme === "dark"
+          ? "Switch to light mode"
+          : "Switch to dark mode"
+      }
     >
-      {theme === "dark" ? (
+      {resolvedTheme === "dark" ? (
         <Sun className="h-4 w-4 text-amber-500" />
       ) : (
         <Moon className="h-4 w-4 text-blue-400" />
diff --git a/lib/actions.ts b/lib/actions.ts
index f73b4d5..3087e6a 100644
--- a/lib/actions.ts
+++ b/lib/actions.ts
@@ -7,7 +7,6 @@ export interface ModuleResource {
   id: string;
   title: string;
   type: string;
-  content: string;
   fileUrl: string | null;
   moduleId: string;
   createdAt: string;
@@ -32,7 +31,16 @@ export interface ModuleBasic {
   lastStudied: string | null;
 }
 
-// Server action to fetch module details by ID
+/**
+ * Retrieves detailed information for a specific module along with its associated resources.
+ *
+ * This function fetches the module data for the authenticated user using the provided module ID. If found, it updates the module's
+ * last studied timestamp and returns the module details with all date fields serialized to ISO strings. If the module ID is null,
+ * the user is not authenticated, or the module does not exist, the function returns null.
+ *
+ * @param moduleId - Unique identifier for the module or null if not provided.
+ * @returns A Promise that resolves to the module details with its resources, or null if the module cannot be found.
+ */
 export async function getModuleDetails(
   moduleId: string | null
 ): Promise<ModuleWithResources | null> {
@@ -74,7 +82,6 @@ export async function getModuleDetails(
         : null,
       resources: moduleData.resources.map((resource) => ({
         ...resource,
-        content: resource.content || "",
         createdAt: resource.createdAt.toISOString(),
         updatedAt: resource.updatedAt.toISOString(),
       })),
@@ -117,7 +124,16 @@ export async function getAllModules(): Promise<ModuleBasic[]> {
   }
 }
 
-// Specifically get module by name
+/**
+ * Fetches a module by its name for the authenticated user.
+ *
+ * Performs a case-insensitive search to locate a module by the given name and retrieves its associated resources.
+ * If a module is found, its "lastStudied" timestamp is updated and all date fields are serialized to ISO strings.
+ * Returns null if the module name is null, the user is not authenticated, or no matching module is found.
+ *
+ * @param moduleName - The name of the module to retrieve.
+ * @returns The module details with associated resources, or null if not found or unauthenticated.
+ */
 export async function getModuleByName(
   moduleName: string | null
 ): Promise<ModuleWithResources | null> {
@@ -159,7 +175,6 @@ export async function getModuleByName(
         : null,
       resources: moduleData.resources.map((resource) => ({
         ...resource,
-        content: resource.content || "",
         createdAt: resource.createdAt.toISOString(),
         updatedAt: resource.updatedAt.toISOString(),
       })),
@@ -170,7 +185,18 @@ export async function getModuleByName(
   }
 }
 
-// Function to fetch resources from the database
+/**
+ * Retrieves resources associated with modules belonging to a specific user.
+ *
+ * This function first fetches all modules owned by the given user and extracts their IDs. It then
+ * retrieves resources linked to those modules, including the module's name, orders them by creation
+ * date in descending order, and maps the results to a simplified format with ISO-formatted date strings.
+ * In case of an error during retrieval or mapping, it logs the error and returns an empty array.
+ *
+ * @param userId - The identifier of the user whose module resources are being fetched.
+ * @returns A promise that resolves to an array of resource objects containing properties: id, title, type, url,
+ *          moduleId, moduleName, createdAt, and updatedAt.
+ */
 export async function getResources(userId: string) {
   try {
     // First get user's modules
@@ -206,7 +232,6 @@ export async function getResources(userId: string) {
     return resources.map((resource) => ({
       id: resource.id,
       title: resource.title,
-      description: resource.content, // Map content to description
       type: resource.type,
       url: resource.fileUrl, // Map fileUrl to url
       moduleId: resource.moduleId,
diff --git a/lib/fileTypes.ts b/lib/fileTypes.ts
index 2ef5ae6..7f6a36f 100644
--- a/lib/fileTypes.ts
+++ b/lib/fileTypes.ts
@@ -147,17 +147,22 @@ export function getFileTypeFromExtension(extension: string): string {
 }
 
 /**
- * Gets a human-readable file type description from a MIME type
+ * Returns a human-readable file type description from a MIME type.
  *
- * @param mimeType The MIME type string (e.g., "image/jpeg")
- * @returns Human-readable file type or a formatted version of the MIME type if not found
+ * This function converts the provided MIME type to lowercase and splits it into its primary type and subtype.
+ * It then checks a predefined mapping for a matching description. If a match is found, that description is returned.
+ * If no match exists, the function returns the MIME type formatted with the primary type capitalized.
+ * An empty input results in "Unknown".
+ *
+ * @param mimeType - MIME type string (e.g., "image/jpeg").
+ * @returns A human-readable file type description, a formatted MIME type string, or "Unknown" for an empty input.
  */
 export function getFileTypeFromMimeType(mimeType: string): string {
   if (!mimeType) return "Unknown";
 
   const [type, subtype] = mimeType.toLowerCase().split("/");
 
-  if (mimeTypeMap[type] && mimeTypeMap[type][subtype]) {
+  if (mimeTypeMap[type]?.[subtype]) {
     return mimeTypeMap[type][subtype];
   }
 
diff --git a/lib/modules.ts b/lib/modules.ts
index 9d39d95..0f210a2 100644
--- a/lib/modules.ts
+++ b/lib/modules.ts
@@ -2,6 +2,14 @@
 
 import prisma from "./prisma";
 
+/**
+ * Retrieves a module's context information as a formatted string.
+ *
+ * This asynchronous function fetches a module by its unique identifier from the database, including its associated resources. If found, it returns a string that includes the module's name, an optional description, and a list of resources formatted with each resource's title and type separated by two newlines. If no module is found, it returns "No module information available."
+ *
+ * @param moduleId - The unique identifier of the module.
+ * @returns A formatted string representing the module's details or a message indicating the absence of module data.
+ */
 export async function getModuleContext(moduleId: string): Promise<string> {
   const modules = await prisma.module.findUnique({
     where: { id: moduleId },
@@ -13,9 +21,7 @@ export async function getModuleContext(moduleId: string): Promise<string> {
   }
 
   const resourcesContext = modules.resources
-    .map(
-      (resource) => `${resource.title} (${resource.type}):\n${resource.content}`
-    )
+    .map((resource) => `${resource.title} ${resource.type}`)
     .join("\n\n");
 
   return `
diff --git a/lib/supabase.ts b/lib/supabase.ts
index ae56af2..3d37ed9 100644
--- a/lib/supabase.ts
+++ b/lib/supabase.ts
@@ -1,10 +1,5 @@
 import { createClient } from "@supabase/supabase-js";
 
-// Environment variables should be set in your .env.local file
-// NEXT_PUBLIC_SUPABASE_URL=your-project-url
-// NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
-// SUPABASE_SERVICE_KEY=your-service-key (only for server-side usage)
-
 // Client-side Supabase client (limited permissions via anon key)
 export const supabaseClient = createClient(
   process.env.NEXT_PUBLIC_SUPABASE_URL || "",
@@ -132,11 +127,16 @@ export async function getSignedUrl(
 /**
  * Deletes a file from a specified Supabase Storage bucket.
  *
- * This function creates a fresh admin client and attempts to remove the file at the given path from the designated bucket. If the deletion fails, it logs the error and returns null.
+ * This function creates a fresh admin client and attempts to remove the file at the given path from the designated bucket.
+ * If the file does not exist (indicated by a 404 error or "Not found" message), it returns a success response with the notFound flag set to true.
  *
  * @param bucket - The name of the storage bucket.
- * @param path - The path to the file being deleted.
- * @returns The deletion result data if successful; otherwise, null.
+ * @param path - The path to the file that should be removed.
+ * @returns An object containing:
+ *   - success: true if the deletion was successful or the file was already absent; false otherwise.
+ *   - data: the response data from the deletion operation, or null if not available.
+ *   - notFound: a boolean flag indicating if the file was not found.
+ *   - error: an error message if the deletion failed.
  */
 export async function deleteFile(bucket: string, path: string) {
   // Get a fresh client for each operation
@@ -146,13 +146,38 @@ export async function deleteFile(bucket: string, path: string) {
     const { data, error } = await client.storage.from(bucket).remove([path]);
 
     if (error) {
+      // Check if this is specifically a not-found error
+      const isNotFoundError =
+        error.message.includes("Not found") || error.message.includes("404");
+
+      if (isNotFoundError) {
+        // Return a "success" response for not-found, as the end state is the same (the file doesn't exist in storage)
+        return {
+          success: true,
+          data: null,
+          notFound: true,
+        };
+      }
+
       console.error("Error removing file:", error);
-      return null;
+      return {
+        success: false,
+        error: error.message,
+        data: null,
+      };
     }
 
-    return data;
+    return {
+      success: true,
+      data,
+      notFound: false,
+    };
   } catch (error) {
     console.error("Error deleting file:", error);
-    return null;
+    return {
+      success: false,
+      error: error instanceof Error ? error.message : "Unknown error",
+      data: null,
+    };
   }
 }


Reviews:
user: login: sharan1303
body: @coderabbitai review
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-01T01:35:47Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>components/dialogs/DeleteConfirmationDialog.tsx (1)</summary><blockquote>

`28-33`: **JSDoc should be updated to include isDeleting parameter.**

The JSDoc comment should be updated to document the new isDeleting parameter for completeness.

```diff
 /**
  * A dialog for confirming resource deletion.
  *
  * @param isOpen - Whether the dialog is currently open
  * @param setIsOpen - Function to set the open state
  * @param resourceTitle - The title of the resource being deleted
  * @param onDelete - Function to call when deletion is confirmed
+ * @param isDeleting - Whether the deletion is in progress
  */
```

</blockquote></details>
<details>
<summary>components/Clerk/clerk-styles.css (1)</summary><blockquote>

`1-39`: **Consider reducing use of !important declarations.**

While the !important declarations ensure styles are applied correctly, consider exploring more specific selectors or working with Clerk's theming API to reduce reliance on !important, which can make future style overrides challenging.

</blockquote></details>
<details>
<summary>components/Modules/modules-content.tsx (1)</summary><blockquote>

`117-145`: **Good implementation of event-driven module list refresh**

The event listener for `module-created` enables dynamic refreshing of the module list without requiring manual page reloads, which significantly improves the user experience. The implementation includes proper cleanup of event listeners and appropriate user feedback via toast notifications.



Consider adding debouncing to the event handler to prevent potential performance issues if multiple module creation events occur in rapid succession:

```diff
-  const handleModuleCreated = () => {
+  const handleModuleCreated = debounce(() => {
     // Refresh modules when a new module is created
     if (isLoaded && !sessionLoading) {
       api
         .getModules(searchQuery || undefined)
         .then((data) => {
           const modulesList = data.modules || [];
           setModules(modulesList);
           setFilteredModules(modulesList);
           // If we're on the modules tab, show a toast notification
           if (activeTab === "modules") {
             toast.success("New module added");
           }
         })
         .catch((error) => {
           console.error("Error refreshing modules:", error);
         });
     }
-  };
+  }, 300);
```

You would need to add:
```typescript
import { debounce } from 'lodash';
```

</blockquote></details>
<details>
<summary>components/Main/ClientChatPage.tsx (1)</summary><blockquote>

`227-229`: **Improved React key handling with fallback values.**

The changes add fallback values for React keys across various components, ensuring they remain unique even when message IDs are undefined. This prevents React key warnings and improves the stability of the component's rendering cycle.


While this implementation works well, consider extracting a helper function to generate these keys with fallbacks to reduce repetition:

```diff
+// Helper function to generate consistent keys with fallbacks
+const getMessageKey = (prefix: string, id: string | undefined, fallback: string | number) => 
+  `${prefix}-${id || fallback}`;

// Then use it like:
-key={`user-${message.id || index}`}
+key={getMessageKey('user', message.id, index)}
```


Also applies to: 243-243, 248-249, 255-256, 257-261, 267-270, 277-280, 288-289, 301-302, 305-305, 311-318, 323-324, 331-332, 340-341

</blockquote></details>
<details>
<summary>components/Clerk/ClerkProviderWithTheme.tsx (1)</summary><blockquote>

`1-34`: **Well-structured Clerk theme integration component.**

This new component properly integrates Clerk authentication with the application's theme system. It correctly:
- Uses the `useTheme` hook to detect the current theme
- Conditionally applies the dark theme when appropriate
- Customizes Clerk UI elements to match the application design


There's a commented-out style definition for the card element that should either be removed or uncommented:

```diff
-    //   card: "rounded-lg shadow-md",
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0cd67e8d2d5922d8f5f632a72dc73cb892a0f2f7 and d41e9ef4ec27f08fb125df752da57f7934088981.

</details>

<details>
<summary>📒 Files selected for processing (19)</summary>

* `app/[moduleName]/chat/[id]/page.tsx` (0 hunks)
* `app/api/chat/route.ts` (1 hunks)
* `app/api/modules/clear/route.ts` (2 hunks)
* `app/api/resources/upload/route.ts` (3 hunks)
* `app/layout.tsx` (3 hunks)
* `app/modules/loading.tsx` (1 hunks)
* `components/Clerk/ClerkProviderWithTheme.tsx` (1 hunks)
* `components/Clerk/clerk-styles.css` (1 hunks)
* `components/Main/ClientChatPage.tsx` (6 hunks)
* `components/Modules/module-form.tsx` (1 hunks)
* `components/Modules/modules-content.tsx` (2 hunks)
* `components/Resource/resource-row.tsx` (1 hunks)
* `components/Sidebar/ClientSidebar.tsx` (1 hunks)
* `components/dialogs/DeleteConfirmationDialog.tsx` (3 hunks)
* `components/ui/theme-toggle.tsx` (2 hunks)
* `lib/actions.ts` (0 hunks)
* `lib/fileTypes.ts` (1 hunks)
* `lib/modules.ts` (1 hunks)
* `lib/supabase.ts` (2 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (2)</summary>

* app/[moduleName]/chat/[id]/page.tsx
* lib/actions.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>app/layout.tsx (3)</summary><blockquote>

<details>
<summary>components/Clerk/ClerkProviderWithTheme.tsx (1)</summary>

* `ClerkProviderWithTheme` (9-33)

</details>
<details>
<summary>context/session-context.tsx (1)</summary>

* `SessionProvider` (25-61)

</details>
<details>
<summary>components/Main/SessionInitializer.tsx (1)</summary>

* `SessionInitializer` (7-38)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (30)</summary><blockquote>

<details>
<summary>lib/fileTypes.ts (1)</summary>

`160-160`: **Great use of optional chaining!**

This change from the previous approach (likely using `mimeTypeMap[type] && mimeTypeMap[type][subtype]`) to optional chaining (`mimeTypeMap[type]?.[subtype]`) improves code readability and safety. The optional chaining operator prevents potential "Cannot read property of undefined" errors while making the code more concise.

</details>
<details>
<summary>app/modules/loading.tsx (1)</summary>

`38-40`: **Accessibility improvement with aria-label!**

Adding the aria-label to the search input improves screen reader accessibility while the placeholder formatting change maintains consistent coding style.

</details>
<details>
<summary>app/api/resources/upload/route.ts (3)</summary>

`19-19`: **LGTM: Documentation updated to match new file size limit.**

The documentation correctly reflects the increased file size limit from 10MB to 50MB.

---

`75-76`: **LGTM: File size limit consistently updated.**

The comments and constant have been properly updated to 50MB.

---

`78-81`: **LGTM: Error message updated for consistency.**

The error message now correctly references the new 50MB limit.

</details>
<details>
<summary>components/dialogs/DeleteConfirmationDialog.tsx (2)</summary>

`12-17`: **Interface properly extended with isDeleting property.**

The DeleteConfirmationDialogProps interface has been appropriately updated with the new isDeleting boolean property.

---

`47-53`: **Good UX improvement for deletion feedback!**

Adding the disabled state and conditional button text provides helpful visual feedback during deletion operations, preventing multiple delete requests.

</details>
<details>
<summary>components/Modules/module-form.tsx (1)</summary>

`153-161`: **Great implementation of event-based refresh mechanism!**

Dispatching a custom event when a module is created enables loose coupling between components while still allowing the module list to refresh automatically.


The event includes the complete module data in the detail property, which is useful for components that need this information without making additional API calls.

</details>
<details>
<summary>components/Resource/resource-row.tsx (1)</summary>

`683-683`: **Positive enhancement for deletion UX.**

Adding the `isDeleting` prop to the DeleteConfirmationDialog improves user feedback during resource deletion operations. This change aligns with the PR's objective to enhance deletion workflows by providing visual confirmation of the ongoing process.

</details>
<details>
<summary>app/layout.tsx (2)</summary>

`15-15`: **Clean implementation of themed Clerk provider.**

The import of the specialized ClerkProviderWithTheme component appropriately separates theming concerns from the layout component.

---

`45-57`: **Improved theme integration for authentication UI.**

The refactoring of provider hierarchy with ClerkProviderWithTheme wrapping SessionProvider ensures consistent theming across authentication components. This structure maintains proper component hierarchy while adding theme support.

</details>
<details>
<summary>components/Clerk/clerk-styles.css (5)</summary>

`1-7`: **Well-structured dark mode styling for Clerk components.**

The selectors appropriately target Clerk's components with high specificity to ensure styles are applied consistently in dark mode.

---

`9-17`: **Consistent dark mode styling for form fields.**

The input field styling matches the card background, providing a cohesive dark mode experience.

---

`19-23`: **Light mode card styling defined with appropriate specificity.**

The light mode styles for cards are properly defined with !important to override Clerk's default styling.

---

`25-34`: **Comprehensive light mode input styling.**

The light mode input field styling is thorough, addressing background, text color, and border color to maintain visual consistency.

---

`36-39`: **Attention to detail with password visibility button styling.**

Good catch addressing the password visibility button color in dark mode for better contrast and accessibility.

</details>
<details>
<summary>app/api/chat/route.ts (2)</summary>

`5-5`: **Clean import of getModuleContext.**

The import statement is appropriately placed and aligns with the file's purpose of handling module-specific contexts in the chat route.

---

`4-6`: <details>
<summary>✅ Verification successful</summary>

**Verify SESSION_ID_KEY import is needed.**

The AI summary mentioned an import of SESSION_ID_KEY from @/lib/session, but it doesn't appear in the code. If this was intended, please ensure it's added; otherwise, remove the blank line.

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for SESSION_ID_KEY usage in the file
grep -n "SESSION_ID_KEY" app/api/chat/route.ts
```

Length of output: 46

---

I'll run another script to search the entire repository for any usage of `SESSION_ID_KEY` to ensure no reference was missed:

---

🏁 Script executed:

```shell
#!/bin/bash
rg -n "SESSION_ID_KEY" .
```

Length of output: 1501

---



</details>

**ACTION: Remove redundant blank line for SESSION_ID_KEY import**

The verification confirms that no instance of `SESSION_ID_KEY` is referenced in `app/api/chat/route.ts`. Since this file does not require `SESSION_ID_KEY`, please remove the placeholder/blank line rather than adding an unused import.

</details>
<details>
<summary>components/Sidebar/ClientSidebar.tsx (1)</summary>

`323-352`: **Great addition of optimistic UI updates!**

The implementation for handling optimistic chat updates improves user experience by providing immediate feedback. The code properly replaces temporary optimistic entries with real data once available, maintaining state consistency.

A few observations:
- You correctly preserve the optimistic chat's properties while updating its ID, title, and timestamp
- The chat is properly moved to the top of the list
- The `_isOptimistic` flag is set to false, preventing future replacements

</details>
<details>
<summary>lib/modules.ts (1)</summary>

`15-17`: <details>
<summary>✅ Verification successful</summary>

**Simplified resource context format by removing content**

The resource mapping function has been modified to exclude the content property, which aligns with the broader changes to remove unused content fields from resources. This change will reduce data payload sizes and improve performance.



---

🏁 Script executed:

```shell
#!/bin/bash
# Check if any code still relies on the resource content in the context string

# Look for places that might be affected by this format change
rg -A 2 "resourcesContext|getModuleContext" --type typescript
```

Length of output: 98

---

**Simplified resource mapping function update verified**

The resource mapping in **lib/modules.ts** (lines 15–17) now correctly excludes the `content` property as intended, reducing data payload sizes and aligning with the broader removal strategy. To ensure that no downstream code relies on the previous format (which included content), please run the above command and manually confirm that there aren’t unintended references to the old format (e.g., via `getModuleContext`).

---

🏁 Script executed:

```shell

#!/bin/bash
# Re-run the search limited to TypeScript files (using glob matching)
rg -A 2 "resourcesContext|getModuleContext" -g "*.ts"
```

Length of output: 1037

---



</details>

**Simplified resource context update verified**

The changes in **lib/modules.ts** (lines 15–17) now map each resource to a string composed solely of its title and type, effectively removing the unused `content` property as intended. A code search confirmed that downstream usage—such as the `getModuleContext` function imported in **app/api/chat/route.ts**—is aligned with this new simplified format, with no remaining dependencies on the removed `content` field.

No further modifications or adjustments appear necessary.

</details>
<details>
<summary>components/ui/theme-toggle.tsx (3)</summary>

`9-9`: **Good improvement using resolvedTheme for more accurate theming**

Using `resolvedTheme` instead of just `theme` is a better practice as it represents the actually applied theme rather than just the requested theme state.

---

`26-30`: **Enhanced accessibility with accurate button title**

Using `resolvedTheme` for the button title ensures that screen readers and tooltips will announce the correct state, improving accessibility.

---

`32-32`: **Fixed theme icon consistency**

Using `resolvedTheme` for the conditional rendering of the icon ensures visual consistency with the actual applied theme, avoiding potential flicker or mismatch between the icon and the theme during transitions.

</details>
<details>
<summary>components/Main/ClientChatPage.tsx (1)</summary>

`197-197`: **Updated model name to reflect new version.**

The default model name has been updated from "Gemini Flash" to "Gemini 2.0 Flash" to reflect the latest version of the model.

</details>
<details>
<summary>components/Clerk/ClerkProviderWithTheme.tsx (1)</summary>

`7-7`: <details>
<summary>✅ Verification successful</summary>

**Custom CSS file imported but not included in PR.**

The component imports a custom CSS file `./clerk-styles.css`, but this file doesn't appear to be part of the current PR. Make sure this file exists or is created as part of this change to avoid runtime errors.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the clerk-styles.css file exists in the expected location
if [ -f "components/Clerk/clerk-styles.css" ]; then
  echo "✅ The clerk-styles.css file exists"
else
  echo "❌ The clerk-styles.css file is missing"
fi
```

Length of output: 125

---



</details>

**File Verification Complete: Clerk Stylesheet Found**

The custom CSS file `clerk-styles.css` exists in the expected location (`components/Clerk/clerk-styles.css`). No further action is required regarding this file.

</details>
<details>
<summary>app/api/modules/clear/route.ts (2)</summary>

`6-10`: **Enhanced safety with confirmation requirement for bulk deletion.**

The implementation now requires explicit confirmation via a query parameter to perform bulk deletion, which is an excellent safety practice for destructive operations. This prevents accidental data loss from API calls without proper confirmation.



Also applies to: 20-26

---

`35-43`: **Added structured logging for better production debugging.**

The new structured logging with JSON format is a great addition that will improve observability in production environments. The log includes relevant details like user ID, count of deleted items, and timestamp, making it easier to monitor and troubleshoot issues.

</details>
<details>
<summary>lib/supabase.ts (3)</summary>

`130-132`: **Improved documentation to clarify 404 error handling.**

The updated documentation now clearly explains how the function handles "not found" errors, making it easier for developers to understand the expected behavior.



Also applies to: 135-136

---

`145-156`: **Enhanced error handling for file not found scenarios.**

The function now properly handles the case when a file doesn't exist, treating it as a success state with a `notFound` flag rather than an error. This makes sense since the end state (file doesn't exist) is the same as a successful deletion.

---

`159-164`: **Improved return structure with more detailed response.**

The function now returns a more informative object with `success`, `data`, and optional `notFound` flags, making it easier for calling code to handle different scenarios. This structured response improves the API's usability and clarity.



Also applies to: 166-171, 173-178

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T14:15:11Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d41e9ef4ec27f08fb125df752da57f7934088981 and 5914f49f9c32b0f0e7c801402a9545f2e40578b6.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `app/layout.tsx` (3 hunks)
* `components/Clerk/ClerkProviderWithTheme.tsx` (1 hunks)
* `components/Clerk/clerk-styles.css` (1 hunks)
* `components/dialogs/DeleteConfirmationDialog.tsx` (3 hunks)
* `components/ui/theme-toggle.tsx` (2 hunks)
* `lib/supabase.ts` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>app/layout.tsx (3)</summary><blockquote>

<details>
<summary>components/Clerk/ClerkProviderWithTheme.tsx (1)</summary>

* `ClerkProviderWithTheme` (9-32)

</details>
<details>
<summary>context/session-context.tsx (1)</summary>

* `SessionProvider` (25-61)

</details>
<details>
<summary>components/Main/SessionInitializer.tsx (1)</summary>

* `SessionInitializer` (7-38)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (15)</summary><blockquote>

<details>
<summary>components/dialogs/DeleteConfirmationDialog.tsx (4)</summary>

`17-17`: **Well-structured interface enhancement.**

Adding the `isDeleting` boolean property to the interface is a good improvement, providing clear typing for the new loading state functionality.

---

`27-27`: **Thorough documentation.**

The JSDoc comment is well-written and properly documents the new parameter, maintaining consistency with the existing documentation style.

---

`34-34`: **Clean parameter addition.**

The `isDeleting` parameter is correctly destructured at the end of the parameter list, following component structure best practices.

---

`51-51`: **Excellent UX improvements.**

The combination of disabling the button and providing a dynamic label text creates a much better user experience by:
1. Preventing duplicate delete operations
2. Providing clear visual feedback on the ongoing process
3. Avoiding confusion during the deletion operation

This is a great enhancement for accessibility and user feedback.



Also applies to: 53-53

</details>
<details>
<summary>lib/supabase.ts (2)</summary>

`130-132`: **Well-documented function behavior changes!**

The documentation clearly explains the enhanced error handling for 404 errors, which makes the function's behavior more predictable for consumers.



Also applies to: 135-135

---

`159-163`: **Improved return structure with consistent format!**

The new structured return format with `success`, `data`, and contextual flags provides much better information to callers than the previous implementation. This consistent object structure across all code paths will make error handling much cleaner for consumers.

The defensive programming in the catch block is excellent:
```typescript
error instanceof Error ? error.message : "Unknown error"
```
This ensures graceful handling even if an unexpected non-Error object is thrown.



Also applies to: 166-170, 173-177

</details>
<details>
<summary>app/layout.tsx (2)</summary>

`15-15`: **Clean import for new clerk theme provider.**

The import of `ClerkProviderWithTheme` looks good, maintaining a clean separation of concerns by moving Clerk theming logic to a dedicated component.

---

`42-60`: **Improved theme integration for Clerk.**

The refactoring to use `ClerkProviderWithTheme` instead of direct `ClerkProvider` usage is a good architectural improvement. This approach properly integrates Clerk's authentication UI with the application's theming system.

</details>
<details>
<summary>components/ui/theme-toggle.tsx (3)</summary>

`9-9`: **Good use of resolvedTheme for more accurate theme detection.**

Adding `resolvedTheme` from the `useTheme` hook is a good improvement. The `resolvedTheme` provides the actual applied theme rather than just the selected theme preference, which can differ during theme transitions.

---

`26-30`: **Improved accessibility with accurate theme information.**

Using `resolvedTheme` instead of `theme` for the button title ensures that the accessibility text accurately represents the current visual state.

---

`32-32`: **Consistent icon rendering based on actual theme.**

Switching to `resolvedTheme` for icon rendering ensures the icon accurately reflects the current visual state, which improves UX by maintaining visual consistency.

</details>
<details>
<summary>components/Clerk/ClerkProviderWithTheme.tsx (4)</summary>

`1-8`: **Good setup for the Clerk theme provider.**

The imports and CSS inclusion are properly structured. Using the client directive is necessary since this component uses browser-side hooks like `useTheme`.

---

`9-16`: **Clean implementation of theme detection.**

The component accepts children properly and correctly uses `resolvedTheme` to determine the current visual theme state.

---

`17-29`: **Well-structured appearance configuration.**

The `clerkAppearance` object is properly configured with conditional theming based on the detected theme. The code is clean and the comments are helpful.

---

`31-32`: **Proper component composition.**

The component correctly passes the appearance configuration to the ClerkProvider and forwards children, maintaining proper component composition.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:25:51Z

Review comments:
diff_hunk: @@ -146,13 +142,38 @@ export async function deleteFile(bucket: string, path: string) {
     const { data, error } = await client.storage.from(bucket).remove([path]);
 
     if (error) {
+      // Check if this is specifically a not-found error
+      const isNotFoundError =
+        error.message.includes("Not found") || error.message.includes("404");
+
+      if (isNotFoundError) {
+        // Return a "success" response for not-found, as the end state is the same (the file doesn't exist in storage)
+        return {
+          success: true,
+          data: null,
+          notFound: true,
+        };
+      }
+
path: lib/supabase.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Good approach to handling 404 errors!**

Treating "not found" errors as a success state with a flag is a smart pattern. This is more elegant than treating it as an error since the end goal (file doesn't exist) is achieved in both cases.

One small consideration:

The error detection relies on string matching which could be fragile if Supabase changes their error messages. Consider checking for HTTP status codes if the Supabase API provides them:

```diff
const isNotFoundError =
-  error.message.includes("Not found") || error.message.includes("404");
+  (error.message.includes("Not found") || error.message.includes("404") || error.status === 404);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
      // Check if this is specifically a not-found error
      const isNotFoundError =
        (error.message.includes("Not found") || error.message.includes("404") || error.status === 404);

      if (isNotFoundError) {
        // Return a "success" response for not-found, as the end state is the same (the file doesn't exist in storage)
        return {
          success: true,
          data: null,
          notFound: true,
        };
      }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-01T16:25:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+/* Custom styles for Clerk components */
+
+/* Dark mode styles with high specificity */
+html[class~="dark"] .cl-card,
+html[class~="dark"] .cl-userButtonPopoverCard {
+  background-color: #1a1e2e !important; /* Dark navy blue */
+}
+
+/* Dark mode input field styles to match the card */
+html[class~="dark"] .cl-formFieldInput,
+html[class~="dark"] .cl-input,
+html[class~="dark"] .cl-formFieldInput input,
+html[class~="dark"] .cl-userButtonPopoverActionButton__username input {
+  background-color: #1f1f23 !important; /* Match card background */
+  color: white !important;
+  border-color: #374151 !important;
+}
+
+/* Light mode card background */
+.cl-card,
+.cl-userButtonPopoverCard {
+  background-color: #ffffff !important;
+}
+
+/* Light mode input field styles to match the card */
+html[class~="light"] .cl-formFieldInput,
+html[class~="light"] .cl-input,
+html[class~="light"] .cl-formFieldInput input,
+html[class~="light"] .cl-userButtonPopoverActionButton__username
+input {
+  background-color: #ffffff !important; /* Match card background */
+  color: black !important;
+  border-color: #e5e7eb !important;
+}
+
+/* Password visibility button in dark mode */
+html[class~="dark"] .cl-formFieldInputShowPasswordButton {
+  color: #9ca3af !important;
+}
path: components/Clerk/clerk-styles.css
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Well-structured CSS for Clerk component theming.**

The CSS structure properly handles both dark and light modes with appropriate specificity to override Clerk's default styles.

While the extensive use of `!important` is generally not recommended, it's often necessary when customizing third-party components that have their own styling system. Consider adding a comment explaining why `!important` is needed here to help future developers understand the rationale.


```diff
+~ /* Note: !important is used throughout this file because Clerk's components 
+~    have high-specificity selectors that require overriding */
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
/* Custom styles for Clerk components */
/* Note: !important is used throughout this file because Clerk's components 
   have high-specificity selectors that require overriding */

/* Dark mode styles with high specificity */
html[class~="dark"] .cl-card,
html[class~="dark"] .cl-userButtonPopoverCard {
  background-color: #1a1e2e !important; /* Dark navy blue */
}

/* Dark mode input field styles to match the card */
html[class~="dark"] .cl-formFieldInput,
html[class~="dark"] .cl-input,
html[class~="dark"] .cl-formFieldInput input,
html[class~="dark"] .cl-userButtonPopoverActionButton__username input {
  background-color: #1f1f23 !important; /* Match card background */
  color: white !important;
  border-color: #374151 !important;
}

/* Light mode card background */
.cl-card,
.cl-userButtonPopoverCard {
  background-color: #ffffff !important;
}

/* Light mode input field styles to match the card */
html[class~="light"] .cl-formFieldInput,
html[class~="light"] .cl-input,
html[class~="light"] .cl-formFieldInput input,
html[class~="light"] .cl-userButtonPopoverActionButton__username
input {
  background-color: #ffffff !important; /* Match card background */
  color: black !important;
  border-color: #e5e7eb !important;
}

/* Password visibility button in dark mode */
html[class~="dark"] .cl-formFieldInputShowPasswordButton {
  color: #9ca3af !important;
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit c83e9b6
created_at: 2025-04-01T16:25:50Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-01T01:35:12Z
body: [vc]: #Icd2rXBEkNCSY6KQ9FfguYQ7HmKy/zpwli+AxMudCmE=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzdHVkeS1jaGF0LWFwcCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJzdHVkeS1jaGF0LWFwcC1naXQtcmV2aWV3LWNvZGVyYWJiaS04YjZiODItc2hhcmFuMTMwM3MtcHJvamVjdHMudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc2hhcmFuMTMwM3MtcHJvamVjdHMvc3R1ZHktY2hhdC1hcHAvQWVNU1M5NlpoUUE5NkF4NUVQbk10cWRYZVRGaiIsInByZXZpZXdVcmwiOiJzdHVkeS1jaGF0LWFwcC1naXQtcmV2aWV3LWNvZGVyYWJiaS04YjZiODItc2hhcmFuMTMwM3MtcHJvamVjdHMudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **study-chat-app** | ✅ Ready ([Inspect](https://vercel.com/sharan1303s-projects/study-chat-app/AeMSS96ZhQA96Ax5EPnMtqdXeTFj)) | [Visit Preview](https://study-chat-app-git-review-coderabbi-8b6b82-sharan1303s-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/study-chat-app-git-review-coderabbi-8b6b82-sharan1303s-projects.vercel.app?via=pr-comment-feedback-link) | Apr 1, 2025 5:13pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-01T01:35:14Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request makes multiple modifications across API endpoints, UI components, and supporting libraries. In API routes, it removes the resource content property, adjusts session and module context handling, requires confirmation for module deletion, and increases file upload limits. The UI updates include accessibility improvements, refined key handling, optimistic chat updates, and event-driven module list refreshes. Additionally, the Clerk integration is revamped with a themed provider and custom CSS, while libraries see type and error handling adjustments.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `app/[moduleName]/chat/[id]/page.tsx`<br>`lib/actions.ts` | Removed the `content` property from resource mappings and the `ModuleResource` interface, altering how resources are represented. |
| `app/api/chat/route.ts`<br>`app/api/modules/clear/route.ts`<br>`app/api/resources/upload/route.ts` | Added new imports for module context and session handling; updated the DELETE method to accept a `Request` with confirmation and JSON logging; increased file upload size limit from 10MB to 50MB. |
| `app/layout.tsx`<br>`components/Clerk/ClerkProviderWithTheme.tsx`<br>`components/Clerk/clerk-styles.css` | Replaced `ClerkProvider` with a new themed `ClerkProviderWithTheme` wrapping `SessionProvider` and introduced custom CSS for Clerk component styling. |
| `app/modules/loading.tsx`<br>`components/Main/ClientChatPage.tsx`<br>`components/Modules/module-form.tsx`<br>`components/Modules/modules-content.tsx`<br>`components/Resource/resource-row.tsx`<br>`components/dialogs/DeleteConfirmationDialog.tsx`<br>`components/ui/theme-toggle.tsx`<br>`components/Sidebar/ClientSidebar.tsx` | Updated UI elements: adjusted placeholder and aria-label for accessibility; modified default model name and key attributes for React rendering; added custom event dispatch on module creation with corresponding listener for refreshing modules; incorporated an `isDeleting` prop for deletion dialogs; and refined theme handling with the resolved theme. |
| `lib/fileTypes.ts`<br>`lib/modules.ts`<br>`lib/supabase.ts` | Simplified MIME type checks using optional chaining; updated resource formatting in module context; and enhanced file deletion error handling with a detailed return structure. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant MF as ModuleForm
    participant W as Window
    participant MC as ModulesContent
    participant API as getModules API
    MF->>W: Dispatch "module-created" event with module details
    W->>MC: Propagate "module-created" event
    MC->>API: Call getModules(searchQuery)
    API-->>MC: Return updated modules list
    MC->>UI: Update modules list & display success toast
```

```mermaid
sequenceDiagram
    participant U as User
    participant API as DELETE Endpoint
    participant DB as Database
    participant L as Logger
    U->>API: Send DELETE request with ?confirmed=true/false
    alt confirmed = true
        API->>DB: Delete modules
        DB-->>API: Return deletion count
        API->>L: Log event (user, count, timestamp)
        API-->>U: Return success response
    else not confirmed
        API-->>U: Return 400 error (confirmation required)
    end
```

## Poem
> I’m just a rabbit with code in my paws,  
> Hopping through changes without any pause.  
> New events pop up like carrots in a row,  
> With keys and themes in a bright new glow.  
> I nibble bugs and cheer with each deploy—  
> A hop, a skip, pure coding joy!  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 5914f49f9c32b0f0e7c801402a9545f2e40578b6 and c83e9b6f25be8538db132f22e8f43d29f7d6d828.

</details>

<details>
<summary>📒 Files selected for processing (14)</summary>

* `app/[moduleName]/chat/[id]/page.tsx` (1 hunks)
* `app/api/chat/route.ts` (1 hunks)
* `app/api/modules/clear/route.ts` (2 hunks)
* `app/layout.tsx` (3 hunks)
* `components/Clerk/ClerkProviderWithTheme.tsx` (1 hunks)
* `components/Clerk/clerk-styles.css` (1 hunks)
* `components/Main/ClientChatPage.tsx` (7 hunks)
* `components/Sidebar/ClientSidebar.tsx` (2 hunks)
* `components/dialogs/DeleteConfirmationDialog.tsx` (2 hunks)
* `components/ui/theme-toggle.tsx` (2 hunks)
* `lib/actions.ts` (3 hunks)
* `lib/fileTypes.ts` (1 hunks)
* `lib/modules.ts` (2 hunks)
* `lib/supabase.ts` (2 hunks)

</details>

</details>
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



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

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=sharan1303/study-chat-app&utm_content=27):

- 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: notion-workspace[bot]
created_at: 2025-04-01T01:35:40Z
body: <a href="https://www.notion.so/Review-comments-by-coderabbit-1c77a719d3278047bf3fcdbe735f5af2?pvs=8&n=github_linkback">Review comments by coderabbit</a>
author_association: NONE
user: login: sharan1303
created_at: 2025-04-01T14:08:14Z
body: @coderabbitai review
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-04-01T14:08:20Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR
user: login: sharan1303
created_at: 2025-04-01T15:16:12Z
body: @coderabbitai resume
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-04-01T15:16:19Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Reviews resumed.

</details>
author_association: CONTRIBUTOR
user: login: sharan1303
created_at: 2025-04-01T15:26:47Z
body: @coderabbitai don't worry about the issue number it is linked in Notion, review as normal
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-04-01T15:27:03Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
@sharan1303 Understood! I'll proceed with reviewing the PR normally without worrying about the issue number in Notion. Initiating the review now.

<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR
user: login: sharan1303
created_at: 2025-04-01T15:46:36Z
body: @coderabbitai review the PR now
I am not seeing your notes
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-04-01T15:46:57Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
@sharan1303 I notice you addressed me as "@coderabbit" instead of "@coderabbitai", which is why I didn't respond earlier. Let me review your PR now. The previous reviews may still be in progress or there might have been an issue. I'll initiate a fresh review of your changes.

<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-04-01T15:47:06Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
@sharan1303 I'll review your PR right away. Let me analyze the changes and provide you with detailed feedback.

<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-04-01T16:54:13Z
body: > [!NOTE]
> Generated docstrings for this pull request at https://github.com/sharan1303/study-chat-app/pull/28
author_association: CONTRIBUTOR