Skip to content

Curation wave leaderboard filter n sort#2034

Merged
simo6529 merged 9 commits intomainfrom
curation-wave-leaderboard-filter-n-sort
Mar 3, 2026
Merged

Curation wave leaderboard filter n sort#2034
simo6529 merged 9 commits intomainfrom
curation-wave-leaderboard-filter-n-sort

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Mar 2, 2026

Summary by CodeRabbit

  • New Features

    • Price filtering on leaderboards (min/max + currency) and PRICE sort
    • Curation drop modal for creating leaderboard drops
    • Inline expandable "Supported URLs" panel in drop creation
  • Improvements

    • Responsive header layout and action-mode behavior refinements
    • Batch-aware drop creation with end-of-queue success notification
    • Improved URL input handling and helper text
  • Tests

    • Expanded unit/integration test coverage for leaderboard, header, modal, and drop flows

simo6529 added 5 commits March 2, 2026 12:06
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 2, 2026

📝 Walkthrough

Walkthrough

Adds price-based filtering and a curation-drop modal to leaderboard flows, updates header measurement/layout logic for responsive action rendering, forwards price filters through leaderboard components and data hooks, replaces progressive debounce with batch-error-aware drop processing, and expands tests across these areas.

Changes

Cohort / File(s) Summary
Tests — MyStream & Curation
__tests__/components/brain/my-stream/MyStreamWave.test.tsx, __tests__/components/brain/my-stream/MyStreamWaveLeaderboard.test.tsx, __tests__/components/waves/CreateDrop.test.tsx, __tests__/components/waves/CreateCurationDropContent.supportedUrls.test.tsx, __tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx
Expanded mocks (useWave, router, React Query), added curation modal lifecycle tests, local-only price filter behavior, onAllDropsAdded callback assertions, and supported-URLs UI tests.
Tests — Header / Sort / Layout
__tests__/components/waves/leaderboard/header/WaveleaderboardHeader.test.tsx, __tests__/components/waves/leaderboard/header/WaveleaderboardSort.test.tsx, __tests__/components/waves/leaderboard/header/waveLeaderboardHeaderLayout.test.ts
Large suite additions covering price filter UI, debounced input behavior, responsive header layout, action wrapping/scroll behavior, and custom sort-items handling.
Leaderboard Header & Sort
components/waves/leaderboard/header/WaveleaderboardHeader.tsx, components/waves/leaderboard/header/WaveleaderboardSort.tsx
Added price-filter UI, debounced inputs, onPriceRangeChange callbacks, and support for custom sort items plus a PRICE option in curation sort set.
Header Measurement & Layout Engine
components/waves/leaderboard/header/useLeaderboardHeaderControlMeasurements.ts, components/waves/leaderboard/header/waveLeaderboardHeaderLayout.ts
Extended measurement hook (rowWidth, actionsFullWidth, actionsIconWidth, probe refs) and added resolver to compute actionMode (full/icon), wrapActions, and control layout based on widths.
Leaderboard Components — Price Props
components/waves/leaderboard/drops/WaveLeaderboardDrops.tsx, components/waves/leaderboard/grid/WaveLeaderboardGrid.tsx, components/waves/leaderboard/gallery/WaveLeaderboardGallery.tsx
Added optional props minPrice, maxPrice, priceCurrency and forwarded them to useWaveDropsLeaderboard.
Curation Modal & Create Content
components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.tsx, components/waves/CreateCurationDropContent.tsx, components/waves/CreateCurationDropUrlInput.tsx
New WaveLeaderboardCurationDropModal component (focus trap, ESC, scroll lock); replaced modal-supported-URLs helper with inline expandable panel; UrlInput gained optional placeholder prop.
MyStream / Brain Integration
components/brain/my-stream/MyStreamWaveLeaderboard.tsx, components/brain/my-stream/MyStreamWave.tsx, components/brain/BrainMobile.tsx
Wired curation modal flow and price state into MyStream leaderboard; added key={wave.id} to ensure remount on wave change.
Data Fetching & Queue Processing
hooks/useWaveDropsLeaderboard.ts, components/waves/CreateDrop.tsx, hooks/useProgressiveDebounce.ts (deleted)
useWaveDropsLeaderboard: added PRICE sort and min/max/price_currency params; CreateDrop: removed progressive debounce, added batch-error tracking & conditional onAllDropsAdded; removed useProgressiveDebounce hook.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WaveLeaderboardHeader
    participant MyStreamWaveLeaderboard
    participant WaveLeaderboardCurationDropModal
    participant WaveDropCreate
    participant API

    User->>WaveLeaderboardHeader: Click "Create drop"
    WaveLeaderboardHeader->>MyStreamWaveLeaderboard: onCreateDrop()
    MyStreamWaveLeaderboard->>MyStreamWaveLeaderboard: check isCurationWave & canCreateDrop
    alt curation wave & eligible
        MyStreamWaveLeaderboard->>WaveLeaderboardCurationDropModal: open modal (isOpen=true)
        WaveLeaderboardCurationDropModal->>WaveLeaderboardCurationDropModal: trap focus, disable body scroll
        User->>WaveDropCreate: submit artwork
        WaveDropCreate->>API: POST create drop
        API-->>WaveDropCreate: success
        WaveDropCreate->>WaveLeaderboardCurationDropModal: onSuccess -> onClose
        WaveLeaderboardCurationDropModal->>MyStreamWaveLeaderboard: onClose()
        WaveLeaderboardCurationDropModal->>WaveLeaderboardCurationDropModal: restore body scroll, restore focus
    end
Loading
sequenceDiagram
    participant User
    participant WaveLeaderboardPriceFilters
    participant WaveLeaderboardHeader
    participant MyStreamWaveLeaderboard
    participant useWaveDropsLeaderboard
    participant API

    User->>WaveLeaderboardHeader: Toggle price filters / enter min/max
    WaveLeaderboardHeader->>WaveLeaderboardPriceFilters: show inputs
    User->>WaveLeaderboardPriceFilters: set min/max
    WaveLeaderboardPriceFilters->>WaveLeaderboardHeader: debounce -> onPriceRangeChange({min,max})
    WaveLeaderboardHeader->>MyStreamWaveLeaderboard: propagate price change
    MyStreamWaveLeaderboard->>useWaveDropsLeaderboard: pass minPrice/maxPrice/priceCurrency
    useWaveDropsLeaderboard->>API: fetch drops with min_price/max_price/price_currency
    API-->>useWaveDropsLeaderboard: return filtered results
    useWaveDropsLeaderboard-->>WaveLeaderboardHeader: update list rendering
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ragnep

Poem

🐰
Price fields whisper in the header light,
Modal opens gently for the curation night,
Focus held, body still — a drop takes flight,
Debounce laid to rest, batch errors watch the gate,
Rabbit hops, applauds the new leaderboard state.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Curation wave leaderboard filter n sort' accurately describes the main changes in the pull request, which introduce price filtering and sorting capabilities for curation wave leaderboards.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch curation-wave-leaderboard-filter-n-sort

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f90adf20c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread components/waves/leaderboard/header/useLeaderboardHeaderControlMeasurements.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
hooks/useWaveDropsLeaderboard.ts (1)

107-124: ⚠️ Potential issue | 🟡 Minor

Normalize empty currency to undefined to avoid cache key fragmentation.

A whitespace-only currency becomes "" in the query key but is omitted from request params, so semantically identical requests can hash to different cache keys.

Suggested fix
-  const normalizedPriceCurrency = useMemo(
-    () => priceCurrency?.trim() ?? undefined,
-    [priceCurrency]
-  );
+  const normalizedPriceCurrency = useMemo(() => {
+    const trimmed = priceCurrency?.trim();
+    return trimmed ? trimmed : undefined;
+  }, [priceCurrency]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useWaveDropsLeaderboard.ts` around lines 107 - 124, The
normalizedPriceCurrency currently uses priceCurrency?.trim() ?? undefined which
leaves a trimmed empty string ("") instead of undefined and can fragment the
query key; update the useMemo for normalizedPriceCurrency (in
useWaveDropsLeaderboard.ts) to explicitly convert a trimmed empty string to
undefined (e.g. compute const t = priceCurrency?.trim(); return t === "" ?
undefined : t) so the queryKey generation (where normalizedPriceCurrency is
used) treats empty/whitespace currency as undefined rather than "".
🧹 Nitpick comments (4)
__tests__/components/waves/CreateDrop.test.tsx (1)

156-158: Consider adding a test for the error path.

The test verifies onAllDropsAdded is called on success, but there's no corresponding test verifying it is not called when a batch error occurs. This would ensure the hasBatchErrorsRef logic works correctly in both directions.

💡 Suggested test case
it("does not call onAllDropsAdded when submission fails", async () => {
  const onAllDropsAdded = jest.fn();
  commonApiPostMock.mockRejectedValueOnce(new Error("Network error"));

  render(
    <AuthContext.Provider value={{ setToast: jest.fn() } as any}>
      <ReactQueryWrapperContext.Provider
        value={{ waitAndInvalidateDrops: jest.fn() } as any}
      >
        <CreateDrop
          activeDrop={null}
          onCancelReplyQuote={() => {}}
          onDropAddedToQueue={jest.fn()}
          wave={wave}
          dropId={null}
          fixedDropMode={"BOTH" as any}
          privileges={{} as any}
          onAllDropsAdded={onAllDropsAdded}
        />
      </ReactQueryWrapperContext.Provider>
    </AuthContext.Provider>
  );

  await userEvent.click(screen.getByText("submit"));

  await waitFor(() => expect(commonApiPostMock).toHaveBeenCalled());
  expect(onAllDropsAdded).not.toHaveBeenCalled();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/waves/CreateDrop.test.tsx` around lines 156 - 158, Add a
new test that asserts the error path: render CreateDrop (using the same props as
existing tests, including onAllDropsAdded jest.fn()), mock commonApiPostMock to
reject (e.g., mockRejectedValueOnce(new Error("Network error"))), trigger the
submit (userEvent.click on "submit"), wait for commonApiPostMock to have been
called, and then assert that onAllDropsAdded was not called; this verifies the
hasBatchErrorsRef logic in CreateDrop prevents calling onAllDropsAdded on
failure.
components/waves/CreateCurationDropContent.tsx (1)

390-397: Consider renaming the prop for clarity.

Passing tw-w-full through the padding prop works but is semantically misleading since tw-w-full isn't padding-related. The PrimaryButton's padding prop appears to be a general className extension point, but naming it "padding" suggests it should only contain padding values.

This is a minor naming concern that doesn't affect functionality.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/waves/CreateCurationDropContent.tsx` around lines 390 - 397, The
prop name padding on PrimaryButton is misleading because it accepts general
classes like tw-w-full; rename the prop to something semantically accurate
(e.g., className, extraClass, or containerClass) by updating the PrimaryButton
component's prop type and JSX usage (replace padding with the new prop name
inside the PrimaryButton implementation and where it is applied to the element),
then update this call site (the PrimaryButton in CreateCurationDropContent with
onClicked={onDrop} loading={submitting} disabled={!canSubmit}) to pass the new
prop instead of padding; optionally add a short compatibility alias (keep
padding as a deprecated prop that maps to the new prop with a TODO) to avoid
breaking other callers and update any prop typings and tests accordingly.
__tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx (1)

72-96: Use the real pre-test overflow baseline instead of hardcoding "scroll".

Capture the current value from document.body.style.overflow before rendering, then assert restoration to that captured value. This avoids environment-coupled assumptions.

Proposed test-hardening diff
-    const originalOverflow = "scroll";
-    document.body.style.overflow = originalOverflow;
+    const originalOverflow = document.body.style.overflow;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@__tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx`
around lines 72 - 96, The test in WaveLeaderboardCurationDropModal.test.tsx
hardcodes originalOverflow as "scroll"; capture the real baseline by reading
document.body.style.overflow into a variable (e.g., originalOverflow) before
rendering the component, then assert after closing that
document.body.style.overflow equals that captured value; update the assertions
in the test that references WaveLeaderboardCurationDropModal to use the captured
originalOverflow instead of the literal "scroll".
components/brain/my-stream/MyStreamWaveLeaderboard.tsx (1)

67-69: Decouple local filter/modal state from parent remount behavior.

Consider resetting curation-local state on wave.id change in-component. This prevents stale price filters/modal state if this component is ever reused without a remount key.

Suggested hardening
   const [isCurationDropModalOpen, setIsCurationDropModalOpen] = useState(false);
   const [minPrice, setMinPrice] = useState<number | undefined>(undefined);
   const [maxPrice, setMaxPrice] = useState<number | undefined>(undefined);
+
+  useEffect(() => {
+    setIsCurationDropModalOpen(false);
+    setMinPrice(undefined);
+    setMaxPrice(undefined);
+  }, [wave.id]);

Also applies to: 200-212

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/brain/my-stream/MyStreamWaveLeaderboard.tsx` around lines 67 - 69,
The component keeps curation/filter UI state (e.g., minPrice, maxPrice and
modal-related state) that can become stale when the parent reuses the component
for a different wave; add a useEffect that listens for changes to wave.id and
resets those local states to their initial values (call setMinPrice(undefined),
setMaxPrice(undefined) and also reset/close any modal state such as
setIsFilterModalOpen(false), setIsCurationModalOpen(false),
setSelectedCuration(undefined) or similar variables used around lines 200-212)
so the component clears filters and modals whenever wave.id changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.tsx`:
- Around line 49-103: The modal in WaveLeaderboardCurationDropModal lacks focus
trapping and initial-focus handling, so implement focus management: either swap
the native <dialog> for an accessible dialog primitive (e.g., HeadlessUI/Dialog
or react-aria/Dialog) or add a focus-trap effect in the
WaveLeaderboardCurationDropModal component that (1) sets initial focus to a
known element (use the close button with aria-label "Close modal" or the heading
with id "leaderboard-drop-art-title"), (2) traps Tab/Shift+Tab inside the panel
referenced by data-testid "curation-drop-modal-panel", (3) closes on Escape by
calling onClose, and (4) restores focus to the previously focused element when
closed; ensure the close button and WaveDropCreate remain focusable and wired to
the trap/restore logic.

In `@hooks/useWaveDropsLeaderboard.ts`:
- Around line 166-174: The code currently forwards inverted price ranges
(normalizedMinPrice > normalizedMaxPrice) to params, causing empty results;
update the logic in useWaveDropsLeaderboard (around normalizedMinPrice,
normalizedMaxPrice, params) to detect when both normalizedMinPrice and
normalizedMaxPrice are numbers and normalizedMinPrice > normalizedMaxPrice, then
either swap them (set min_price = normalizedMaxPrice.toString() and max_price =
normalizedMinPrice.toString()) or skip both filters—apply the chosen
normalization before assigning params["min_price"] and params["max_price"] so
the API never receives an inverted range.

---

Outside diff comments:
In `@hooks/useWaveDropsLeaderboard.ts`:
- Around line 107-124: The normalizedPriceCurrency currently uses
priceCurrency?.trim() ?? undefined which leaves a trimmed empty string ("")
instead of undefined and can fragment the query key; update the useMemo for
normalizedPriceCurrency (in useWaveDropsLeaderboard.ts) to explicitly convert a
trimmed empty string to undefined (e.g. compute const t = priceCurrency?.trim();
return t === "" ? undefined : t) so the queryKey generation (where
normalizedPriceCurrency is used) treats empty/whitespace currency as undefined
rather than "".

---

Nitpick comments:
In `@__tests__/components/waves/CreateDrop.test.tsx`:
- Around line 156-158: Add a new test that asserts the error path: render
CreateDrop (using the same props as existing tests, including onAllDropsAdded
jest.fn()), mock commonApiPostMock to reject (e.g., mockRejectedValueOnce(new
Error("Network error"))), trigger the submit (userEvent.click on "submit"), wait
for commonApiPostMock to have been called, and then assert that onAllDropsAdded
was not called; this verifies the hasBatchErrorsRef logic in CreateDrop prevents
calling onAllDropsAdded on failure.

In
`@__tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx`:
- Around line 72-96: The test in WaveLeaderboardCurationDropModal.test.tsx
hardcodes originalOverflow as "scroll"; capture the real baseline by reading
document.body.style.overflow into a variable (e.g., originalOverflow) before
rendering the component, then assert after closing that
document.body.style.overflow equals that captured value; update the assertions
in the test that references WaveLeaderboardCurationDropModal to use the captured
originalOverflow instead of the literal "scroll".

In `@components/brain/my-stream/MyStreamWaveLeaderboard.tsx`:
- Around line 67-69: The component keeps curation/filter UI state (e.g.,
minPrice, maxPrice and modal-related state) that can become stale when the
parent reuses the component for a different wave; add a useEffect that listens
for changes to wave.id and resets those local states to their initial values
(call setMinPrice(undefined), setMaxPrice(undefined) and also reset/close any
modal state such as setIsFilterModalOpen(false), setIsCurationModalOpen(false),
setSelectedCuration(undefined) or similar variables used around lines 200-212)
so the component clears filters and modals whenever wave.id changes.

In `@components/waves/CreateCurationDropContent.tsx`:
- Around line 390-397: The prop name padding on PrimaryButton is misleading
because it accepts general classes like tw-w-full; rename the prop to something
semantically accurate (e.g., className, extraClass, or containerClass) by
updating the PrimaryButton component's prop type and JSX usage (replace padding
with the new prop name inside the PrimaryButton implementation and where it is
applied to the element), then update this call site (the PrimaryButton in
CreateCurationDropContent with onClicked={onDrop} loading={submitting}
disabled={!canSubmit}) to pass the new prop instead of padding; optionally add a
short compatibility alias (keep padding as a deprecated prop that maps to the
new prop with a TODO) to avoid breaking other callers and update any prop
typings and tests accordingly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec13e9b and 8f90adf.

📒 Files selected for processing (25)
  • __tests__/components/brain/my-stream/MyStreamWave.test.tsx
  • __tests__/components/brain/my-stream/MyStreamWaveLeaderboard.test.tsx
  • __tests__/components/waves/CreateCurationDropContent.supportedUrls.test.tsx
  • __tests__/components/waves/CreateDrop.test.tsx
  • __tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx
  • __tests__/components/waves/leaderboard/header/WaveleaderboardHeader.test.tsx
  • __tests__/components/waves/leaderboard/header/WaveleaderboardSort.test.tsx
  • __tests__/components/waves/leaderboard/header/waveLeaderboardHeaderLayout.test.ts
  • __tests__/hooks/useWaveDropsLeaderboard.extra.test.ts
  • components/brain/BrainMobile.tsx
  • components/brain/my-stream/MyStreamWave.tsx
  • components/brain/my-stream/MyStreamWaveLeaderboard.tsx
  • components/waves/CreateCurationDropContent.tsx
  • components/waves/CreateCurationDropUrlInput.tsx
  • components/waves/CreateDrop.tsx
  • components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.tsx
  • components/waves/leaderboard/drops/WaveLeaderboardDrops.tsx
  • components/waves/leaderboard/gallery/WaveLeaderboardGallery.tsx
  • components/waves/leaderboard/grid/WaveLeaderboardGrid.tsx
  • components/waves/leaderboard/header/WaveleaderboardHeader.tsx
  • components/waves/leaderboard/header/WaveleaderboardSort.tsx
  • components/waves/leaderboard/header/useLeaderboardHeaderControlMeasurements.ts
  • components/waves/leaderboard/header/waveLeaderboardHeaderLayout.ts
  • hooks/useProgressiveDebounce.ts
  • hooks/useWaveDropsLeaderboard.ts
💤 Files with no reviewable changes (1)
  • hooks/useProgressiveDebounce.ts

Comment thread hooks/useWaveDropsLeaderboard.ts Outdated
simo6529 added 3 commits March 3, 2026 08:23
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
__tests__/hooks/useWaveDropsLeaderboard.extra.test.ts (1)

120-146: Consider also asserting canonicalized bounds in the queryKey.

This test validates swapped request params, but adding query-key assertions would prevent cache fragmentation regressions for inverted bounds.

Test addition
@@
   const call = (queryClientMock.prefetchInfiniteQuery as jest.Mock).mock
     .calls[0][0];
+  expect(call.queryKey[1].min_price).toBe(0.5);
+  expect(call.queryKey[1].max_price).toBe(2.75);
   await call.queryFn({ pageParam: null });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/hooks/useWaveDropsLeaderboard.extra.test.ts` around lines 120 -
146, The test currently asserts that inverted min/max are swapped in the request
params but doesn't assert the canonicalized bounds are used in the queryKey,
which can cause cache fragmentation; update the test (around
useWaveDropsLeaderboard renderHook and the captured prefetchInfiniteQuery call
via queryClientMock.prefetchInfiniteQuery.mock.calls[0][0]) to also assert that
call.queryKey (or the first mock argument representing the query key) contains
the canonicalized parameters (min_price: "0.5", max_price: "2.75",
price_currency: "ETH", waveId: "2", sort: WaveDropsLeaderboardSort.PRICE) so the
key is identical regardless of input order. Ensure you reference the same
captured call object (the variable named call in the test) when adding the
queryKey assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/useWaveDropsLeaderboard.ts`:
- Around line 107-110: The cache key and request params are using inconsistent
canonicalized price filters causing duplicate caches; update
useWaveDropsLeaderboard to compute canonicalized filter values once (e.g.,
replace the current normalizedPriceCurrency useMemo and compute
normalizedPriceLower/normalizedPriceUpper as trimmed/undefined or
numeric-normalized bounds) and use those same normalized symbols for both the
queryKey and the API request params; ensure empty/whitespace currency becomes
undefined (not "") and the price lower/upper are normalized in the same order
for key and params so the queryKey exactly matches the request parameters.

---

Nitpick comments:
In `@__tests__/hooks/useWaveDropsLeaderboard.extra.test.ts`:
- Around line 120-146: The test currently asserts that inverted min/max are
swapped in the request params but doesn't assert the canonicalized bounds are
used in the queryKey, which can cause cache fragmentation; update the test
(around useWaveDropsLeaderboard renderHook and the captured
prefetchInfiniteQuery call via
queryClientMock.prefetchInfiniteQuery.mock.calls[0][0]) to also assert that
call.queryKey (or the first mock argument representing the query key) contains
the canonicalized parameters (min_price: "0.5", max_price: "2.75",
price_currency: "ETH", waveId: "2", sort: WaveDropsLeaderboardSort.PRICE) so the
key is identical regardless of input order. Ensure you reference the same
captured call object (the variable named call in the test) when adding the
queryKey assertion.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f90adf and dbcc7b0.

📒 Files selected for processing (9)
  • __tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx
  • __tests__/components/waves/leaderboard/header/WaveleaderboardHeader.test.tsx
  • __tests__/components/waves/leaderboard/header/waveLeaderboardHeaderLayout.test.ts
  • __tests__/hooks/useWaveDropsLeaderboard.extra.test.ts
  • components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.tsx
  • components/waves/leaderboard/header/WaveleaderboardHeader.tsx
  • components/waves/leaderboard/header/useLeaderboardHeaderControlMeasurements.ts
  • components/waves/leaderboard/header/waveLeaderboardHeaderLayout.ts
  • hooks/useWaveDropsLeaderboard.ts

Comment thread hooks/useWaveDropsLeaderboard.ts Outdated
Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Mar 3, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
hooks/useWaveDropsLeaderboard.ts (1)

94-95: ⚠️ Potential issue | 🟠 Major

Normalize whitespace-only priceCurrency to undefined before canonicalization.

At Line 94, priceCurrency?.trim() ?? undefined preserves "" for whitespace-only input. That empty string is kept in queryKey at Line 141, but omitted from API params at Line 189, so cache key and request params diverge.

Suggested fix
-    const normalizedPriceCurrency = priceCurrency?.trim() ?? undefined;
+    const trimmedPriceCurrency = priceCurrency?.trim();
+    const normalizedPriceCurrency = trimmedPriceCurrency
+      ? trimmedPriceCurrency
+      : undefined;

Also applies to: 141-141, 189-191

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useWaveDropsLeaderboard.ts` around lines 94 - 95, Normalize
whitespace-only priceCurrency to undefined by replacing the current
normalization (normalizedPriceCurrency = priceCurrency?.trim() ?? undefined)
with logic that treats an empty trimmed string as undefined; update the value
used in the query key construction (where normalizedPriceCurrency is referenced)
so the queryKey and the API params use the same canonical value, and ensure the
same normalizedPriceCurrency variable is used when building the request params
in the function (so priceCurrency, normalizedPriceCurrency, and the queryKey
generation are consistent).
🧹 Nitpick comments (1)
components/waves/leaderboard/header/WaveleaderboardHeader.tsx (1)

702-704: Include wave.id in the price-filter key to avoid cross-wave draft carryover.

If minPrice/maxPrice are unchanged between waves, the current key can reuse the same component instance and preserve prior draft input state.

Suggested refactor
-                key={`${minPrice ?? ""}|${maxPrice ?? ""}`}
+                key={`${wave.id}|${minPrice ?? ""}|${maxPrice ?? ""}`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/waves/leaderboard/header/WaveleaderboardHeader.tsx` around lines
702 - 704, The WaveLeaderboardPriceFilters component key currently uses only
minPrice and maxPrice which can cause retained draft state across different
waves; update the key for WaveLeaderboardPriceFilters to also include wave.id
(for example key={`${wave.id}|${minPrice ?? ""}|${maxPrice ?? ""}`) so each wave
renders a fresh instance tied to its waveId, preventing cross-wave draft
carryover while keeping minPrice/maxPrice in the key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@hooks/useWaveDropsLeaderboard.ts`:
- Around line 94-95: Normalize whitespace-only priceCurrency to undefined by
replacing the current normalization (normalizedPriceCurrency =
priceCurrency?.trim() ?? undefined) with logic that treats an empty trimmed
string as undefined; update the value used in the query key construction (where
normalizedPriceCurrency is referenced) so the queryKey and the API params use
the same canonical value, and ensure the same normalizedPriceCurrency variable
is used when building the request params in the function (so priceCurrency,
normalizedPriceCurrency, and the queryKey generation are consistent).

---

Nitpick comments:
In `@components/waves/leaderboard/header/WaveleaderboardHeader.tsx`:
- Around line 702-704: The WaveLeaderboardPriceFilters component key currently
uses only minPrice and maxPrice which can cause retained draft state across
different waves; update the key for WaveLeaderboardPriceFilters to also include
wave.id (for example key={`${wave.id}|${minPrice ?? ""}|${maxPrice ?? ""}`) so
each wave renders a fresh instance tied to its waveId, preventing cross-wave
draft carryover while keeping minPrice/maxPrice in the key.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbcc7b0 and ec74d85.

📒 Files selected for processing (3)
  • __tests__/hooks/useWaveDropsLeaderboard.extra.test.ts
  • components/waves/leaderboard/header/WaveleaderboardHeader.tsx
  • hooks/useWaveDropsLeaderboard.ts

@simo6529 simo6529 merged commit 5a2837e into main Mar 3, 2026
7 checks passed
@simo6529 simo6529 deleted the curation-wave-leaderboard-filter-n-sort branch March 3, 2026 20:04
@coderabbitai coderabbitai Bot mentioned this pull request Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants