Repository: webkom/lego-webapp
Pull request #5453: Throttle theme changes instead of debouncing
Author: login: itsisak
Labels: name: enhancement
description: Pull requests that make enhancements, instead of just purely new features
name: review-needed
description: Pull requests that need review
Created: 2025-03-18T16:40:17Z | Merged: 2025-03-19T15:02:24Z

# Description

Currently the implementation of theme changing uses debounce to prevent spamming the server. This results in slow feedback (toast) for the user. Using throttling instead it sends a request instantly but does not allow further updates until after a delay. 

This way it is still kind of possible to spam the server (only 2s between each request), if we want to eliminate that too I did try out a custom function which combines the functionality of throttling and debounce (instant feedback on first click, then debounce rest of calls), its okay, but i might prefer the user feedback with just throttle.

Also moved more logic to themeUtils, both to make this PR work and its more clean imo. 

The custom event allows updates to the theme to not send a API request to the backend and only be used for syncing local state.

Also anyone know if there is a reason for why there is no `Theme` `enum` with `dark` and `light`? A lot of "magic" strings. If not I could make that. 

# Result

*Before (with debounce):*

https://github.com/user-attachments/assets/a3d775dd-8e7f-4447-a8a0-8c65d5564642

*After (with throttlling):*

https://github.com/user-attachments/assets/ae897ee5-3c74-45ce-9934-24f52a36b859


# Testing

- [ I have thoroughly spammed the theme change button probably 100000000 times] I have thoroughly tested my changes.

---

Resolves ABA-1362



Diff:
diff --git a/lego-webapp/components/Header/ToggleTheme.tsx b/lego-webapp/components/Header/ToggleTheme.tsx
index 1bf7e00f53..b8e1478813 100644
--- a/lego-webapp/components/Header/ToggleTheme.tsx
+++ b/lego-webapp/components/Header/ToggleTheme.tsx
@@ -1,11 +1,6 @@
 import { Icon } from '@webkom/lego-bricks';
 import cx from 'classnames';
-import { debounce } from 'lodash';
 import { MoonStar, Sun } from 'lucide-react';
-import { useCallback } from 'react';
-import { updateUserTheme } from '~/redux/actions/UserActions';
-import { useAppDispatch } from '~/redux/hooks';
-import { useCurrentUser, useIsLoggedIn } from '~/redux/slices/auth';
 import { applySelectedTheme, getTheme, useTheme } from '~/utils/themeUtils';
 import styles from './toggleTheme.module.css';
 import type { ReactNode, MouseEvent } from 'react';
@@ -19,29 +14,14 @@ type Props = {
 };
 
 const ToggleTheme = ({ className, children, isButton = true }: Props) => {
-  const dispatch = useAppDispatch();
-  const loggedIn = useIsLoggedIn();
-  const username = useCurrentUser()?.username;
   const icon = useIcon();
 
-  // eslint-disable-next-line react-hooks/exhaustive-deps
-  const debouncedUpdateUserTheme = useCallback(
-    debounce((username, newTheme) => {
-      dispatch(updateUserTheme(username, newTheme));
-    }, 2000),
-    [dispatch],
-  );
-
-  const handleThemeChange = useCallback(
-    (e: MouseEvent) => {
-      e.preventDefault();
-      const currentTheme = getTheme();
-      const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
-      applySelectedTheme(newTheme);
-      loggedIn && username && debouncedUpdateUserTheme(username, newTheme);
-    },
-    [loggedIn, username, debouncedUpdateUserTheme],
-  );
+  const handleThemeChange = (e: MouseEvent) => {
+    e.preventDefault();
+    const currentTheme = getTheme();
+    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
+    applySelectedTheme(newTheme);
+  };
 
   const Component = isButton ? 'button' : 'div';
   return (
diff --git a/lego-webapp/components/Header/index.tsx b/lego-webapp/components/Header/index.tsx
index d3d332493c..eed0bfb799 100644
--- a/lego-webapp/components/Header/index.tsx
+++ b/lego-webapp/components/Header/index.tsx
@@ -174,7 +174,9 @@ const Header = () => {
         ? getTheme() !== getOSTheme()
         : getTheme() !== currentUser.selectedTheme)
     ) {
-      applySelectedTheme(currentUser.selectedTheme || 'light');
+      applySelectedTheme(currentUser.selectedTheme || 'light', {
+        updateUserTheme: false,
+      });
     }
   }, [loggedIn, currentUser]);
 
diff --git a/lego-webapp/pages/lending/@lendableObjectId/request/@lendingRequestId/+Page.tsx b/lego-webapp/pages/lending/@lendableObjectId/request/@lendingRequestId/+Page.tsx
index 52de9f7e35..8f3317b744 100644
--- a/lego-webapp/pages/lending/@lendableObjectId/request/@lendingRequestId/+Page.tsx
+++ b/lego-webapp/pages/lending/@lendableObjectId/request/@lendingRequestId/+Page.tsx
@@ -47,7 +47,7 @@ type Params = {
 };
 
 const LendingRequest = () => {
-  const { query, setQueryValue } = useQuery({
+  const { query } = useQuery({
     fromAdmin: '',
   });
 
diff --git a/lego-webapp/utils/themeUtils.tsx b/lego-webapp/utils/themeUtils.tsx
index 02e79a9921..1f011e8e3e 100644
--- a/lego-webapp/utils/themeUtils.tsx
+++ b/lego-webapp/utils/themeUtils.tsx
@@ -1,16 +1,40 @@
-import { createContext, useEffect } from 'react';
+import { throttle } from 'lodash';
+import { createContext, useCallback, useEffect } from 'react';
+import { updateUserTheme } from '~/redux/actions/UserActions';
 import { useAppDispatch, useAppSelector } from '~/redux/hooks';
+import { useCurrentUser, useIsLoggedIn } from '~/redux/slices/auth';
 import { setTheme } from '~/redux/slices/theme';
 
-export const applySelectedTheme = (theme: 'light' | 'dark' | 'auto') => {
-  if (!import.meta.env.SSR) {
-    document.documentElement.setAttribute(
-      'data-theme',
-      theme === 'auto' ? getOSTheme() : theme,
-    );
-    window.dispatchEvent(new Event('themeChange'));
-    localStorage.setItem('theme', theme);
-  }
+type ThemeChangeEventDetail = {
+  updateUserTheme?: boolean;
+};
+
+type ApplySelectedThemeOptions = {
+  updateUserTheme?: boolean;
+};
+
+export const applySelectedTheme = (
+  theme: 'light' | 'dark' | 'auto',
+  options: ApplySelectedThemeOptions = {
+    updateUserTheme: true,
+  },
+) => {
+  if (import.meta.env.SSR) return;
+
+  document.documentElement.setAttribute(
+    'data-theme',
+    theme === 'auto' ? getOSTheme() : theme,
+  );
+
+  window.dispatchEvent(
+    new CustomEvent<ThemeChangeEventDetail>('themeChange', {
+      detail: {
+        updateUserTheme: options.updateUserTheme,
+      },
+    }),
+  );
+
+  localStorage.setItem('theme', theme);
 };
 
 export const getTheme = (): 'dark' | 'light' =>
@@ -23,20 +47,45 @@ export const ThemeContext = createContext<'dark' | 'light'>(getTheme());
 
 export const ThemeContextListener = () => {
   const dispatch = useAppDispatch();
+  const username = useCurrentUser()?.username;
+  const loggedIn = useIsLoggedIn();
+
+  // Throttle ensures instant feedback first time user changes theme,
+  // but also ensures user cant spam the server with requests
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  const throttledUpdateUserTheme = useCallback(
+    throttle(
+      (username, theme) => dispatch(updateUserTheme(username, theme)),
+      2000,
+    ),
+    [dispatch],
+  );
 
   useEffect(() => {
-    // Configure listener to synchronize state
-    const handleThemeChange = () => dispatch(setTheme(getTheme()));
+    const handleThemeChange = (event?: Event) => {
+      const currentTheme = getTheme();
+      dispatch(setTheme(currentTheme)); // Synchronize local state
+
+      if (
+        (event as CustomEvent<ThemeChangeEventDetail> | undefined)?.detail
+          ?.updateUserTheme &&
+        loggedIn &&
+        username
+      )
+        throttledUpdateUserTheme(username, currentTheme); // Synchronize server state
+    };
+
     handleThemeChange();
     window.addEventListener('themeChange', handleThemeChange);
 
-    // Optimistically upadte theme from localStorage
+    // Optimistically update theme from localStorage
     const cachedTheme =
       localStorage.getItem('theme') === 'dark' ? 'dark' : 'light';
-    applySelectedTheme(cachedTheme);
+    applySelectedTheme(cachedTheme, { updateUserTheme: false });
 
     return () => window.removeEventListener('themeChange', handleThemeChange);
-  }, [dispatch]);
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [dispatch, loggedIn, username]);
 
   return <></>;
 };


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

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

<details>
<summary>lego-webapp/utils/themeUtils.tsx (1)</summary><blockquote>

`89-89`: **Consider enhancing the effect dependency explanation.**

The effect dependencies now include `loggedIn` and `username`, which is correct. However, the ESLint disable comment doesn't explicitly mention why `throttledUpdateUserTheme` is excluded from the dependency array.


```diff
-  // eslint-disable-next-line react-hooks/exhaustive-deps
+  // eslint-disable-next-line react-hooks/exhaustive-deps - throttledUpdateUserTheme is intentionally excluded to prevent recreation on each render
```

</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 d097536b93bdf389c86e9d205fec4c6739bbe7d6 and bb3d99391d9423f20c642e48882ac7273eb48c92.

</details>

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

* `lego-webapp/components/Header/ToggleTheme.tsx` (1 hunks)
* `lego-webapp/components/Header/index.tsx` (1 hunks)
* `lego-webapp/utils/themeUtils.tsx` (2 hunks)

</details>

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

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

<details>
<summary>lego-webapp/components/Header/index.tsx (1)</summary><blockquote>

<details>
<summary>lego-webapp/utils/themeUtils.tsx (1) (1)</summary>

* `applySelectedTheme` (16:38)

</details>

</blockquote></details>
<details>
<summary>lego-webapp/components/Header/ToggleTheme.tsx (1)</summary><blockquote>

<details>
<summary>lego-webapp/utils/themeUtils.tsx (2) (2)</summary>

* `getTheme` (40:44)
* `applySelectedTheme` (16:38)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Analyze (javascript-typescript)

</details>

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

<details>
<summary>lego-webapp/components/Header/index.tsx (1)</summary>

`177-179`: **Improved synchronization with the backend by preventing redundant theme updates.**

The code now passes `updateUserTheme: false` when applying the user's selected theme during app initialization. This is a good optimization that prevents unnecessary API calls when the app is just restoring the user's existing theme preference.

</details>
<details>
<summary>lego-webapp/components/Header/ToggleTheme.tsx (1)</summary>

`19-24`: **Successfully simplified theme toggle with throttling approach.**

The component has been effectively refactored to remove debouncing logic in favor of a simpler, more responsive implementation. The theme change now happens immediately when the user clicks the toggle button, providing faster feedback.


This change aligns perfectly with the PR objective of throttling theme changes instead of debouncing, allowing for faster user feedback when toggling themes.

</details>
<details>
<summary>lego-webapp/utils/themeUtils.tsx (4)</summary>

`8-14`: **Good addition of type definitions for better type safety.**

The new type definitions enhance code clarity and provide better type checking:
- `ThemeChangeEventDetail` ensures type safety for the custom event
- `ApplySelectedThemeOptions` provides a clear interface for the options parameter

This is a good practice that improves maintainability.

---

`16-38`: **Well-implemented theme application with enhanced control options.**

The updated `applySelectedTheme` function now:
1. Accepts an options parameter with fine-grained control
2. Uses early return for SSR handling (cleaner code)
3. Dispatches a more informative custom event with relevant detail

This implementation provides better control over when to update the server with theme changes.

---

`53-62`: **Effective throttling implementation with good documentation.**

The throttling approach is well-implemented and documented:
- First click provides immediate feedback (better UX)
- Subsequent clicks within 2 seconds are throttled (prevents server spam)
- ESLint disable is properly commented

This is precisely what was needed to address the issue mentioned in the PR objectives.

---

`66-77`: **Improved theme change event handler with conditional server updates.**

The theme change handler now:
1. Always updates the local Redux state for UI consistency
2. Only sends updates to the server when necessary (based on event detail, login status, and username)

This implementation efficiently separates local UI state from server synchronization.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T16:42:46Z
user: login: jonasdeluna
body: Great :gem: :open_hands: 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-18T16:45:46Z
user: login: ivarnakken
body: Very nice use of throttle! Good job!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-18T16:54:00Z
user: login: Bestem0r
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T14:17:44Z
user: login: itsisak
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T14:18:47Z

Review comments:
diff_hunk: @@ -47,7 +47,7 @@ type Params = {
 };
 
 const LendingRequest = () => {
-  const { query, setQueryValue } = useQuery({
+  const { query } = useQuery({
path: lego-webapp/pages/lending/@lendableObjectId/request/@lendingRequestId/+Page.tsx
user: login: Bestem0r
body: Great! Awesome work on this file 🤩
created_at: 2025-03-19T14:16:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,7 +47,7 @@ type Params = {
 };
 
 const LendingRequest = () => {
-  const { query, setQueryValue } = useQuery({
+  const { query } = useQuery({
path: lego-webapp/pages/lending/@lendableObjectId/request/@lendingRequestId/+Page.tsx
user: login: itsisak
body: Thank you so much! I spent 2 hours working on it!
created_at: 2025-03-19T14:18:47Z
author_association: CONTRIBUTOR

PR comments:
user: login: linear[bot]
created_at: 2025-03-18T16:40:19Z
body: <p><a href="https://linear.app/abakus-webkom/issue/ABA-1362/improve-theme-change-feedback">ABA-1362 Improve theme change feedback</a></p>
author_association: NONE
user: login: vercel[bot]
created_at: 2025-03-18T16:40:23Z
body: [vc]: #8qzPzDfQGXOyR3vY/v42CxZptUNWh4FJc92lY2HYKDU=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJsZWdvLWJyaWNrcy1zdG9yeWJvb2siLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9hYmFrdXMtbnRudS9sZWdvLWJyaWNrcy1zdG9yeWJvb2svN2JoeDFYaEd0TFVUWURrTmp3VUJXdThBRkIxTiIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIiwicm9vdERpcmVjdG9yeSI6InBhY2thZ2VzL2xlZ28tYnJpY2tzIiwicHJldmlld1VybCI6ImxlZ28tYnJpY2tzLXN0b3J5Ym9vay1naXQtdGhyb3R0bGUtdGhlbWUtY2hhbmdlcy1hYmFrdXMtbnRudS52ZXJjZWwuYXBwIn1dfQ==
**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) |
| :--- | :----- | :------ | :------- | :------ |
| **lego-bricks-storybook** | ⬜️ Ignored ([Inspect](https://vercel.com/abakus-ntnu/lego-bricks-storybook/7bhx1XhGtLUTYDkNjwUBWu8AFB1N)) | [Visit Preview](https://lego-bricks-storybook-git-throttle-theme-changes-abakus-ntnu.vercel.app) |  | Mar 19, 2025 2:15pm |
</details>


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-18T16:42:43Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

The changes simplify how theme toggling is managed across the application. In the Header components, unnecessary imports, hooks, and debounced logic have been removed from the ToggleTheme component. The theme change handler now directly applies the theme without checking user login status. Additionally, the call to `applySelectedTheme` in the Header now includes an options object to disable user theme updates. In the theme utilities module, the `applySelectedTheme` function has been updated with an additional options parameter, new type definitions, simplified SSR logic, and enhanced event details for improved control of user theme synchronization.

## Changes

| Files                                                                 | Change Summary                                                                                                                                                                                                                                                                                                                   |
|----------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `lego-webapp/components/Header/ToggleTheme.tsx`<br>`lego-webapp/components/Header/index.tsx` | Removed unused debounce and user-related hooks from `ToggleTheme` and simplified its theme change logic. In `index.tsx`, updated the call to `applySelectedTheme` by adding an options object (`{ updateUserTheme: false }`) to control the update of the user's theme preference.                                                 |
| `lego-webapp/utils/themeUtils.tsx`                                   | Updated `applySelectedTheme` to accept an `options` parameter (defaulting to updating the user theme), added new type definitions (`ApplySelectedThemeOptions` and `ThemeChangeEventDetail`), simplified SSR handling by early returning if in SSR mode, and enhanced the theme change event details and listener logic.  |
| `lego-webapp/pages/lending/@lendableObjectId/request/@lendingRequestId/+Page.tsx` | Removed `setQueryValue` from destructuring assignment in `LendingRequest`, simplifying state management and reducing re-renders related to query changes.                                                                                                                                                                        |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant U as User
    participant TT as ToggleTheme
    participant H as Header
    participant TUtil as ThemeUtils
    participant TCL as ThemeContextListener
    participant S as Server

    U->>TT: Click toggle button
    TT->>TT: Execute handleThemeChange(event)
    TT->>TUtil: Call applySelectedTheme(newTheme, options)
    TUtil->>TCL: Dispatch CustomEvent with {updateUserTheme flag}
    TCL->>TCL: Evaluate event detail and logged-in status
    TCL->>S: Update user theme (if flagged and applicable)
```

</details>

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡🧪 Multi-step agentic review comment chat (experimental)</summary>
> 
> - We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
> 						- To enable this feature, set early_access to true under in the settings.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent 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 bb3d99391d9423f20c642e48882ac7273eb48c92 and b2baf93866c609df1fe875305d0edaefece1541d.

</details>

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

* `lego-webapp/pages/lending/@lendableObjectId/request/@lendingRequestId/+Page.tsx` (1 hunks)
* `lego-webapp/utils/themeUtils.tsx` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* lego-webapp/utils/themeUtils.tsx

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Analyze (javascript-typescript)

</details>

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

<details>
<summary>lego-webapp/pages/lending/@lendableObjectId/request/@lendingRequestId/+Page.tsx (1)</summary>

`50-50`: **Good code cleanup removing the unused variable.**

This change simplifies the component by eliminating the unused `setQueryValue` function from the destructured result of the `useQuery` hook. The component only needs read access to query parameters and doesn't modify them, so this cleanup improves code clarity.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


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

<!-- internal state end -->
<!-- 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=webkom/lego-webapp&utm_content=5453):

- 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 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: coderabbitai[bot]
created_at: 2025-03-18T16:48:19Z
body: > [!CAUTION]
> No docstrings were generated.
author_association: CONTRIBUTOR