Repository: BibliothecaDAO/eternum
Pull request #3161: add sweep and fix for transferred sp
Author: login: RedBeardEth
Created: 2025-05-12T07:57:42Z | Merged: 2025-05-12T08:16:51Z



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

- **New Features**
  - Introduced a slider-based "sweep" selection tool in the marketplace, allowing users to batch-select multiple season passes at once.
  - Added a new, visually styled slider component for enhanced UI interaction.

- **Enhancements**
  - The marketplace UI now displays a slider control for batch selection when sweep is active, and shows selected pass images otherwise.
  - The total price display now reflects the maximum price among selected passes.
  - Improved marketplace account handling for more reliable season pass approval status.

- **Bug Fixes**
  - Token balances with zero value are now excluded from displayed results, ensuring only relevant balances appear.

- **UI Improvements**
  - Added bottom padding to the animated grid layout for better spacing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/client/apps/landing/package.json b/client/apps/landing/package.json
index 4b9ef74ceab..95f9f59ce4a 100644
--- a/client/apps/landing/package.json
+++ b/client/apps/landing/package.json
@@ -37,6 +37,7 @@
     "@radix-ui/react-popover": "^1.1.2",
     "@radix-ui/react-radio-group": "^1.3.4",
     "@radix-ui/react-separator": "^1.1.0",
+    "@radix-ui/react-slider": "^1.3.4",
     "@radix-ui/react-slot": "^1.2.0",
     "@radix-ui/react-tooltip": "^1.1.3",
     "@solana/web3.js": "catalog:",
diff --git a/client/apps/landing/src/components/modules/animated-grid.tsx b/client/apps/landing/src/components/modules/animated-grid.tsx
index 42bab315201..45f26a0062d 100644
--- a/client/apps/landing/src/components/modules/animated-grid.tsx
+++ b/client/apps/landing/src/components/modules/animated-grid.tsx
@@ -53,7 +53,7 @@ const getRowSpanClass = (span: number | undefined, breakpoint: string) => {
 export const AnimatedGrid = <T,>({ items, renderItem }: AnimatedGridProps<T>) => {
   return (
     <motion.div
-      className="grid grid-cols-1 sm:grid-cols-12 gap-8 px-2"
+      className="grid grid-cols-1 sm:grid-cols-12 gap-8 px-2 pb-4"
       initial="hidden"
       animate="visible"
       variants={{
diff --git a/client/apps/landing/src/components/ui/slider.tsx b/client/apps/landing/src/components/ui/slider.tsx
new file mode 100644
index 00000000000..40c44b79326
--- /dev/null
+++ b/client/apps/landing/src/components/ui/slider.tsx
@@ -0,0 +1,22 @@
+import { cn } from "@/lib/utils";
+import * as SliderPrimitive from "@radix-ui/react-slider";
+import * as React from "react";
+
+const Slider = React.forwardRef<
+  React.ElementRef<typeof SliderPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
+>(({ className, ...props }, ref) => (
+  <SliderPrimitive.Root
+    ref={ref}
+    className={cn("relative flex w-full touch-none select-none items-center", className)}
+    {...props}
+  >
+    <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-secondary">
+      <SliderPrimitive.Range className="absolute h-full bg-primary" />
+    </SliderPrimitive.Track>
+    <SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 cursor-pointer" />
+  </SliderPrimitive.Root>
+));
+Slider.displayName = SliderPrimitive.Root.displayName;
+
+export { Slider };
diff --git a/client/apps/landing/src/hooks/services/index.ts b/client/apps/landing/src/hooks/services/index.ts
index f78cf4f5527..b00526f4cd0 100644
--- a/client/apps/landing/src/hooks/services/index.ts
+++ b/client/apps/landing/src/hooks/services/index.ts
@@ -198,6 +198,7 @@ WITH limited_active_orders AS (
   LEFT JOIN active_orders ao
     ON ao.token_id_hex = substr(tb.token_id, instr(tb.token_id, ':') + 1)
   WHERE tb.contract_address = '{contractAddress}'
+  AND tb.balance != "0x0000000000000000000000000000000000000000000000000000000000000000"
   AND tb.account_address = '{trimmedAccountAddress}';
   `,
 };
diff --git a/client/apps/landing/src/hooks/use-marketplace.tsx b/client/apps/landing/src/hooks/use-marketplace.tsx
index 43bef132fa9..0dc428d0066 100644
--- a/client/apps/landing/src/hooks/use-marketplace.tsx
+++ b/client/apps/landing/src/hooks/use-marketplace.tsx
@@ -36,7 +36,7 @@ export const useMarketplace = () => {
     },
   } = useDojo();
 
-  const { account } = useAccount();
+  const { account, address } = useAccount();
 
   const { contract } = useContract({
     abi: SeasonPassAbi,
@@ -49,11 +49,11 @@ export const useMarketplace = () => {
   });
 
   // improve
-  const seasonPassApproved = useReadContract({
+  const { data: seasonPassApproved } = useReadContract({
     abi: SeasonPassAbi,
     address: seasonPassAddress as `0x${string}`,
     functionName: "is_approved_for_all",
-    args: [account?.address as `0x${string}`, marketplaceAddress],
+    args: [address as `0x${string}`, marketplaceAddress],
     watch: true,
   });
 
@@ -173,7 +173,7 @@ export const useMarketplace = () => {
     cancelOrder,
     editOrder,
     approveMarketplace,
-    seasonPassApproved: seasonPassApproved.data,
+    seasonPassApproved: seasonPassApproved,
     isLoading: isCreatingOrder || isAcceptingOrder || isCancellingOrder || isEditingOrder,
     isCreatingOrder,
     isAcceptingOrder,
diff --git a/client/apps/landing/src/routes/trade.lazy.tsx b/client/apps/landing/src/routes/trade.lazy.tsx
index 1fdb4da1049..7583b939eea 100644
--- a/client/apps/landing/src/routes/trade.lazy.tsx
+++ b/client/apps/landing/src/routes/trade.lazy.tsx
@@ -13,16 +13,18 @@ import {
   PaginationPrevious,
 } from "@/components/ui/pagination";
 import { ScrollHeader } from "@/components/ui/scroll-header";
+import { Slider } from "@/components/ui/slider";
 import { marketplaceAddress, seasonPassAddress } from "@/config";
 import { fetchActiveMarketOrdersTotal, fetchOpenOrdersByPrice, OpenOrderByPrice } from "@/hooks/services";
 import { useTraitFiltering } from "@/hooks/useTraitFiltering";
 import { displayAddress } from "@/lib/utils";
 import { useSelectedPassesStore } from "@/stores/selected-passes";
+import { useDebounce } from "@bibliothecadao/react";
 import { useAccount, useConnect } from "@starknet-react/core";
 import { useSuspenseQueries } from "@tanstack/react-query";
 import { createLazyFileRoute } from "@tanstack/react-router";
 import { Badge, Grid2X2, Grid3X3, Loader2 } from "lucide-react";
-import { Suspense, useCallback, useState } from "react";
+import { Suspense, useCallback, useEffect, useState } from "react";
 import { formatUnits } from "viem";
 
 export const Route = createLazyFileRoute("/season-passes")({
@@ -196,7 +198,6 @@ function SeasonPasses() {
     }
     setIsTransferOpen(true);
   }, []);
-  console.log(totals.data?.[0]);
   const totalPasses = filteredSeasonPasses.length;
   const activeOrders = totals.data?.[0]?.active_order_count ?? 0;
   const totalWeiStr = BigInt(totals.data?.[0]?.open_orders_total_wei ?? 0);
@@ -209,6 +210,49 @@ function SeasonPasses() {
 
   const [isPurchaseDialogOpen, setIsPurchaseDialogOpen] = useState(false);
   const [isHeaderScrolled, setIsHeaderScrolled] = useState(false);
+  const [sweepCount, setSweepCount] = useState(0);
+  const debouncedSweepCount = useDebounce(sweepCount, 200); // 300ms debounce
+
+  // Update effect to use debounced value
+  useEffect(() => {
+    // Only run sweep selection if sweepCount > 0
+    if (sweepCount > 0) {
+      // Get the passes we want to select
+      const passesToSelect = paginatedPasses.slice(0, sweepCount);
+
+      // Get current selected pass IDs
+      const currentSelectedIds = new Set(selectedPasses.map((pass) => pass.token_id));
+
+      // Toggle passes that need to be selected
+      passesToSelect.forEach((pass) => {
+        if (!currentSelectedIds.has(pass.token_id)) {
+          togglePass(pass);
+        }
+      });
+
+      // Toggle passes that need to be deselected
+      selectedPasses.forEach((pass) => {
+        if (!passesToSelect.some((p) => p.token_id === pass.token_id)) {
+          togglePass(pass);
+        }
+      });
+    }
+  }, [sweepCount, paginatedPasses, selectedPasses, togglePass]);
+
+  // Reset sweep count only when manually selecting passes
+  useEffect(() => {
+    // Only reset if we have selected passes and sweep count is > 0
+    // AND the selection count doesn't match the sweep count (indicating manual selection)
+    // AND we're not in the middle of a sweep operation (debouncedSweepCount matches sweepCount)
+    if (
+      selectedPasses.length > 0 &&
+      sweepCount > 0 &&
+      selectedPasses.length !== sweepCount &&
+      debouncedSweepCount === sweepCount
+    ) {
+      setSweepCount(0);
+    }
+  }, [selectedPasses.length, sweepCount, debouncedSweepCount]);
 
   return (
     <div className="flex flex-col h-full overflow-hidden">
@@ -298,46 +342,85 @@ function SeasonPasses() {
           </div>
 
           {/* Sticky Bottom Sweep Bar */}
-          {selectedPasses.length > 0 && (
-            <div className="sticky bottom-0 left-0 right-0 bg-background border-t py-2 px-4 shadow-lg">
-              <div className="container mx-auto flex items-center justify-start">
+          <div className="sticky bottom-0 left-0 right-0 bg-background border-t py-2 px-4 shadow-lg">
+            <div className="container mx-auto flex gap-4">
+              <div className="flex items-center justify-between">
                 <div className="flex items-center gap-4">
-                  <div className="flex items-center gap-2">
-                    {/* Selected Pass Images */}
-                    <div className="flex -space-x-2">
-                      {selectedPasses.slice(0, 3).map((pass) => {
-                        const metadata = pass.metadata ? JSON.parse(pass.metadata) : null;
-                        const image = metadata?.image;
-                        return (
-                          <div
-                            key={pass.token_id}
-                            className="relative w-10 h-10 rounded-full border-2 border-background overflow-hidden"
-                          >
-                            <img src={image} alt={`Pass #${pass.token_id}`} className="w-full h-full object-cover" />
-                          </div>
-                        );
-                      })}
+                  {selectedPasses.length > 0 && sweepCount === 0 && !debouncedSweepCount ? (
+                    <div className="flex items-center gap-2">
+                      {/* Selected Pass Images */}
+                      <div className="flex -space-x-2">
+                        {selectedPasses.slice(0, 3).map((pass) => {
+                          const metadata = pass.metadata ? JSON.parse(pass.metadata) : null;
+                          const image = metadata?.image;
+                          return (
+                            <div
+                              key={pass.token_id}
+                              className="relative w-10 h-10 rounded-full border-2 border-background overflow-hidden"
+                            >
+                              <img src={image} alt={`Pass #${pass.token_id}`} className="w-full h-full object-cover" />
+                            </div>
+                          );
+                        })}
+                      </div>
+                      {/* Show count if more than 3 passes */}
+                      {selectedPasses.length > 3 && (
+                        <span className="text-sm font-medium text-muted-foreground">
+                          +{selectedPasses.length - 3} more
+                        </span>
+                      )}
                     </div>
-                    {/* Show count if more than 3 passes */}
-                    {selectedPasses.length > 3 && (
-                      <span className="text-sm font-medium text-muted-foreground">
-                        +{selectedPasses.length - 3} more
-                      </span>
-                    )}
-                  </div>
+                  ) : (
+                    <div className="flex items-center gap-4 w-52 -mt-3">
+                      <div className="flex-1">
+                        <div className="flex items-center justify-between mb-2">
+                          <span className="text-xs font-medium">Sweep Selection</span>
+                          <span className="text-xs text-muted-foreground">
+                            {sweepCount} / {Math.min(activeOrders, 24)}
+                          </span>
+                        </div>
+                        <Slider
+                          value={[sweepCount]}
+                          onValueChange={([value]: number[]) => setSweepCount(value)}
+                          max={Math.min(activeOrders, 24)}
+                          step={1}
+                          className="w-full"
+                        />
+                      </div>
+                    </div>
+                  )}
                   <Button className="mr-4" onClick={() => setIsPurchaseDialogOpen(true)}>
                     Buy ({selectedPasses.length}) Passes
                   </Button>
                 </div>
                 <div className="flex items-center gap-4">
-                  <div className="font-semibold">{getTotalPrice().toFixed(4)} Lords</div>
-                  <Button variant="outline" size="sm" onClick={clearSelection}>
+                  <div className="font-semibold">
+                    {getTotalPrice()} Lords{" "}
+                    <span className="text-xs text-muted-foreground">
+                      (Max Price:{" "}
+                      {selectedPasses.length > 0
+                        ? formatUnits(
+                            BigInt(Math.max(...selectedPasses.map((pass) => Number(pass.best_price_hex)))),
+                            18,
+                          )
+                        : 0}
+                      )
+                    </span>
+                  </div>
+                  <Button
+                    variant="outline"
+                    size="sm"
+                    onClick={() => {
+                      setSweepCount(0);
+                      clearSelection();
+                    }}
+                  >
                     Clear Selection
                   </Button>
                 </div>
               </div>
             </div>
-          )}
+          </div>
 
           {/* Pagination Controls */}
           {totalPages > 1 && (
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 947ec15f139..a02e8f19523 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -670,6 +670,9 @@ importers:
       '@radix-ui/react-separator':
         specifier: ^1.1.0
         version: 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.21))(@types/react@18.3.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slider':
+        specifier: ^1.3.4
+        version: 1.3.4(@types/react-dom@18.3.7(@types/react@18.3.21))(@types/react@18.3.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       '@radix-ui/react-slot':
         specifier: ^1.2.0
         version: 1.2.2(@types/react@18.3.21)(react@18.3.1)
@@ -3980,6 +3983,19 @@ packages:
       '@types/react-dom':
         optional: true
 
+  '@radix-ui/react-slider@1.3.4':
+    resolution: {integrity: sha512-Cp6hEmQtRJFci285vkdIJ+HCDLTRDk+25VhFwa1fcubywjMUE3PynBgtN5RLudOgSCYMlT4jizCXdmV+8J7Y2w==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
   '@radix-ui/react-slot@1.0.1':
     resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==}
     peerDependencies:
@@ -14849,6 +14865,25 @@ snapshots:
       '@types/react': 18.3.21
       '@types/react-dom': 18.3.7(@types/react@18.3.21)
 
+  '@radix-ui/react-slider@1.3.4(@types/react-dom@18.3.7(@types/react@18.3.21))(@types/react@18.3.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/number': 1.1.1
+      '@radix-ui/primitive': 1.1.2
+      '@radix-ui/react-collection': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.21))(@types/react@18.3.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.21)(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(@types/react@18.3.21)(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(@types/react@18.3.21)(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.2(@types/react-dom@18.3.7(@types/react@18.3.21))(@types/react@18.3.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.21)(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.21)(react@18.3.1)
+      '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.21)(react@18.3.1)
+      '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.21)(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+    optionalDependencies:
+      '@types/react': 18.3.21
+      '@types/react-dom': 18.3.7(@types/react@18.3.21)
+
   '@radix-ui/react-slot@1.0.1(react@18.3.1)':
     dependencies:
       '@babel/runtime': 7.27.1


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

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

<details>
<summary>client/apps/landing/src/routes/trade.lazy.tsx (2)</summary><blockquote>

`22-23`: **Prefer internal hook import to avoid bundle duplication**

`useDebounce` already exists in `packages/react/src/hooks/helpers/use-debounce.ts`, yet the code imports it from the published package (`@bibliothecadao/react`).  Importing from two different locations across the monorepo will bloat the bundle (React hooks cannot be tree-shaken easily) and can even break the “multiple React” rule if the packaged build ships its own React instance.

```diff
-import { useDebounce } from "@bibliothecadao/react";
+import { useDebounce } from "@bibliothecadao/react/src/hooks/helpers/use-debounce"; // or re-export via a central internal alias
```

Aligning all call-sites to the same source keeps the bundle lean and avoids subtle hook-identity bugs.

---

`216-240`: **Guard the sweep synchronisation effect to prevent needless re-renders**

Because `selectedPasses` is part of the dependency list, every call to `togglePass` inside the effect triggers a new render → effect run → render cascade.  
Although the second run usually exits early, it is still executed on every sweep, which is unnecessary work and may become noticeable on slow devices.

A minimal early-exit makes the effect run only when a real diff exists:

```diff
  useEffect(() => {
-    if (sweepCount > 0) {
+    if (sweepCount === 0) return;                          // nothing to do
+
+    // If the current selection already matches the desired slice,
+    // skip all toggling work.
+    const desiredIds = new Set(paginatedPasses.slice(0, sweepCount).map(p => p.token_id));
+    const currentIds = new Set(selectedPasses.map(p => p.token_id));
+    if (desiredIds.size === currentIds.size &&
+        [...desiredIds].every(id => currentIds.has(id))) {
+      return;
+    }
+
+    // === diff & toggle ===
```

This removes the extra pass and the flicker it can cause on very large pages.

</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 45501960832dcd59ac79c01871d3d0488fe5ca3a and 5fae93a3b99116f4dcecd66033ecab9ac823141a.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`

</details>

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

* `client/apps/landing/package.json` (1 hunks)
* `client/apps/landing/src/components/ui/slider.tsx` (1 hunks)
* `client/apps/landing/src/hooks/services/index.ts` (1 hunks)
* `client/apps/landing/src/hooks/use-marketplace.tsx` (3 hunks)
* `client/apps/landing/src/routes/trade.lazy.tsx` (3 hunks)

</details>

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

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>client/apps/landing/src/routes/trade.lazy.tsx (3)</summary><blockquote>

<details>
<summary>packages/react/src/hooks/helpers/use-debounce.ts (1)</summary>

* `useDebounce` (8-26)

</details>
<details>
<summary>client/apps/landing/src/components/ui/slider.tsx (1)</summary>

* `Slider` (22-22)

</details>
<details>
<summary>client/apps/eternum-mobile/src/shared/ui/button.tsx (1)</summary>

* `Button` (47-47)

</details>

</blockquote></details>

</details>

</details>

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

* GitHub Check: Run pr agent on every pull request, respond to user comments

</details>

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

<details>
<summary>client/apps/landing/package.json (1)</summary>

`40-40`: **Appropriate Radix UI dependency added.**

The addition of `@radix-ui/react-slider` is consistent with the project's existing pattern of using Radix UI components for the UI. This dependency supports the new slider component implemented in `slider.tsx`.

</details>
<details>
<summary>client/apps/landing/src/hooks/services/index.ts (1)</summary>

`201-201`: **Improved token balance filtering.**

Adding this filter condition to exclude zero-value token balances is a good optimization. This ensures that only relevant tokens with actual balances are returned, which is particularly important for the new sweep feature in the marketplace UI.

</details>
<details>
<summary>client/apps/landing/src/hooks/use-marketplace.tsx (4)</summary>

`39-39`: **Better account data extraction.**

Destructuring both `account` and `address` from `useAccount()` provides more direct access to the address value, which is used in other parts of the hook.

---

`52-53`: **Improved type safety and data handling.**

Destructuring `data` as `seasonPassApproved` directly and adding `as const` to the ABI type improves type inference and simplifies data access. This is a good TypeScript practice.

---

`56-56`: **More reliable address usage.**

Replacing `account?.address` with direct use of `address` removes unnecessary optional chaining and provides a more reliable input to the contract function.

---

`176-176`: **Cleaner API exposure.**

Directly exposing the destructured `seasonPassApproved` value rather than accessing `.data` on it provides a cleaner API and is consistent with the earlier destructuring change.

</details>
<details>
<summary>client/apps/landing/src/components/ui/slider.tsx (1)</summary>

`1-22`: **Well-implemented Radix UI slider wrapper component.**

This slider component is well-structured and follows best practices:
- Uses `React.forwardRef` to properly forward refs to the underlying component
- Correctly types component props with TypeScript
- Implements appropriate styling using Tailwind CSS classes
- Sets a proper display name for debugging purposes
- Follows the composition pattern of Radix UI primitives

The implementation is clean and consistent with modern React patterns.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T08:01:31Z

Review comments:
diff_hunk: @@ -298,46 +342,85 @@ function SeasonPasses() {
           </div>
 
           {/* Sticky Bottom Sweep Bar */}
-          {selectedPasses.length > 0 && (
-            <div className="sticky bottom-0 left-0 right-0 bg-background border-t py-2 px-4 shadow-lg">
-              <div className="container mx-auto flex items-center justify-start">
+          <div className="sticky bottom-0 left-0 right-0 bg-background border-t py-2 px-4 shadow-lg">
+            <div className="container mx-auto flex gap-4">
+              <div className="flex items-center justify-between">
                 <div className="flex items-center gap-4">
-                  <div className="flex items-center gap-2">
-                    {/* Selected Pass Images */}
-                    <div className="flex -space-x-2">
-                      {selectedPasses.slice(0, 3).map((pass) => {
-                        const metadata = pass.metadata ? JSON.parse(pass.metadata) : null;
-                        const image = metadata?.image;
-                        return (
-                          <div
-                            key={pass.token_id}
-                            className="relative w-10 h-10 rounded-full border-2 border-background overflow-hidden"
-                          >
-                            <img src={image} alt={`Pass #${pass.token_id}`} className="w-full h-full object-cover" />
-                          </div>
-                        );
-                      })}
+                  {selectedPasses.length > 0 && sweepCount === 0 && !debouncedSweepCount ? (
+                    <div className="flex items-center gap-2">
+                      {/* Selected Pass Images */}
+                      <div className="flex -space-x-2">
+                        {selectedPasses.slice(0, 3).map((pass) => {
+                          const metadata = pass.metadata ? JSON.parse(pass.metadata) : null;
+                          const image = metadata?.image;
+                          return (
+                            <div
+                              key={pass.token_id}
+                              className="relative w-10 h-10 rounded-full border-2 border-background overflow-hidden"
+                            >
+                              <img src={image} alt={`Pass #${pass.token_id}`} className="w-full h-full object-cover" />
+                            </div>
+                          );
+                        })}
+                      </div>
+                      {/* Show count if more than 3 passes */}
+                      {selectedPasses.length > 3 && (
+                        <span className="text-sm font-medium text-muted-foreground">
+                          +{selectedPasses.length - 3} more
+                        </span>
+                      )}
                     </div>
-                    {/* Show count if more than 3 passes */}
-                    {selectedPasses.length > 3 && (
-                      <span className="text-sm font-medium text-muted-foreground">
-                        +{selectedPasses.length - 3} more
-                      </span>
-                    )}
-                  </div>
+                  ) : (
+                    <div className="flex items-center gap-4 w-52 -mt-3">
+                      <div className="flex-1">
+                        <div className="flex items-center justify-between mb-2">
+                          <span className="text-xs font-medium">Sweep Selection</span>
+                          <span className="text-xs text-muted-foreground">
+                            {sweepCount} / {Math.min(activeOrders, 24)}
+                          </span>
+                        </div>
+                        <Slider
+                          value={[sweepCount]}
+                          onValueChange={([value]: number[]) => setSweepCount(value)}
+                          max={Math.min(activeOrders, 24)}
+                          step={1}
+                          className="w-full"
+                        />
+                      </div>
+                    </div>
+                  )}
                   <Button className="mr-4" onClick={() => setIsPurchaseDialogOpen(true)}>
                     Buy ({selectedPasses.length}) Passes
                   </Button>
                 </div>
                 <div className="flex items-center gap-4">
-                  <div className="font-semibold">{getTotalPrice().toFixed(4)} Lords</div>
-                  <Button variant="outline" size="sm" onClick={clearSelection}>
+                  <div className="font-semibold">
+                    {getTotalPrice()} Lords{" "}
+                    <span className="text-xs text-muted-foreground">
+                      (Max Price:{" "}
+                      {selectedPasses.length > 0
+                        ? formatUnits(
+                            BigInt(Math.max(...selectedPasses.map((pass) => Number(pass.best_price_hex)))),
+                            18,
+                          )
+                        : 0}
+                      )
path: client/apps/landing/src/routes/trade.lazy.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`Max Price` calculation loses precision for large values**

`Math.max(...selectedPasses.map((pass) => Number(pass.best_price_hex)))`  
casts 256-bit wei values to `number`, overflowing `Number.MAX_SAFE_INTEGER` and silently truncating the price.

Use `BigInt` comparison instead:

```diff
- ? formatUnits(
-     BigInt(Math.max(...selectedPasses.map((pass) => Number(pass.best_price_hex)))),
-     18,
-   )
+ ? formatUnits(
+     selectedPasses
+       .map((p) => BigInt(p.best_price_hex))
+       .reduce((prev, curr) => (curr > prev ? curr : prev), 0n),
+     18,
+   )
```

This preserves full precision and avoids rounding errors that could mis-inform users about the cost of a sweep.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T08:01:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -298,46 +342,85 @@ function SeasonPasses() {
           </div>
 
           {/* Sticky Bottom Sweep Bar */}
-          {selectedPasses.length > 0 && (
-            <div className="sticky bottom-0 left-0 right-0 bg-background border-t py-2 px-4 shadow-lg">
-              <div className="container mx-auto flex items-center justify-start">
+          <div className="sticky bottom-0 left-0 right-0 bg-background border-t py-2 px-4 shadow-lg">
+            <div className="container mx-auto flex gap-4">
+              <div className="flex items-center justify-between">
                 <div className="flex items-center gap-4">
-                  <div className="flex items-center gap-2">
-                    {/* Selected Pass Images */}
-                    <div className="flex -space-x-2">
-                      {selectedPasses.slice(0, 3).map((pass) => {
-                        const metadata = pass.metadata ? JSON.parse(pass.metadata) : null;
-                        const image = metadata?.image;
-                        return (
-                          <div
-                            key={pass.token_id}
-                            className="relative w-10 h-10 rounded-full border-2 border-background overflow-hidden"
-                          >
-                            <img src={image} alt={`Pass #${pass.token_id}`} className="w-full h-full object-cover" />
-                          </div>
-                        );
-                      })}
+                  {selectedPasses.length > 0 && sweepCount === 0 && !debouncedSweepCount ? (
+                    <div className="flex items-center gap-2">
+                      {/* Selected Pass Images */}
+                      <div className="flex -space-x-2">
+                        {selectedPasses.slice(0, 3).map((pass) => {
+                          const metadata = pass.metadata ? JSON.parse(pass.metadata) : null;
+                          const image = metadata?.image;
+                          return (
+                            <div
+                              key={pass.token_id}
+                              className="relative w-10 h-10 rounded-full border-2 border-background overflow-hidden"
+                            >
+                              <img src={image} alt={`Pass #${pass.token_id}`} className="w-full h-full object-cover" />
+                            </div>
+                          );
+                        })}
+                      </div>
+                      {/* Show count if more than 3 passes */}
+                      {selectedPasses.length > 3 && (
+                        <span className="text-sm font-medium text-muted-foreground">
+                          +{selectedPasses.length - 3} more
+                        </span>
+                      )}
                     </div>
-                    {/* Show count if more than 3 passes */}
-                    {selectedPasses.length > 3 && (
-                      <span className="text-sm font-medium text-muted-foreground">
-                        +{selectedPasses.length - 3} more
-                      </span>
-                    )}
-                  </div>
+                  ) : (
+                    <div className="flex items-center gap-4 w-52 -mt-3">
+                      <div className="flex-1">
+                        <div className="flex items-center justify-between mb-2">
+                          <span className="text-xs font-medium">Sweep Selection</span>
+                          <span className="text-xs text-muted-foreground">
+                            {sweepCount} / {Math.min(activeOrders, 24)}
+                          </span>
+                        </div>
+                        <Slider
+                          value={[sweepCount]}
+                          onValueChange={([value]: number[]) => setSweepCount(value)}
+                          max={Math.min(activeOrders, 24)}
+                          step={1}
path: client/apps/landing/src/routes/trade.lazy.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Slider `max` does not match the number of visible passes**

`max` is derived from `activeOrders`, which is a global count.  
On the last page (or when filters are applied) `activeOrders` can be **greater** than the number of passes actually rendered in `paginatedPasses`.  
Setting the slider beyond that amount silently drops the selection (the reset effect at lines 242-255 fires) and confuses the user.

```diff
- max={Math.min(activeOrders, 24)}
+ /* limit to the passes the user can currently see */
+ max={Math.min(paginatedPasses.length, 24)}
```

Also update the label so the denominator matches:

```diff
- {sweepCount} / {Math.min(activeOrders, 24)}
+ {sweepCount} / {Math.min(paginatedPasses.length, 24)}
```

This guarantees the slider never advertises a value that cannot be fulfilled.



<!-- 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
                            {sweepCount} / {Math.min(paginatedPasses.length, 24)}
                          </span>
                        </div>
                        <Slider
                          value={[sweepCount]}
                          onValueChange={([value]: number[]) => setSweepCount(value)}
                          /* limit to the passes the user can currently see */
                          max={Math.min(paginatedPasses.length, 24)}
                          step={1}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T08:01:30Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-12T07:57:47Z
body: [vc]: #H47xIeuvU+KPZHDZDThdtJ+W3gAzP3stlpk7b8KSSQE=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJldGVybnVtIiwicm9vdERpcmVjdG9yeSI6bnVsbCwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImV0ZXJudW0tZ2l0LW1hcmtldHBsYWNlLWJ1bGstZnVuY3Rpb25hbGl0eS1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2xvb3QtYmlibGlvdGhlY2EvZXRlcm51bS9DTVppQlp1SHQyQ3NRWkRZR1J2ZFVzelE5TjZ2IiwicHJldmlld1VybCI6ImV0ZXJudW0tZ2l0LW1hcmtldHBsYWNlLWJ1bGstZnVuY3Rpb25hbGl0eS1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifSx7Im5hbWUiOiJldGVybnVtLWxhbmRpbmciLCJyb290RGlyZWN0b3J5IjpudWxsLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZXRlcm51bS1sYW5kaW5nLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1LWJmOTJkOS1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2xvb3QtYmlibGlvdGhlY2EvZXRlcm51bS1sYW5kaW5nL0FYTXZtd0RybWI2NWM2bjlDbmh2SzdRNUFYU3ciLCJwcmV2aWV3VXJsIjoiZXRlcm51bS1sYW5kaW5nLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1LWJmOTJkOS1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifSx7Im5hbWUiOiJldGVybnVtLWRvY3MiLCJyb290RGlyZWN0b3J5IjpudWxsLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZXRlcm51bS1kb2NzLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1bmN0LTBmNDFhMi1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2xvb3QtYmlibGlvdGhlY2EvZXRlcm51bS1kb2NzL0J0bjE2RGNxS3VtdkNBZ0I2azlreGlXdWFncHgiLCJwcmV2aWV3VXJsIjoiZXRlcm51bS1kb2NzLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1bmN0LTBmNDFhMi1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifSx7Im5hbWUiOiJldGVybnVtLW1vYmlsZSIsInJvb3REaXJlY3RvcnkiOm51bGwsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJldGVybnVtLW1vYmlsZS1naXQtbWFya2V0cGxhY2UtYnVsay1mdW4tYWEwYTgxLWxvb3QtYmlibGlvdGhlY2EudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbG9vdC1iaWJsaW90aGVjYS9ldGVybnVtLW1vYmlsZS9GVVZWQ0xyajc5M1JBb3hLaUFMaGRTRTl2S1dMIiwicHJldmlld1VybCI6ImV0ZXJudW0tbW9iaWxlLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1bi1hYTBhODEtbG9vdC1iaWJsaW90aGVjYS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIn0seyJuYW1lIjoiZXRlcm51bS1pbmZyYSIsInJvb3REaXJlY3RvcnkiOm51bGwsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJldGVybnVtLWluZnJhLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1bmMtODBmNDE2LWxvb3QtYmlibGlvdGhlY2EudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbG9vdC1iaWJsaW90aGVjYS9ldGVybnVtLWluZnJhLzdCOERmdU53anhwaUxZWnFvV1QxV1ZGWUw1VTkiLCJwcmV2aWV3VXJsIjoiZXRlcm51bS1pbmZyYS1naXQtbWFya2V0cGxhY2UtYnVsay1mdW5jLTgwZjQxNi1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **eternum** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum/CMZiBZuHt2CsQZDYGRvdUszQ9N6v)) | [Visit Preview](https://eternum-git-marketplace-bulk-functionality-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-git-marketplace-bulk-functionality-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 12, 2025 8:11am |
| **eternum-docs** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum-docs/Btn16DcqKumvCAgB6k9kxiWuagpx)) | [Visit Preview](https://eternum-docs-git-marketplace-bulk-funct-0f41a2-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-docs-git-marketplace-bulk-funct-0f41a2-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 12, 2025 8:11am |
| **eternum-infra** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum-infra/7B8DfuNwjxpiLYZqoWT1WVFYL5U9)) | [Visit Preview](https://eternum-infra-git-marketplace-bulk-func-80f416-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-infra-git-marketplace-bulk-func-80f416-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 12, 2025 8:11am |
| **eternum-landing** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum-landing/AXMvmwDrmb65c6n9CnhvK7Q5AXSw)) | [Visit Preview](https://eternum-landing-git-marketplace-bulk-fu-bf92d9-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-landing-git-marketplace-bulk-fu-bf92d9-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 12, 2025 8:11am |
| **eternum-mobile** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum-mobile/FUVVCLrj793RAoxKiALhdSE9vKWL)) | [Visit Preview](https://eternum-mobile-git-marketplace-bulk-fun-aa0a81-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-mobile-git-marketplace-bulk-fun-aa0a81-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 12, 2025 8:11am |






author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-12T07:57:49Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

A new slider UI component was added using the Radix UI library, along with its dependency. The marketplace trade UI now supports a "sweep" feature, allowing batch selection of season passes via the slider. Supporting logic and state management were introduced for this feature. Minor query and hook improvements were also made. A padding class was added to the animated grid component container.

## Changes

| File(s)                                                                                         | Change Summary                                                                                                                      |
|------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| client/apps/landing/package.json                                                               | Added `@radix-ui/react-slider` dependency.                                                                                          |
| client/apps/landing/src/components/ui/slider.tsx                                               | Introduced a new exported `Slider` React component, wrapping Radix Slider with custom styling and ref forwarding.                   |
| client/apps/landing/src/hooks/services/index.ts                                                | Updated SQL query to exclude token balances with zero value.                                                                        |
| client/apps/landing/src/hooks/use-marketplace.tsx                                              | Improved hook: destructures `account` and `address`, updates contract call arguments, and exposes `seasonPassApproved` directly.    |
| client/apps/landing/src/routes/trade.lazy.tsx                                                  | Added sweep selection logic and UI for season passes, including state, effects, slider integration, and conditional rendering.      |
| client/apps/landing/src/components/modules/animated-grid.tsx                                  | Added bottom padding class `pb-4` to the root container of `AnimatedGrid` component.                                                |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
  participant User
  participant SeasonPassesComponent
  participant Slider
  participant SelectionStore

  User->>Slider: Adjust sweep count
  Slider-->>SeasonPassesComponent: Update sweepCount state
  SeasonPassesComponent->>SelectionStore: Select first N passes (N = sweepCount)
  SeasonPassesComponent->>SeasonPassesComponent: Debounce sweepCount
  SeasonPassesComponent->>SelectionStore: Sync selection with debounced count
  User->>SeasonPassesComponent: Manually select/deselect passes
  SeasonPassesComponent->>SeasonPassesComponent: Reset sweepCount if selection differs
```

## Poem

> In the meadow of code, a slider appears,  
> Letting bunnies sweep passes with minimal tears.  
> Batch select with a glide, so swift and so neat,  
> Radix and hooks make this feature complete.  
> Now with a hop, the marketplace grows—  
> Where every new feature, a rabbit well knows! 🐇✨

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> <details>
> <summary>client/apps/landing/src/components/modules/animated-grid.tsx</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.26.0
> 
> ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
> 
> From ESLint v9.0.0, the default configuration file is now eslint.config.js.
> If you are using a .eslintrc.* file, please follow the migration guide
> to update your configuration file to the new format:
> 
> https://eslint.org/docs/latest/use/configure/migration-guide
> 
> If you still have problems after following the migration guide, please stop by
> https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> <details>
> <summary>client/apps/landing/src/hooks/use-marketplace.tsx</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.26.0
> 
> ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
> 
> From ESLint v9.0.0, the default configuration file is now eslint.config.js.
> If you are using a .eslintrc.* file, please follow the migration guide
> to update your configuration file to the new format:
> 
> https://eslint.org/docs/latest/use/configure/migration-guide
> 
> If you still have problems after following the migration guide, please stop by
> https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->


---

<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 5fae93a3b99116f4dcecd66033ecab9ac823141a and 1cc1b4a363d63759e6918751052aab94e6ef8179.

</details>

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

* `client/apps/landing/src/components/modules/animated-grid.tsx` (1 hunks)
* `client/apps/landing/src/hooks/use-marketplace.tsx` (3 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* client/apps/landing/src/components/modules/animated-grid.tsx

</details>

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

* client/apps/landing/src/hooks/use-marketplace.tsx

</details>

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


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

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

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

</details>

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

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

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

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

</details>

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

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=BibliothecaDAO/eternum&utm_content=3161):

- 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.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

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 generate sequence diagram` to generate a sequence diagram of the changes in 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: github-actions[bot]
created_at: 2025-05-12T07:59:18Z
body: Failed to generate code suggestions for PR
author_association: CONTRIBUTOR