Skip to content

Curation sales#2100

Merged
simo6529 merged 13 commits intomainfrom
curation-sales
Mar 12, 2026
Merged

Curation sales#2100
simo6529 merged 13 commits intomainfrom
curation-sales

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Mar 11, 2026

Summary by CodeRabbit

  • New Features
    • Added a Sales tab for curation/rank waves that shows marketplace previews in a responsive grid with pagination, loading, empty and error states.
  • UI
    • Sales tab appears in mobile and desktop tab bars (inserted after Leaderboard) and respects active-state styling.
  • Layout
    • New sales view styling ensures consistent spacing and responsive layout across devices.

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 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eaff1bbf-2d05-44ba-a0b8-c8a70af6fa3c

📥 Commits

Reviewing files that changed from the base of the PR and between 87a2eac and 9645618.

📒 Files selected for processing (4)
  • __tests__/components/brain/my-stream/MyStreamWaveSales.test.tsx
  • __tests__/hooks/waves/useWaveDecisions.test.ts
  • components/brain/my-stream/MyStreamWaveSales.tsx
  • hooks/waves/useWaveDecisions.ts

📝 Walkthrough

Walkthrough

Adds a SALES tab and Sales view to the Brain UI for curation/rank waves, implements MyStreamWaveSales component with paginated sales fetching, introduces useDebouncedQueryRefetch and useWaveDrops hooks, updates layout/context for sales styling, and expands tests to cover the new flow.

Changes

Cohort / File(s) Summary
Tab & Types
types/waves.types.ts
Added SALES enum member to wave tab types.
Core UI Integration
components/brain/BrainMobile.tsx, components/brain/mobile/BrainMobileTabs.tsx, components/brain/ContentTabContext.tsx
Added BrainView.SALES, wired Sales tab button and view rendering, and include SALES in default/available tabs for curation waves.
MyStream Sales UI
components/brain/my-stream/MyStreamWave.tsx, components/brain/my-stream/MyStreamWaveTabsLeaderboard.tsx, components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
Inserted SALES tab content, added renderAfterLeaderboard prop to leaderboard tabs, and conditional visibility/selection logic for Sales tab on curation waves.
New Sales Component & Layout
components/brain/my-stream/MyStreamWaveSales.tsx, components/brain/my-stream/layout/LayoutContext.tsx
New MyStreamWaveSales component rendering sales grid with pagination; added salesViewStyle to layout context and provider.
Data & Hooks
hooks/useDebouncedQueryRefetch.ts, hooks/useWaveDrops.ts, hooks/useWaveGalleryDrops.ts, hooks/useDropMessages.ts
New debounced refetch hook; implemented paginated drops hook with WebSocket refetch integration; delegated gallery hook to useWaveDrops; simplified drop message refetching to use the new debounce hook.
Decisions Pagination
hooks/waves/useWaveDecisions.ts
Switched to useInfiniteQuery, added pagination, aggregation, and new fetchNextPage/hasNextPage/isFetchingNextPage outputs.
Tests
__tests__/components/brain/*, __tests__/hooks/*
Added/updated tests for SALES tab, MyStreamWaveSales behavior, useDebouncedQueryRefetch, useWaveDrops, useWaveGalleryDrops, and updated wave decisions tests to infinite-query shape.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant BrainMobileTabs
    participant BrainMobile
    participant MyStreamWave
    participant MyStreamWaveSales
    participant useWaveDecisions
    participant API

    User->>BrainMobileTabs: Click SALES tab (curation wave)
    BrainMobileTabs->>BrainMobile: onViewChange(BrainView.SALES)
    BrainMobile->>MyStreamWave: Render with activeTab=SALES
    MyStreamWave->>MyStreamWaveSales: Render SALES content (waveId)
    MyStreamWaveSales->>useWaveDecisions: Fetch decisions for wave
    useWaveDecisions->>API: GET /decisions?waveId=...
    API-->>useWaveDecisions: Return decisionPoints
    useWaveDecisions-->>MyStreamWaveSales: Return decisions
    MyStreamWaveSales->>MyStreamWaveSales: Extract sale URLs and render MarketplacePreview cards
    MyStreamWaveSales-->>User: Display sales grid
Loading
sequenceDiagram
    participant useWaveDrops
    participant useInfiniteQuery
    participant API
    participant WebSocket
    participant useDebouncedQueryRefetch as Debounce

    useWaveDrops->>useInfiniteQuery: Initialize queryKey & queryFn
    useInfiniteQuery->>API: Fetch initial drops page
    API-->>useInfiniteQuery: Return paginated drops
    useInfiniteQuery-->>useWaveDrops: Return data + fetchNextPage
    WebSocket->>useWaveDrops: DROP_UPDATE for waveId
    useWaveDrops->>Debounce: requestRefetch()
    Debounce->>useInfiniteQuery: refetch() (debounced)
    useInfiniteQuery->>API: Re-fetch drops
    API-->>useInfiniteQuery: Updated drops
    useInfiniteQuery-->>useWaveDrops: Updated data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ragnep
  • prxt6529
  • GelatoGenesis

Poem

🐰 A little hop into curation's halls,
New SALES tabs tucked between the calls,
Debounced refetch keeps the flow neat,
Sales cards bloom tidy and sweet,
Hop, click, and watch the previews meet.

🚥 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 sales' directly aligns with the main objective of adding a SALES tab/view specifically for curation waves, which is the primary change across all modified files.

✏️ 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-sales
📝 Coding Plan for PR comments
  • Generate coding plan

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

import { USER_PAGE_ACTIVITY_TAB } from "./activity.types";

P2 Badge Restore enum re-export for activity wrapper consumers

This change removes the module-level re-export of USER_PAGE_ACTIVITY_TAB, but existing in-repo consumers still import that symbol from this file (for example __tests__/components/user/stats/activity/UserPageActivityTab.test.tsx and __tests__/components/user/stats/activity/tabs/UserPageActivityTabs.test.tsx). In environments that run these tests (or any downstream code using that import path), the import now fails with a missing export error, so this commit introduces a breaking API change without migrating all callers.

ℹ️ 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".

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

Caution

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

⚠️ Outside diff range comments (1)
components/brain/mobile/BrainMobileTabs.tsx (1)

255-287: ⚠️ Potential issue | 🟠 Major

Hide OUTCOME for curation waves in this mobile branch.

This block now adds SALES for curation waves, but it still renders BrainView.OUTCOME unconditionally a few lines later. That no longer matches the curation tab contract used elsewhere in this PR, where curation waves include SALES and omit OUTCOME, so mobile will expose an extra tab/view that desktop does not.

Suggested fix
-            <button
-              ref={(el) => {
-                tabRefs.current[BrainView.OUTCOME] = el;
-              }}
-              onClick={() => onViewChange(BrainView.OUTCOME)}
-              className={outcomeButtonClasses}
-            >
-              <span className={otucomeButtonTextClasses}>Outcome</span>
-            </button>
+            {!isCurationWave && (
+              <button
+                ref={(el) => {
+                  tabRefs.current[BrainView.OUTCOME] = el;
+                }}
+                onClick={() => onViewChange(BrainView.OUTCOME)}
+                className={outcomeButtonClasses}
+              >
+                <span className={otucomeButtonTextClasses}>Outcome</span>
+              </button>
+            )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/brain/mobile/BrainMobileTabs.tsx` around lines 255 - 287, The
OUTCOME button is rendered unconditionally which exposes BrainView.OUTCOME for
curation waves; update the JSX so the button that assigns
tabRefs.current[BrainView.OUTCOME] and renders with
outcomeButtonClasses/otucomeButtonTextClasses is only rendered when
isCurationWave is false (e.g., wrap it in a conditional !isCurationWave) to
match the curation wave contract used elsewhere (where curation waves include
SALES and omit OUTCOME); ensure tabRefs and onViewChange usage remains identical
inside that conditional.
🧹 Nitpick comments (2)
__tests__/hooks/waves/useWaveDecisions.test.ts (2)

13-39: Consider adding edge-case tests for robustness.

The current test covers the happy path well. For more confidence, consider adding tests for:

  • waveId being undefined or null (query should be disabled)
  • Empty data array response
  • Error state handling
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/hooks/waves/useWaveDecisions.test.ts` around lines 13 - 39, Add
unit tests in the useWaveDecisions test suite to cover edge cases: 1) call
renderHook(() => useWaveDecisions({ waveId: undefined })) and assert
useQueryMock was called with enabled: false for QueryKey.WAVE_DECISIONS and that
result.current.decisionPoints is empty/handled; 2) mock useQueryMock to return {
data: { data: [] }, isError: false, ... } and assert decisionPoints is an empty
array; 3) mock useQueryMock to return an error state (isError: true, error:
someError) and assert the hook surfaces error state (e.g.,
result.current.isError or result.current.error) and does not crash; use the
existing symbols useWaveDecisions, useQueryMock, fetchMock,
QueryKey.WAVE_DECISIONS, and decisionPoints to locate where to change tests.

21-21: Unused mock setup – fetchMock.mockResolvedValue has no effect.

Since useQuery is fully mocked (lines 22-28), the queryFn inside useWaveDecisions is never executed. This means fetchMock.mockResolvedValue(data) is dead code and can mislead readers into thinking commonApiFetch is being tested.

Either remove this line or, if you want to test the integration with commonApiFetch, mock only the fetch and let useQuery call through to the actual implementation.

🧹 Proposed fix: Remove unused mock
-    fetchMock.mockResolvedValue(data);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/hooks/waves/useWaveDecisions.test.ts` at line 21, Remove the dead
mock by deleting the fetchMock.mockResolvedValue(data) call in the test for
useWaveDecisions; since useQuery is fully mocked (the mock of useQuery that
stubs the queryFn on lines ~22-28), the queryFn inside useWaveDecisions never
executes and the fetch mock (fetchMock.mockResolvedValue) has no effect—either
remove that line, or if you intend to test integration with commonApiFetch
instead, stop mocking useQuery so the real queryFn in useWaveDecisions runs and
let commonApiFetch be hit by the fetchMock.
🤖 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/brain/my-stream/MyStreamWaveSales.tsx`:
- Around line 39-45: The intersection callback driving auto-pagination (created
via useIntersectionObserver and stored in intersectionElementRef) calls
fetchNextPage blindly, which causes infinite advancement when pages contain zero
renderable sales (salesUrls filtered by nft_link). Change the guard so
fetchNextPage only runs when there is at least one newly rendered sale on the
current page or the sentinel has been moved below the last rendered item: track
the count/index of rendered sales (e.g., lastRenderableCount) derived from
salesUrls (or detect whether the latest page added any items with nft_link) and
return early if the latest page produced zero new renderable items; otherwise
call fetchNextPage. Keep the existing checks (hasNextPage, isFetching,
isFetchingNextPage) and update the intersection handler used in
useIntersectionObserver accordingly.

---

Outside diff comments:
In `@components/brain/mobile/BrainMobileTabs.tsx`:
- Around line 255-287: The OUTCOME button is rendered unconditionally which
exposes BrainView.OUTCOME for curation waves; update the JSX so the button that
assigns tabRefs.current[BrainView.OUTCOME] and renders with
outcomeButtonClasses/otucomeButtonTextClasses is only rendered when
isCurationWave is false (e.g., wrap it in a conditional !isCurationWave) to
match the curation wave contract used elsewhere (where curation waves include
SALES and omit OUTCOME); ensure tabRefs and onViewChange usage remains identical
inside that conditional.

---

Nitpick comments:
In `@__tests__/hooks/waves/useWaveDecisions.test.ts`:
- Around line 13-39: Add unit tests in the useWaveDecisions test suite to cover
edge cases: 1) call renderHook(() => useWaveDecisions({ waveId: undefined }))
and assert useQueryMock was called with enabled: false for
QueryKey.WAVE_DECISIONS and that result.current.decisionPoints is empty/handled;
2) mock useQueryMock to return { data: { data: [] }, isError: false, ... } and
assert decisionPoints is an empty array; 3) mock useQueryMock to return an error
state (isError: true, error: someError) and assert the hook surfaces error state
(e.g., result.current.isError or result.current.error) and does not crash; use
the existing symbols useWaveDecisions, useQueryMock, fetchMock,
QueryKey.WAVE_DECISIONS, and decisionPoints to locate where to change tests.
- Line 21: Remove the dead mock by deleting the
fetchMock.mockResolvedValue(data) call in the test for useWaveDecisions; since
useQuery is fully mocked (the mock of useQuery that stubs the queryFn on lines
~22-28), the queryFn inside useWaveDecisions never executes and the fetch mock
(fetchMock.mockResolvedValue) has no effect—either remove that line, or if you
intend to test integration with commonApiFetch instead, stop mocking useQuery so
the real queryFn in useWaveDecisions runs and let commonApiFetch be hit by the
fetchMock.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c224adb1-74b5-4b9b-b0f8-3cba7d573065

📥 Commits

Reviewing files that changed from the base of the PR and between af8221b and 3220353.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • __tests__/components/brain/BrainMobile.test.tsx
  • __tests__/components/brain/ContentTabContext.test.tsx
  • __tests__/components/brain/mobile/BrainMobileTabs.test.tsx
  • __tests__/components/brain/my-stream/MyStreamWave.test.tsx
  • __tests__/components/brain/my-stream/MyStreamWaveDesktopTabs.test.tsx
  • __tests__/components/brain/my-stream/MyStreamWaveSales.test.tsx
  • __tests__/components/brain/my-stream/MyStreamWaveTabsLeaderboard.test.tsx
  • __tests__/hooks/useDebouncedQueryRefetch.test.ts
  • __tests__/hooks/useWaveDrops.test.ts
  • __tests__/hooks/useWaveGalleryDrops.test.ts
  • __tests__/hooks/waves/useWaveDecisions.test.ts
  • components/brain/BrainMobile.tsx
  • components/brain/ContentTabContext.tsx
  • components/brain/mobile/BrainMobileTabs.tsx
  • components/brain/my-stream/MyStreamWave.tsx
  • components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
  • components/brain/my-stream/MyStreamWaveSales.tsx
  • components/brain/my-stream/MyStreamWaveTabsLeaderboard.tsx
  • components/brain/my-stream/layout/LayoutContext.tsx
  • components/user/stats/activity/UserPageActivityWrapper.tsx
  • components/user/stats/activity/activity.helpers.ts
  • hooks/useDebouncedQueryRefetch.ts
  • hooks/useDropMessages.ts
  • hooks/useWaveDrops.ts
  • hooks/useWaveGalleryDrops.ts
  • types/waves.types.ts
💤 Files with no reviewable changes (2)
  • components/user/stats/activity/UserPageActivityWrapper.tsx
  • components/user/stats/activity/activity.helpers.ts

Comment thread components/brain/my-stream/MyStreamWaveSales.tsx Outdated
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.

🧹 Nitpick comments (1)
components/brain/my-stream/MyStreamWaveSales.tsx (1)

94-101: Consider consolidating duplicate "No sales yet." rendering paths.

There are two separate code paths rendering "No sales yet.":

  1. Lines 94-101: when salesUrls.length === 0 && !hasNextPage
  2. Lines 118-124: when inside the else branch with salesUrls.length === 0 (but hasNextPage is true)

The second path only triggers when there are no renderable sales but more pages exist. Since handleIntersection blocks fetching when salesUrls.length === 0, users would see "No sales yet." even though subsequent pages might contain sales.

This is an acceptable trade-off to prevent infinite fetching, but worth documenting with a comment explaining the intentional behavior.

📝 Suggested clarifying comment
   } else if (salesUrls.length === 0 && !hasNextPage) {
+    // No renderable sales and pagination exhausted
     salesContent = (
       ...
     );
   } else {
     salesContent = (
       <div className="tw-p-2 sm:tw-p-4 lg:tw-pr-2">
         {salesUrls.length > 0 ? (
           ...
         ) : (
+          // No renderable sales yet; we don't auto-paginate with 0 sales
+          // to prevent infinite fetching on sparse waves
           <div className="tw-flex tw-h-full tw-items-center tw-justify-center tw-p-6">

Also applies to: 118-124

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

In `@components/brain/my-stream/MyStreamWaveSales.tsx` around lines 94 - 101,
Consolidate the duplicate "No sales yet." JSX by extracting it into one reusable
constant or small render helper (e.g., create renderNoSales and use it wherever
salesContent is set) and replace the two separate JSX blocks with that single
reference; also add a short inline comment near handleIntersection and the
salesUrls/hasNextPage logic explaining that handleIntersection intentionally
blocks further fetches when salesUrls.length === 0 so showing "No sales yet." in
that case is expected behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@components/brain/my-stream/MyStreamWaveSales.tsx`:
- Around line 94-101: Consolidate the duplicate "No sales yet." JSX by
extracting it into one reusable constant or small render helper (e.g., create
renderNoSales and use it wherever salesContent is set) and replace the two
separate JSX blocks with that single reference; also add a short inline comment
near handleIntersection and the salesUrls/hasNextPage logic explaining that
handleIntersection intentionally blocks further fetches when salesUrls.length
=== 0 so showing "No sales yet." in that case is expected behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: abac6ab1-463c-4024-aa03-ee3e80bf64e3

📥 Commits

Reviewing files that changed from the base of the PR and between 3220353 and 669eeea.

📒 Files selected for processing (2)
  • __tests__/components/brain/my-stream/MyStreamWaveSales.test.tsx
  • components/brain/my-stream/MyStreamWaveSales.tsx

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

🧹 Nitpick comments (1)
components/brain/my-stream/MyStreamWaveSales.tsx (1)

32-43: Use a stable key instead of ${url}-${index}.

This list is newest-first, so prepending one new sale shifts every later index and remounts existing MarketplacePreview cards. Keep the drop identity alongside the URL and key on that.

Suggested refactor
-  const salesUrls = useMemo(
+  const sales = useMemo(
     () =>
       decisionPoints
         .slice()
         .reverse()
         .flatMap((decisionPoint) =>
           decisionPoint.winners.flatMap((winner) => {
             const url = getFirstSaleUrl(winner.drop.nft_links);
-            return url ? [url] : [];
+            return url ? [{ id: winner.drop.id, url }] : [];
           })
         ),
     [decisionPoints]
   );
-  const isInitialLoading = isFetching && salesUrls.length === 0;
+  const isInitialLoading = isFetching && sales.length === 0;
@@
-  } else if (salesUrls.length === 0) {
+  } else if (sales.length === 0) {
@@
-          {salesUrls.map((url, index) => (
-            <MarketplacePreview key={`${url}-${index}`} href={url} compact />
+          {sales.map(({ id, url }) => (
+            <MarketplacePreview key={id} href={url} compact />
           ))}

Also applies to: 71-73

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

In `@components/brain/my-stream/MyStreamWaveSales.tsx` around lines 32 - 43, The
current stable-key issue: salesUrls is built from decisionPoints and components
use `${url}-${index}` which breaks stability when new items prepend; update code
to produce and use a stable identity combining the sale URL and the drop's
unique id (e.g., use winner.drop.id or another stable drop identifier) rather
than the array index — modify the salesUrls construction (where getFirstSaleUrl
is called) to return objects like { url, dropId } and then use that dropId when
rendering MarketplacePreview (also update the same pattern at the other
occurrence around lines 71-73) so keys remain stable across prepends.
🤖 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/brain/my-stream/MyStreamWaveSales.tsx`:
- Around line 31-43: The sales list is truncated because useWaveDecisions
currently only fetches page 1 with page_size: 100; update the data fetching in
useWaveDecisions (or its caller) to paginate and aggregate all pages (or stream
until no more results) so decisionPoints contains the full history, then keep
the existing useMemo that computes salesUrls from decisionPoints; ensure the
pagination logic deduplicates/merges pages and preserves ordering
(reverse+flatMap behavior relying on decisionPoints) so older rounds are not
silently dropped.
- Around line 31-64: The component currently treats an errored fetch the same as
an empty result because it only checks isFetching and decisionPoints; update the
use of useWaveDecisions to also destructure the error/ isError (or similar) flag
from the hook (e.g., const { decisionPoints, isFetching, error, isError } =
useWaveDecisions({ waveId })) and add an explicit branch before the empty-state
branch that renders an error UI when error/isError is truthy (use the same
styling container used for loading/empty states and include a concise message
like "Failed to load sales" plus error.message). Ensure references to
decisionPoints, salesUrls, isInitialLoading, and salesContent remain correct and
that the error branch runs before the salesUrls.length === 0 branch.

---

Nitpick comments:
In `@components/brain/my-stream/MyStreamWaveSales.tsx`:
- Around line 32-43: The current stable-key issue: salesUrls is built from
decisionPoints and components use `${url}-${index}` which breaks stability when
new items prepend; update code to produce and use a stable identity combining
the sale URL and the drop's unique id (e.g., use winner.drop.id or another
stable drop identifier) rather than the array index — modify the salesUrls
construction (where getFirstSaleUrl is called) to return objects like { url,
dropId } and then use that dropId when rendering MarketplacePreview (also update
the same pattern at the other occurrence around lines 71-73) so keys remain
stable across prepends.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b23ec6f-6030-4979-bed1-cbaa9b1b7f33

📥 Commits

Reviewing files that changed from the base of the PR and between 669eeea and 87a2eac.

📒 Files selected for processing (2)
  • __tests__/components/brain/my-stream/MyStreamWaveSales.test.tsx
  • components/brain/my-stream/MyStreamWaveSales.tsx

Comment thread components/brain/my-stream/MyStreamWaveSales.tsx Outdated
Comment thread components/brain/my-stream/MyStreamWaveSales.tsx Outdated
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

@simo6529 simo6529 merged commit e7351bc into main Mar 12, 2026
6 checks passed
@simo6529 simo6529 deleted the curation-sales branch March 12, 2026 15:55
@coderabbitai coderabbitai Bot mentioned this pull request Mar 24, 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