Skip to content

Full NFT Picker UI#1498

Merged
simo6529 merged 44 commits intoxTDHfrom
feature/nft-picker
Oct 6, 2025
Merged

Full NFT Picker UI#1498
simo6529 merged 44 commits intoxTDHfrom
feature/nft-picker

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Sep 30, 2025

Summary by CodeRabbit

  • New Features

    • Full NFT picker: collection search with suggestions, Single/Bucket/All selection modes, range editing, Select All, spam filtering, virtualization, accessibility, and lazy metadata loading.
  • New Components

    • Demo page and client, picker UI, contract header, suggestion list, token list, range editor, All‑tokens status card.
  • Utilities

    • Robust token parsing/range handling, batched NFT metadata/search backend integration, client caching with abort/retry.
  • Documentation

    • Comprehensive implementation spec and quick checklist.
  • Tests

    • Unit and integration tests for parsing, range handling, and API behaviors.
  • Chores

    • Added virtualization dependency and image domains; added query keys for picker.

Signed-off-by: Simo <simo@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 30, 2025

Walkthrough

Adds a complete NFT Picker implementation: spec, types, parsing utilities, React client components, Alchemy/6529 service integrations, client hooks with in-memory caches and virtualization, tests, demo page, docs, Next.js image domains, and a new dependency for virtualization.

Changes

Cohort / File(s) Summary
Specification
spec.md
New comprehensive implementation spec for the NFT Picker (goals, output contracts, modes, parsing/canonicalization, data fetching, virtualization, accessibility, styling, security, testing, acceptance criteria).
Types & Utilities
components/nft-picker/NftPicker.types.ts, components/nft-picker/NftPicker.utils.ts
New TypeScript domain model and utilities: token/range parsing, canonicalization, bigint-safe handling, formatting, merging, windowed expansion, constants and errors (exports include MAX_SAFE, MAX_ENUMERATION, parseTokenExpressionToRanges/ToBigints, toCanonicalRanges/fromCanonicalRanges, sortAndDedupIds/mergeAndSort, tryToNumberArray, expandRangesWindow, etc.).
Picker UI & Subcomponents
components/nft-picker/NftPicker.tsx, components/nft-picker/NftSuggestList.tsx, components/nft-picker/NftTokenList.tsx, components/nft-picker/NftContractHeader.tsx, components/nft-picker/NftEditRanges.tsx, components/nft-picker/AllTokensSelectedCard.tsx
New client React components implementing the NFT Picker UI: collection search, virtualized suggestion list, contract header, token list (virtualized, lazy metadata), range editor, all-selected card, accessibility roles, keyboard navigation, mode switching, select-all, and onChange emission.
Client Hooks & Caching
components/nft-picker/useAlchemyClient.ts
New hooks and caching layer: debounced collection search, contract overview caching, token metadata caching with TTL, deduplication and batching, and primeContractCache. Exports: useCollectionSearch, useContractOverviewQuery, useTokenMetadataQuery, primeContractCache.
Services: Alchemy & 6529
services/alchemy-api.ts, services/6529api.ts
Added Alchemy NFT API helpers and typings, normalization/extraction helpers, searchNftCollections, getContractOverview, getTokensMetadata (batched), enhanced getNftsForContractAndOwner with retries/abort; 6529 API helpers updated to accept optional RequestInit and merge headers. New abort/delay utilities included.
React Query Keys
components/react-query-wrapper/ReactQueryWrapper.tsx
Added QueryKey enum members: NFT_PICKER_SEARCH, NFT_PICKER_CONTRACT, NFT_PICKER_TOKENS.
Demo & Client Page
app/demo/nft-picker/page.tsx, app/demo/nft-picker/picker-client.tsx
New Next.js demo page and client component DemoNftPicker demonstrating controlled usage, state handling, bigint-aware stringify helper, and rendering picker output.
Tests
__tests__/components/nft-picker/NftPicker.utils.test.ts, __tests__/services/alchemy-api.test.ts
New/updated tests: utils parsing/merging/formatting/range behaviors; alchemy-api tests mocking 6529 responses and validating searchNftCollections, getContractOverview, getTokensMetadata.
Docs & Checklist
docs/nft-picker-quick-checklist.md
New quick-checklist documenting props, events, behavior, edge-cases, usage, and limitations.
Build / Config
next.config.mjs, package.json
Added image domains (i.seadn.io, i2.seadn.io, res.cloudinary.com) and dependency @tanstack/react-virtual.
Minor Typing Additions
multiple files (gas/royalties, nextGen, contexts)
Several call-sites updated to add generic type parameters to existing fetchUrl calls to tighten response typings (no control-flow changes).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as NftPicker UI
  participant Hooks as useAlchemyClient
  participant Cache as In‑memory Cache
  participant API as Alchemy / 6529
  participant V as Virtualizer

  rect rgba(230,245,255,0.6)
    User->>UI: search / paste contract / switch mode
    UI->>Hooks: useCollectionSearch(query, hideSpam)
    alt cache hit
      Hooks->>Cache: lookup(query, hideSpam)
      Cache-->>Hooks: cached suggestions
    else cache miss
      Hooks->>API: searchNftCollections(query)
      API-->>Hooks: suggestions + spam flags
      Hooks->>Cache: store(query, suggestions)
    end
    Hooks-->>UI: suggestions
    UI->>V: render(visibleRows)
    V-->>UI: visibleRows (lazy metadata requests)
  end

  rect rgba(240,255,240,0.6)
    User->>UI: select contract / pick tokens / edit ranges / Select All
    UI->>UI: parseTokenExpressionToBigints -> canonicalize ranges
    UI->>Hooks: useTokenMetadataQuery(address, tokenIdsWindow)
    Hooks->>API: getTokensMetadata(batched)
    API-->>Hooks: token metadata
    Hooks-->>UI: metadata
    UI-->>User: onChange(NftPickerValue)
  end

  rect rgba(255,245,230,0.6)
    API-->>Hooks: error | empty | spam flags
    Hooks-->>UI: propagate errors / spam indicators
    UI-->>User: ARIA live messages, toggles (show spam)
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • ragnep

Poem

A rabbit hops through ranges, bigints in tow,
I parse and tidy tokens row by row.
I cache the carrots, virtualize the feast,
From demo to docs, the picker’s released.
Hooray — a hop, a spec, and metadata aglow! 🥕✨

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 “Full NFT Picker UI” directly captures the primary change in this pull request, which is the implementation of a comprehensive NFT picker user interface complete with its components, utilities, tests, demos, and documentation.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/nft-picker

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

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)
spec.md (1)

228-233: Promote “API specifics” to a proper heading.

markdownlint (MD036) warns that using bold here instead of a heading trips the “emphasis used as heading” rule. Please switch to an actual heading level (e.g., ### API specifics) to keep the linter green.

Apply this diff:

-**API specifics**
+### API specifics
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cc63ab and 4548fda.

📒 Files selected for processing (1)
  • spec.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
spec.md

69-69: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


228-228: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
spec.md (1)

69-81: Add a language hint to this fenced block.

markdownlint (MD040) flagged this block for missing a language. Please annotate it (e.g., ```text or ```plaintext) so linting and highlighting stay happy.

[ suggest_optional_refactor ]
Apply this diff:

-```
+```text
 /components/nft-picker/
   NftPicker.tsx                // client component (combobox + modes + selected list)
   NftPicker.types.ts           // exported types
@@
 /services/6529api.ts           // reuse existing fetch helpers

</blockquote></details>

</blockquote></details>

</details>

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

Comment thread spec.md 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.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4548fda and 5cb472a.

📒 Files selected for processing (1)
  • spec.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
spec.md

78-78: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


237-237: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

Comment thread spec.md Outdated
Signed-off-by: OpenAI Assistant <123456+openai-assistant@users.noreply.github.com>
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>
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>
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>
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>
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: 11

Caution

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

⚠️ Outside diff range comments (2)
services/alchemy-api.ts (2)

490-497: Bug: wrong chainId passed during pagination recursion.

Should pass the original chainId, not NEXTGEN_CHAIN_ID.

Apply:

-    return getNftsForContractAndOwner(
-      NEXTGEN_CHAIN_ID,
+    return getNftsForContractAndOwner(
+      chainId,
       contract,
       owner,
       nfts,
       response.pageKey
     );

463-486: Add retry cap to avoid infinite recursion on API errors.

The error path re-calls itself without limits. Add a bounded retry counter.

Apply:

-export async function getNftsForContractAndOwner(
+export async function getNftsForContractAndOwner(
   chainId: number,
   contract: string,
   owner: string,
   nfts?: any[],
-  pageKey?: string
+  pageKey?: string,
+  retries: number = 0
 ) {
@@
-  const response = await fetchLegacyUrl(url);
+  const response = await fetchLegacyUrl(url);
   if (response.error) {
-    return getNftsForContractAndOwner(chainId, contract, owner, nfts, pageKey);
+    if (retries >= 3) {
+      throw new Error("Failed to fetch NFTs for owner after retries");
+    }
+    return getNftsForContractAndOwner(chainId, contract, owner, nfts, pageKey, retries + 1);
   }
♻️ Duplicate comments (1)
spec.md (1)

136-181: Unify the emitted output type with the union shown earlier.

This section defines NftSelectionOutput as numbers‑only and drops outputMode/tokenIdsRaw, diverging from the union contract defined at Lines 13–27. Update this block to the same discriminated union.

Apply this replacement:

-export type NftSelectionOutput = {
-  contractAddress: `0x${string}`;
-  tokenIds: number[]; // empty = all
-};
+export type NftSelectionOutput =
+  | {
+      contractAddress: `0x${string}`;
+      outputMode: 'number';
+      tokenIds: number[];
+      tokenIdsRaw: bigint[];
+    }
+  | {
+      contractAddress: `0x${string}`;
+      outputMode: 'bigint';
+      tokenIds: string[];
+      tokenIdsRaw: bigint[];
+    };
🧹 Nitpick comments (12)
services/6529api.ts (1)

38-58: Reorder init spread before method/body
To prevent callers from accidentally overriding the POST method or JSON body, move ...init above method: "POST" and body: JSON.stringify(body) in the fetch options. No current postData calls pass method or body in their init object, so this is purely future-proofing.

components/nft-picker/NftTokenList.tsx (3)

94-108: Avoid double scroll restoration (initialOffset + manual scrollTop).

You set initialOffset on the virtualizer and also set container.scrollTop in effect. Pick one to prevent jumps.

Apply:

   useEffect(() => {
     const container = scrollContainerRef.current;
     if (!container) {
       return;
     }
-    container.scrollTop = getPosition(VIRTUAL_SCROLL_KEY);
     const handleScroll = () => {
       setPosition(VIRTUAL_SCROLL_KEY, container.scrollTop);
     };

159-166: Reuse computed windowTokenIds; avoid O(R) per visible row.

Resolve once via expandRangesWindow and index into it for each virtual row.

Apply:

-        {virtualItems.map((virtualItem) => {
-          const tokenId = resolveTokenIdAtIndex(ranges, virtualItem.index);
+        {virtualItems.map((virtualItem) => {
+          const windowIndex = virtualItem.index - firstVisibleIndex;
+          const tokenId = windowTokenIds[windowIndex] ?? null;
           if (tokenId === null) {
             return null;
           }

Also applies to: 116-126, 110-115


153-156: Prefer list semantics for simple lists (improves a11y).

Use role="list" with role="listitem" (or ul/li) unless you need interactive grid semantics.

Also applies to: 176-177

components/nft-picker/useAlchemyClient.ts (3)

37-40: Add simple TTL GC to in-memory caches to prevent unbounded growth.

Delete expired entries before reads. Keeps memory bounded during long sessions.

Apply:

 const tokenCache = new Map<string, CacheEntry<TokenMetadata[]>>();
 
+function gcExpired<T>(map: Map<string, CacheEntry<T>>, now = Date.now()) {
+  for (const [k, v] of map) {
+    if (v.expires <= now) map.delete(k);
+  }
+}
@@
   const result = useQuery({
@@
-    queryFn: async ({ signal }) => {
-      const cacheKey = getSuggestionCacheKey(
+    queryFn: async ({ signal }) => {
+      const cacheKey = getSuggestionCacheKey(
         debouncedQuery,
         chain,
         hideSpam
       );
-      const now = Date.now();
+      const now = Date.now();
+      gcExpired(suggestionCache, now);
       const cached = suggestionCache.get(cacheKey);
@@
   return useQuery({
@@
-    queryFn: async ({ signal }) => {
+    queryFn: async ({ signal }) => {
       if (!normalizedAddress) {
         return null;
       }
       const cacheKey = getContractCacheKey(normalizedAddress, chain);
-      const now = Date.now();
+      const now = Date.now();
+      gcExpired(contractCache, now);
       const cached = contractCache.get(cacheKey);
@@
   return useQuery({
@@
-    queryFn: async ({ signal }) => {
+    queryFn: async ({ signal }) => {
       if (!params) {
         return [] as TokenMetadata[];
       }
       const cacheKey = getTokenCacheKey(params);
-      const now = Date.now();
+      const now = Date.now();
+      gcExpired(tokenCache, now);
       const cached = tokenCache.get(cacheKey);

Also applies to: 101-126, 165-183, 226-244


192-203: Optional: normalise tokenIds for better cache hits.

Sorting uniqueIds improves reuse across sliding windows; only do this if response order is not relied upon elsewhere.


56-60: Minor: remove extraneous $ in token cache key.

The literal “:$” and “:$$” separators are unusual; consider plain “:”.

app/demo/nft-picker/picker-client.tsx (1)

8-21: Remove unused state/helper or surface the selection.
stringifySelection and the selection binding are unused, so TypeScript/ESLint (with noUnusedLocals) will fail the build. Either drop them or render the selection in the demo.

 export default function DemoNftPicker() {
-  const [selection, setSelection] = useState<NftPickerSelection | null>(null);
+  const [selection, setSelection] = useState<NftPickerSelection | null>(null);
+  const renderedSelection =
+    selection === null
+      ? "No selection"
+      : stringifySelection(selection);

   return (
     <div className="tw-@container tw-mx-auto tw-flex tw-max-w-3xl tw-flex-col tw-gap-4 tw-p-6">
-      <NftPicker onChange={setSelection} allowAll allowRanges hideSpam className="tw-shadow-lg" />
+      <NftPicker onChange={setSelection} allowAll allowRanges hideSpam className="tw-shadow-lg" />
+      <pre className="tw-rounded tw-bg-neutral-900 tw-p-4 tw-text-sm">{renderedSelection}</pre>
     </div>
   );
 }
components/nft-picker/NftPicker.utils.ts (1)

173-182: Naming nit: function suggests merging ranges but returns expanded IDs.

mergeAndSort only sorts and de-dupes; it doesn’t “merge” to ranges (that happens in toCanonicalRanges). Consider renaming to improve clarity (e.g., sortAndDedupeIds).

components/nft-picker/NftPicker.tsx (3)

3-6: Use FontAwesome instead of inline SVG (guideline).

Replace the inline SVG in Select All with FontAwesome and add the import.

As per coding guidelines.

-import { useCallback, useEffect, useMemo, useRef, useState, useId } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState, useId } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faCircle } from "@fortawesome/free-solid-svg-icons";
@@
-                      <svg
-                        className="tw-h-5 tw-w-5"
-                        fill="none"
-                        viewBox="0 0 24 24"
-                        stroke="currentColor"
-                        aria-hidden="true"
-                      >
-                        <circle cx="12" cy="12" r="10" strokeWidth="2" />
-                      </svg>
+                      <FontAwesomeIcon className="tw-h-5 tw-w-5" icon={faCircle} aria-hidden="true" />

Also applies to: 684-694


82-85: Make chain reactive to prop/value changes.

Freezing chain in state prevents updates when the parent prop changes post‑mount. Derive it from props to keep queries aligned.

-  const initialChain = ensureChain(value?.chain ?? defaultValue?.chain ?? chainProp);
-  const [chain] = useState<SupportedChain>(initialChain);
+  const chain = useMemo(
+    () => ensureChain(value?.chain ?? defaultValue?.chain ?? chainProp),
+    [value?.chain, defaultValue?.chain, chainProp]
+  );

Please confirm no callers rely on a one‑time initialization. If they do, add an explicit prop to lock the chain.


238-300: Large input ranges still parse via full expansion path.

Even with a cap in utils, big ranges hit parseTokenExpressionToBigints and then canonicalize, risking heavy work. Prefer parsing directly to ranges and counting via arithmetic to avoid enumeration, then only expand via expandRangesWindow for visible rows.

If preferred, I can add a parseTokenExpressionToRanges helper and thread it through tokenPreview.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cb472a and 5291ab4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • __tests__/components/nft-picker/NftPicker.utils.test.ts (1 hunks)
  • __tests__/services/alchemy-api.test.ts (1 hunks)
  • app/demo/nft-picker/page.tsx (1 hunks)
  • app/demo/nft-picker/picker-client.tsx (1 hunks)
  • components/nft-picker/AllTokensSelectedCard.tsx (1 hunks)
  • components/nft-picker/NftContractHeader.tsx (1 hunks)
  • components/nft-picker/NftEditRanges.tsx (1 hunks)
  • components/nft-picker/NftPicker.tsx (1 hunks)
  • components/nft-picker/NftPicker.types.ts (1 hunks)
  • components/nft-picker/NftPicker.utils.ts (1 hunks)
  • components/nft-picker/NftSuggestList.tsx (1 hunks)
  • components/nft-picker/NftTokenList.tsx (1 hunks)
  • components/nft-picker/useAlchemyClient.ts (1 hunks)
  • components/react-query-wrapper/ReactQueryWrapper.tsx (1 hunks)
  • docs/nft-picker-quick-checklist.md (1 hunks)
  • next.config.mjs (1 hunks)
  • package.json (1 hunks)
  • services/6529api.ts (2 hunks)
  • services/alchemy-api.ts (3 hunks)
  • spec.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript for implementation code

Files:

  • components/nft-picker/NftTokenList.tsx
  • components/react-query-wrapper/ReactQueryWrapper.tsx
  • app/demo/nft-picker/picker-client.tsx
  • components/nft-picker/useAlchemyClient.ts
  • components/nft-picker/NftContractHeader.tsx
  • __tests__/components/nft-picker/NftPicker.utils.test.ts
  • components/nft-picker/NftPicker.utils.ts
  • components/nft-picker/NftPicker.tsx
  • app/demo/nft-picker/page.tsx
  • components/nft-picker/NftPicker.types.ts
  • components/nft-picker/NftSuggestList.tsx
  • components/nft-picker/AllTokensSelectedCard.tsx
  • components/nft-picker/NftEditRanges.tsx
  • services/alchemy-api.ts
  • services/6529api.ts
  • __tests__/services/alchemy-api.test.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks

Files:

  • components/nft-picker/NftTokenList.tsx
  • components/react-query-wrapper/ReactQueryWrapper.tsx
  • app/demo/nft-picker/picker-client.tsx
  • components/nft-picker/NftContractHeader.tsx
  • components/nft-picker/NftPicker.tsx
  • app/demo/nft-picker/page.tsx
  • components/nft-picker/NftSuggestList.tsx
  • components/nft-picker/AllTokensSelectedCard.tsx
  • components/nft-picker/NftEditRanges.tsx
{app,pages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Use NextJS features that match the current version

Files:

  • app/demo/nft-picker/picker-client.tsx
  • app/demo/nft-picker/page.tsx
**/{__tests__/**/*.{ts,tsx},*.test.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/{__tests__/**/*.{ts,tsx},*.test.tsx}: Place tests in tests directories or alongside components as ComponentName.test.tsx
Mock external dependencies and APIs in tests

Files:

  • __tests__/components/nft-picker/NftPicker.utils.test.ts
  • __tests__/services/alchemy-api.test.ts
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/components/nft-picker/NftPicker.utils.test.ts
  • __tests__/services/alchemy-api.test.ts
__tests__/components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use @testing-library/react and @testing-library/user-event for React component tests

Files:

  • __tests__/components/nft-picker/NftPicker.utils.test.ts
app/**/page.tsx

📄 CodeRabbit inference engine (AGENTS.md)

All new pages must be created inside the app/ directory

Files:

  • app/demo/nft-picker/page.tsx
app/**/{page,layout}.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Routes in app/ should export a generateMetadata function using getAppMetadata

Files:

  • app/demo/nft-picker/page.tsx
🧬 Code graph analysis (13)
components/nft-picker/NftTokenList.tsx (4)
components/nft-picker/NftPicker.types.ts (3)
  • SupportedChain (3-3)
  • TokenRange (9-9)
  • TokenMetadata (31-37)
contexts/ScrollPositionContext.tsx (1)
  • useScrollPositionContext (36-40)
components/nft-picker/NftPicker.utils.ts (2)
  • expandRangesWindow (247-284)
  • formatCanonical (216-224)
components/nft-picker/useAlchemyClient.ts (1)
  • useTokenMetadataQuery (186-244)
app/demo/nft-picker/picker-client.tsx (2)
components/nft-picker/NftPicker.types.ts (1)
  • NftPickerSelection (39-51)
components/nft-picker/NftPicker.tsx (1)
  • NftPicker (66-755)
components/nft-picker/useAlchemyClient.ts (2)
services/alchemy-api.ts (5)
  • SearchContractsResult (511-511)
  • TokenMetadataParams (511-511)
  • searchNftCollections (294-325)
  • getContractOverview (327-374)
  • getTokensMetadata (415-454)
components/nft-picker/NftPicker.types.ts (4)
  • ContractOverview (26-29)
  • TokenMetadata (31-37)
  • SupportedChain (3-3)
  • Suggestion (13-24)
components/nft-picker/NftContractHeader.tsx (1)
components/nft-picker/NftPicker.types.ts (1)
  • ContractOverview (26-29)
__tests__/components/nft-picker/NftPicker.utils.test.ts (2)
components/nft-picker/NftPicker.utils.ts (9)
  • parseTokenExpressionToBigints (114-164)
  • mergeAndSort (173-182)
  • toCanonicalRanges (184-202)
  • fromCanonicalRanges (204-214)
  • formatCanonical (216-224)
  • tryToNumberArray (231-245)
  • MAX_SAFE (7-7)
  • bigintCompare (166-171)
  • expandRangesWindow (247-284)
components/nft-picker/NftPicker.types.ts (1)
  • TokenRange (9-9)
components/nft-picker/NftPicker.utils.ts (1)
components/nft-picker/NftPicker.types.ts (3)
  • ParseError (77-82)
  • TokenSelection (11-11)
  • TokenRange (9-9)
components/nft-picker/NftPicker.tsx (9)
components/nft-picker/NftPicker.types.ts (9)
  • SupportedChain (3-3)
  • TokenSelection (11-11)
  • TokenRange (9-9)
  • Suggestion (13-24)
  • ContractOverview (26-29)
  • NftPickerProps (60-75)
  • ParseError (77-82)
  • TokenIdBigInt (7-7)
  • NftPickerSelection (39-51)
components/nft-picker/NftPicker.utils.ts (8)
  • toCanonicalRanges (184-202)
  • formatCanonical (216-224)
  • parseTokenExpressionToBigints (114-164)
  • mergeAndSort (173-182)
  • formatBigIntWithSeparators (226-229)
  • fromCanonicalRanges (204-214)
  • tryToNumberArray (231-245)
  • MAX_SAFE (7-7)
components/nft-picker/useAlchemyClient.ts (3)
  • useContractOverviewQuery (148-184)
  • useCollectionSearch (92-146)
  • primeContractCache (246-259)
helpers/Helpers.ts (1)
  • isValidEthAddress (218-219)
components/nft-picker/NftSuggestList.tsx (1)
  • NftSuggestList (39-175)
components/nft-picker/NftContractHeader.tsx (1)
  • NftContractHeader (18-102)
components/nft-picker/AllTokensSelectedCard.tsx (1)
  • AllTokensSelectedCard (11-45)
components/nft-picker/NftEditRanges.tsx (1)
  • NftEditRanges (26-229)
components/nft-picker/NftTokenList.tsx (1)
  • NftTokenList (70-223)
app/demo/nft-picker/page.tsx (2)
components/providers/metadata.ts (1)
  • getAppMetadata (41-72)
app/demo/nft-picker/picker-client.tsx (1)
  • DemoNftPicker (15-23)
components/nft-picker/NftSuggestList.tsx (3)
components/nft-picker/NftPicker.types.ts (1)
  • Suggestion (13-24)
hooks/useVirtualizedWaves.ts (1)
  • useVirtualizedWaves (14-74)
components/distribution-plan-tool/common/DistributionPlanVerifiedIcon.tsx (1)
  • DistributionPlanVerifiedIcon (1-19)
components/nft-picker/NftEditRanges.tsx (3)
components/nft-picker/NftPicker.types.ts (2)
  • TokenRange (9-9)
  • ParseError (77-82)
components/nft-picker/NftPicker.utils.ts (2)
  • formatCanonical (216-224)
  • formatBigIntWithSeparators (226-229)
components/utils/icons/CopyIcon.tsx (1)
  • CopyIcon (1-21)
services/alchemy-api.ts (3)
components/nft-picker/NftPicker.types.ts (4)
  • SupportedChain (3-3)
  • Suggestion (13-24)
  • ContractOverview (26-29)
  • TokenMetadata (31-37)
helpers/Helpers.ts (1)
  • isValidEthAddress (218-219)
services/6529api.ts (2)
  • fetchUrl (6-23)
  • postData (38-58)
services/6529api.ts (2)
entities/IDBResponse.ts (1)
  • DBResponse (1-6)
services/auth/auth.utils.ts (1)
  • getStagingAuth (77-79)
__tests__/services/alchemy-api.test.ts (2)
services/6529api.ts (2)
  • fetchUrl (6-23)
  • postData (38-58)
services/alchemy-api.ts (3)
  • searchNftCollections (294-325)
  • getContractOverview (327-374)
  • getTokensMetadata (415-454)
🪛 Biome (2.1.2)
components/nft-picker/NftContractHeader.tsx

[error] 27-27: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 31-31: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 markdownlint-cli2 (0.18.1)
spec.md

78-78: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


237-237: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/nft-picker-quick-checklist.md

30-30: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
package.json (1)

64-64: LGTM! Virtualization dependency added correctly.

The @tanstack/react-virtual dependency supports the NFT picker's virtualized list rendering for large suggestion and token collections, as described in the spec.

next.config.mjs (1)

123-125: LGTM! Image domains added for NFT metadata.

The Seadn (OpenSea CDN) and Cloudinary domains enable Next.js image optimization for NFT images, contract logos, and metadata thumbnails used in the NFT picker components.

components/react-query-wrapper/ReactQueryWrapper.tsx (1)

75-77: LGTM! Query keys added for NFT picker cache management.

The new NFT_PICKER_SEARCH, NFT_PICKER_CONTRACT, and NFT_PICKER_TOKENS keys establish distinct cache namespaces for collection searches, contract metadata, and token data, supporting the NFT picker's react-query integration.

services/6529api.ts (1)

6-23: LGTM! fetchUrl correctly extended with RequestInit support.

The updated signature allows callers to pass custom request options while properly handling header merging and authentication.

components/nft-picker/NftTokenList.tsx (2)

169-175: Stable keys require unique tokenIds. Ensure ranges are canonicalised/deduped.

If overlapping ranges are allowed, duplicate keys can occur.

Would you confirm upstream canonicalisation/deduplication guarantees uniqueness? If not, I can add a quick normaliser before render.


180-186: Image domains whitelist.

Next/Image needs all possible metadata.imageUrl domains allowed in next.config.

Please confirm domains are whitelisted for Alchemy gateways/CDNs used here.

Comment thread components/nft-picker/AllTokensSelectedCard.tsx Outdated
Comment thread components/nft-picker/AllTokensSelectedCard.tsx
Comment thread components/nft-picker/NftContractHeader.tsx
Comment thread components/nft-picker/NftEditRanges.tsx Outdated
Comment on lines +6 to +7
import CopyIcon from "@/components/utils/icons/CopyIcon";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use FontAwesome icons in TSX components (project rule).

Replace the custom CopyIcon with FontAwesome to comply. As per coding guidelines.

Apply:

-import CopyIcon from "@/components/utils/icons/CopyIcon";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";
@@
-                  {copyStatus === "copied" ? (
-                    <span className="tw-font-semibold tw-text-emerald-300">✓ Copied!</span>
-                  ) : (
-                    <>
-                      <CopyIcon />
-                      <span>Copy</span>
-                    </>
-                  )}
+                  {copyStatus === "copied" ? (
+                    <span className="tw-inline-flex tw-items-center tw-gap-2 tw-font-semibold tw-text-emerald-300">
+                      <FontAwesomeIcon icon={faCheck} />
+                      <span>Copied</span>
+                    </span>
+                  ) : (
+                    <span className="tw-inline-flex tw-items-center tw-gap-2">
+                      <FontAwesomeIcon icon={faCopy} />
+                      <span>Copy</span>
+                    </span>
+                  )}

Also applies to: 146-153

🤖 Prompt for AI Agents
In components/nft-picker/NftEditRanges.tsx around lines 6-7 (and similarly lines
146-153), replace the custom CopyIcon usage with FontAwesome: remove the
CopyIcon import and add imports for FontAwesomeIcon from
@fortawesome/react-fontawesome and the faCopy icon from
@fortawesome/free-solid-svg-icons; then update the JSX where CopyIcon is
rendered to use <FontAwesomeIcon icon={faCopy} /> (preserve any className/props
for sizing/color), and update any TypeScript types/import statements accordingly
so the component compiles without the custom icon.

Comment thread components/nft-picker/NftPicker.tsx
Comment thread components/nft-picker/NftPicker.utils.ts
Comment thread components/nft-picker/NftTokenList.tsx Outdated
Comment thread docs/nft-picker-quick-checklist.md Outdated
Comment thread spec.md Outdated
Comment thread spec.md Outdated
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>
Signed-off-by: Simo <simo@6529.io>
@simo6529 simo6529 changed the title wip Full NFT Picker UI Oct 1, 2025
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

♻️ Duplicate comments (3)
components/nft-picker/NftEditRanges.tsx (1)

147-154: Use FontAwesome for the success state icon (project rule).

Replace the unicode check with faCheck to comply with “Use FontAwesome for icons” in TSX components. As per coding guidelines.

-import { faCopy } from "@fortawesome/free-solid-svg-icons";
+import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";
@@
-                  {copyStatus === "copied" ? (
-                    <span className="tw-font-semibold tw-text-emerald-300">✓ Copied!</span>
-                  ) : (
+                  {copyStatus === "copied" ? (
+                    <span className="tw-inline-flex tw-items-center tw-gap-2 tw-font-semibold tw-text-emerald-300">
+                      <FontAwesomeIcon icon={faCheck} />
+                      <span>Copied</span>
+                    </span>
+                  ) : (

Also applies to: 6-8

components/nft-picker/NftPicker.types.ts (1)

64-79: Props immutability looks good.

NftPickerProps correctly uses readonly on all fields. As per coding guidelines.

components/nft-picker/NftPicker.tsx (1)

548-625: Previous concern resolved: allSelected discriminator added.

The emitted payloads now include allSelected: isAll in all branches (lines 587, 603, 613), allowing consumers to distinguish "all tokens selected" from "no tokens selected" even when tokenIds is empty.

🧹 Nitpick comments (6)
components/nft-picker/NftEditRanges.tsx (1)

85-103: Improve legacy copy fallback reliability (iOS/WebKit edge).

Set an explicit selection range before execCommand to increase success rates on iOS/Safari.

   textarea.select();
+  try { textarea.setSelectionRange(0, textarea.value.length); } catch {}
   const successful = document.execCommand("copy");
components/nft-picker/useAlchemyClient.ts (2)

64-69: Make token cache key order‑insensitive for better hit rate.

Sort the tokenIds when building the cache key so [1,2,3] and [3,2,1] share the entry. This does not affect returned data order; it only improves cache reuse.

-function getTokenCacheKey(params: TokenMetadataParams): string {
-  return `${params.chain ?? "ethereum"}:${params.address.toLowerCase()}:${params.tokenIds.join("|")}`;
-}
+function getTokenCacheKey(params: TokenMetadataParams): string {
+  const ids = [...params.tokenIds].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
+  return `${params.chain ?? "ethereum"}:${params.address.toLowerCase()}:${ids.join("|")}`;
+}

Also applies to: 202-213


79-86: Add readonly to hook param types (immutability rule).

Mark fields readonly to match the repo convention for props/params in TS.

-type UseCollectionSearchParams = {
-  query: string;
-  chain?: SupportedChain;
-  hideSpam?: boolean;
-  debounceMs?: number;
-  enabled?: boolean;
-};
+type UseCollectionSearchParams = {
+  readonly query: string;
+  readonly chain?: SupportedChain;
+  readonly hideSpam?: boolean;
+  readonly debounceMs?: number;
+  readonly enabled?: boolean;
+};
@@
-type UseContractOverviewParams = {
-  address?: `0x${string}`;
-  chain?: SupportedChain;
-  enabled?: boolean;
-};
+type UseContractOverviewParams = {
+  readonly address?: `0x${string}`;
+  readonly chain?: SupportedChain;
+  readonly enabled?: boolean;
+};
@@
-type UseTokenMetadataParams = {
-  address?: `0x${string}`;
-  tokenIds: readonly string[];
-  chain?: SupportedChain;
-  enabled?: boolean;
-};
+type UseTokenMetadataParams = {
+  readonly address?: `0x${string}`;
+  readonly tokenIds: readonly string[];
+  readonly chain?: SupportedChain;
+  readonly enabled?: boolean;
+};

Also applies to: 87-92, 93-99

components/nft-picker/NftPicker.types.ts (1)

39-54: Optional: consider readonly on selection payloads.

If you want stronger guarantees for consumers, mark BaseSelection/NftPickerValue array fields as readonly to discourage mutation of emitted values.

-type BaseSelection = {
+type BaseSelection = {
   contractAddress: `0x${string}`;
   allSelected: boolean;
-  tokenIdsRaw: TokenIdBigInt[];
+  tokenIdsRaw: readonly TokenIdBigInt[];
 };
@@
 export type NftPickerValue = {
   chain: SupportedChain;
   contractAddress?: `0x${string}`;
-  selectedIds: TokenSelection;
+  selectedIds: Readonly<TokenSelection>;
   allSelected: boolean;
 };

Also applies to: 57-62

services/alchemy-api.ts (1)

459-523: Add AbortSignal and retry backoff to legacy owner fetch.

getNftsForContractAndOwner cannot be aborted and retries immediately; add an optional signal and basic backoff to avoid runaway retries and support cancellations.

-async function fetchLegacyUrl(url: string) {
-  const response = await fetch(url, legacyOptions);
+async function fetchLegacyUrl(url: string, signal?: AbortSignal) {
+  const response = await fetch(url, { ...legacyOptions, signal });
   return await response.json();
 }
 
-export async function getNftsForContractAndOwner(
+export async function getNftsForContractAndOwner(
   chainId: number,
   contract: string,
   owner: string,
   nfts?: any[],
-  pageKey?: string,
-  retries = 0
+  pageKey?: string,
+  retries = 0,
+  signal?: AbortSignal
 ) {
@@
-  const response = await fetchLegacyUrl(url);
+  const response = await fetchLegacyUrl(url, signal);
   if (response.error) {
-    if (retries >= MAX_GET_NFTS_RETRIES) {
+    if (retries >= MAX_GET_NFTS_RETRIES) {
       throw new Error("Failed to fetch NFTs for owner after retries");
     }
+    await new Promise((r) => setTimeout(r, 250 * (retries + 1)));
     return getNftsForContractAndOwner(
       chainId,
       contract,
       owner,
       nfts,
       pageKey,
-      retries + 1
+      retries + 1,
+      signal
     );
   }
@@
   if (response.pageKey) {
     return getNftsForContractAndOwner(
       chainId,
       contract,
       owner,
       nfts,
-      response.pageKey
+      response.pageKey,
+      signal
     );
   }
components/nft-picker/NftPicker.tsx (1)

113-124: Effect dependency may cause extra runs.

Including onContractChange in the dependency array is correct per React rules, but since it's likely not memoized by the parent, this effect may run more often than necessary.

Consider documenting that parent components should memoize the onContractChange callback with useCallback to prevent unnecessary re-initializations, or wrap the effect body in a conditional check to avoid redundant calls.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5291ab4 and a845855.

📒 Files selected for processing (14)
  • __tests__/components/nft-picker/NftPicker.utils.test.ts (1 hunks)
  • app/demo/nft-picker/picker-client.tsx (1 hunks)
  • components/nft-picker/AllTokensSelectedCard.tsx (1 hunks)
  • components/nft-picker/NftContractHeader.tsx (1 hunks)
  • components/nft-picker/NftEditRanges.tsx (1 hunks)
  • components/nft-picker/NftPicker.tsx (1 hunks)
  • components/nft-picker/NftPicker.types.ts (1 hunks)
  • components/nft-picker/NftPicker.utils.ts (1 hunks)
  • components/nft-picker/NftTokenList.tsx (1 hunks)
  • components/nft-picker/useAlchemyClient.ts (1 hunks)
  • docs/nft-picker-quick-checklist.md (1 hunks)
  • services/6529api.ts (2 hunks)
  • services/alchemy-api.ts (4 hunks)
  • spec.md (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/nft-picker-quick-checklist.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • services/6529api.ts
  • spec.md
👮 Files not reviewed due to content moderation or server errors (3)
  • components/nft-picker/NftContractHeader.tsx
  • components/nft-picker/NftTokenList.tsx
  • components/nft-picker/AllTokensSelectedCard.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript for implementation code

Files:

  • components/nft-picker/AllTokensSelectedCard.tsx
  • components/nft-picker/NftEditRanges.tsx
  • __tests__/components/nft-picker/NftPicker.utils.test.ts
  • components/nft-picker/NftPicker.types.ts
  • components/nft-picker/NftTokenList.tsx
  • components/nft-picker/NftPicker.utils.ts
  • components/nft-picker/NftContractHeader.tsx
  • components/nft-picker/useAlchemyClient.ts
  • app/demo/nft-picker/picker-client.tsx
  • components/nft-picker/NftPicker.tsx
  • services/alchemy-api.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks

Files:

  • components/nft-picker/AllTokensSelectedCard.tsx
  • components/nft-picker/NftEditRanges.tsx
  • components/nft-picker/NftTokenList.tsx
  • components/nft-picker/NftContractHeader.tsx
  • app/demo/nft-picker/picker-client.tsx
  • components/nft-picker/NftPicker.tsx
**/{__tests__/**/*.{ts,tsx},*.test.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/{__tests__/**/*.{ts,tsx},*.test.tsx}: Place tests in tests directories or alongside components as ComponentName.test.tsx
Mock external dependencies and APIs in tests

Files:

  • __tests__/components/nft-picker/NftPicker.utils.test.ts
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/components/nft-picker/NftPicker.utils.test.ts
__tests__/components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use @testing-library/react and @testing-library/user-event for React component tests

Files:

  • __tests__/components/nft-picker/NftPicker.utils.test.ts
{app,pages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Use NextJS features that match the current version

Files:

  • app/demo/nft-picker/picker-client.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons

Applied to files:

  • components/nft-picker/AllTokensSelectedCard.tsx
  • components/nft-picker/NftEditRanges.tsx
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.{ts,tsx} : Always add readonly before props

Applied to files:

  • components/nft-picker/AllTokensSelectedCard.tsx
  • components/nft-picker/NftPicker.types.ts
🧬 Code graph analysis (9)
components/nft-picker/NftEditRanges.tsx (2)
components/nft-picker/NftPicker.types.ts (2)
  • TokenRange (9-9)
  • ParseError (81-87)
components/nft-picker/NftPicker.utils.ts (2)
  • formatCanonical (374-382)
  • formatBigIntWithSeparators (384-387)
__tests__/components/nft-picker/NftPicker.utils.test.ts (2)
components/nft-picker/NftPicker.utils.ts (12)
  • parseTokenExpressionToBigints (247-267)
  • parseTokenExpressionToRanges (171-245)
  • MAX_ENUMERATION (8-8)
  • mergeAndSort (331-331)
  • toCanonicalRanges (333-351)
  • fromCanonicalRanges (353-372)
  • isRangeTooLargeError (29-35)
  • formatCanonical (374-382)
  • tryToNumberArray (389-403)
  • MAX_SAFE (7-7)
  • bigintCompare (312-317)
  • expandRangesWindow (405-442)
components/nft-picker/NftPicker.types.ts (1)
  • TokenRange (9-9)
components/nft-picker/NftTokenList.tsx (4)
components/nft-picker/NftPicker.types.ts (3)
  • SupportedChain (3-3)
  • TokenRange (9-9)
  • TokenMetadata (31-37)
contexts/ScrollPositionContext.tsx (1)
  • useScrollPositionContext (36-40)
components/nft-picker/NftPicker.utils.ts (2)
  • expandRangesWindow (405-442)
  • formatCanonical (374-382)
components/nft-picker/useAlchemyClient.ts (1)
  • useTokenMetadataQuery (196-255)
components/nft-picker/NftPicker.utils.ts (1)
components/nft-picker/NftPicker.types.ts (3)
  • ParseError (81-87)
  • TokenRange (9-9)
  • TokenSelection (11-11)
components/nft-picker/NftContractHeader.tsx (1)
components/nft-picker/NftPicker.types.ts (1)
  • ContractOverview (26-29)
components/nft-picker/useAlchemyClient.ts (2)
services/alchemy-api.ts (5)
  • SearchContractsResult (523-523)
  • TokenMetadataParams (523-523)
  • searchNftCollections (295-326)
  • getContractOverview (328-375)
  • getTokensMetadata (416-455)
components/nft-picker/NftPicker.types.ts (4)
  • ContractOverview (26-29)
  • TokenMetadata (31-37)
  • SupportedChain (3-3)
  • Suggestion (13-24)
app/demo/nft-picker/picker-client.tsx (1)
components/nft-picker/NftPicker.types.ts (1)
  • NftPickerSelection (45-53)
components/nft-picker/NftPicker.tsx (9)
components/nft-picker/NftPicker.types.ts (8)
  • SupportedChain (3-3)
  • TokenSelection (11-11)
  • TokenRange (9-9)
  • Suggestion (13-24)
  • ContractOverview (26-29)
  • NftPickerProps (64-79)
  • ParseError (81-87)
  • NftPickerSelection (45-53)
components/nft-picker/NftPicker.utils.ts (10)
  • toCanonicalRanges (333-351)
  • formatCanonical (374-382)
  • parseTokenExpressionToRanges (171-245)
  • formatBigIntWithSeparators (384-387)
  • mergeCanonicalRanges (269-280)
  • removeTokenFromRanges (282-310)
  • fromCanonicalRanges (353-372)
  • isRangeTooLargeError (29-35)
  • tryToNumberArray (389-403)
  • MAX_SAFE (7-7)
components/nft-picker/useAlchemyClient.ts (3)
  • useContractOverviewQuery (157-194)
  • useCollectionSearch (100-155)
  • primeContractCache (257-270)
helpers/Helpers.ts (1)
  • isValidEthAddress (218-219)
components/nft-picker/NftSuggestList.tsx (1)
  • NftSuggestList (39-175)
components/nft-picker/NftContractHeader.tsx (1)
  • NftContractHeader (18-102)
components/nft-picker/AllTokensSelectedCard.tsx (1)
  • AllTokensSelectedCard (12-46)
components/nft-picker/NftEditRanges.tsx (1)
  • NftEditRanges (27-230)
components/nft-picker/NftTokenList.tsx (1)
  • NftTokenList (55-211)
services/alchemy-api.ts (3)
components/nft-picker/NftPicker.types.ts (4)
  • SupportedChain (3-3)
  • Suggestion (13-24)
  • ContractOverview (26-29)
  • TokenMetadata (31-37)
helpers/Helpers.ts (1)
  • isValidEthAddress (218-219)
services/6529api.ts (2)
  • fetchUrl (6-23)
  • postData (38-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (25)
services/alchemy-api.ts (2)

244-293: Contract extraction is robust.

Good normalization across OpenSea fields, safelist mapping, spam flags, and deployer checksum. LGTM.


416-455: Batching and normalization for token metadata look correct.

Chunking by 100, passing AbortSignal, and tolerant parsing with image fallback are solid.

__tests__/components/nft-picker/NftPicker.utils.test.ts (1)

1-163: LGTM! Comprehensive test coverage.

The test suite thoroughly validates utility functions with appropriate edge cases, error handling, and assertions. Test organization is clear and follows best practices.

app/demo/nft-picker/picker-client.tsx (1)

1-30: LGTM! Clean demo implementation.

The component correctly handles BigInt serialization in JSON output and properly manages picker state. Implementation follows React best practices.

components/nft-picker/NftPicker.utils.ts (9)

7-35: LGTM! Range explosion safeguard implemented.

The MAX_ENUMERATION limit and structured error handling effectively address the previous concern about unbounded range expansion. The type guard and error factory provide clear error contracts.


43-89: Tokenization logic handles edge cases correctly.

The standalone dash merging (lines 70-84) properly guards against empty segments and correctly reconstructs range expressions from spaced input.


91-139: Robust token validation with clear error reporting.

The function correctly rejects invalid formats and provides precise error positioning through the overrideStart parameter.


141-169: Canonicalization correctly merges overlapping and adjacent ranges.

The sort-and-merge logic properly handles all edge cases including exact adjacency.


171-245: Comprehensive parsing with multi-level validation.

The function validates individual ranges and aggregate totals against MAX_ENUMERATION, provides detailed parse errors, and handles malformed input gracefully.


247-267: Clean error transformation layer.

Properly converts RangeTooLargeError to ParseError[] format for consistent error handling.


269-310: Token removal handles all split cases correctly.

The function properly handles edge, middle, and single-token scenarios, with final canonicalization ensuring consistency.


312-403: Utility functions provide safe conversions and formatting.

All conversion and formatting functions handle edge cases properly, including unsafe number conversions and range enumeration limits.


405-442: Virtualized windowing logic is correct.

The function properly calculates token windows across ranges with correct offset handling and boundary checks.

components/nft-picker/NftPicker.tsx (6)

1-49: Clean imports and constants.

All dependencies are appropriate and constants are well-defined. Complies with coding guidelines.


51-68: Helper functions are straightforward.

Conversion and defaulting logic is clear and correct.


70-111: Component signature follows guidelines.

Props are readonly, defaults are sensible, and initial state setup is clean.


195-546: Event handlers are well-structured.

All handlers properly manage state updates and emit appropriate change events. The logic for selecting/deselecting all tokens correctly preserves previous ranges.


560-582: Error handling preserves user experience.

The function properly catches range enumeration errors and updates UI state (parseErrors) without throwing, allowing the user to see feedback and correct the input.


634-784: Render structure is well-organized and accessible.

The component properly uses conditional rendering, includes appropriate accessibility attributes, and follows the coding guidelines for TailwindCSS and FontAwesome icons.

components/nft-picker/NftContractHeader.tsx (2)

23-33: Hook ordering issue resolved.

The conditional hook usage flagged in the previous review has been correctly addressed. The useCallback hooks now execute unconditionally before the early return, ensuring consistent hook ordering across all renders.


23-33: Hooks correctly placed before early return.

The previous conditional hook issue has been resolved. The useCallback hooks now execute on every render before the early return, maintaining consistent hook order.

components/nft-picker/NftTokenList.tsx (2)

39-49: BigInt overflow issue resolved.

The previous concern about Number overflow has been properly addressed. The function now accumulates totals using BigInt arithmetic, compares against a BigInt cap, and only converts to Number at the final return. This prevents precision loss and avoids extreme virtual container heights.


39-49: BigInt overflow safely handled.

The previous issue with Number(...) conversion and potential overflow has been resolved. The function now accumulates in BigInt, compares against a BigInt cap, and only converts to Number when the total is safely below the threshold.

components/nft-picker/AllTokensSelectedCard.tsx (2)

1-10: Previous review feedback addressed.

Both issues from the prior review have been resolved:

  • FontAwesome icons (faCheckCircle) now replace Heroicons.
  • Props interface correctly declares all members as readonly.

1-10: FontAwesome icons and readonly props correctly applied.

The previous issues have been resolved:

  • FontAwesome is now used instead of Heroicons (lines 1-2).
  • Props are marked as readonly (lines 8-9).

Comment thread components/nft-picker/NftPicker.tsx
Comment thread components/nft-picker/useAlchemyClient.ts
Signed-off-by: Simo <simo@6529.io>
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

♻️ Duplicate comments (1)
components/nft-picker/NftEditRanges.tsx (1)

6-7: Add faCheck icon import for consistent FontAwesome usage.

The component uses a Unicode checkmark "✓" at line 153 for the copied state, but the coding guidelines require FontAwesome icons in TSX files. Import faCheck from @fortawesome/free-solid-svg-icons to replace the Unicode character.

Apply this diff:

 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faCopy } from "@fortawesome/free-solid-svg-icons";
+import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";

Then update line 153:

                   {copyStatus === "copied" ? (
-                    <span className="tw-font-semibold tw-text-emerald-300">✓ Copied!</span>
+                    <span className="tw-inline-flex tw-items-center tw-gap-2 tw-font-semibold tw-text-emerald-300">
+                      <FontAwesomeIcon icon={faCheck} />
+                      <span>Copied!</span>
+                    </span>
                   ) : (

As per coding guidelines

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a845855 and d8b4f2f.

📒 Files selected for processing (6)
  • components/nft-picker/NftEditRanges.tsx (1 hunks)
  • components/nft-picker/NftPicker.tsx (1 hunks)
  • components/nft-picker/NftPicker.types.ts (1 hunks)
  • components/nft-picker/NftPicker.utils.ts (1 hunks)
  • components/nft-picker/useAlchemyClient.ts (1 hunks)
  • services/alchemy-api.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/nft-picker/NftPicker.types.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript for implementation code

Files:

  • components/nft-picker/NftPicker.utils.ts
  • components/nft-picker/useAlchemyClient.ts
  • components/nft-picker/NftEditRanges.tsx
  • services/alchemy-api.ts
  • components/nft-picker/NftPicker.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks

Files:

  • components/nft-picker/NftEditRanges.tsx
  • components/nft-picker/NftPicker.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons

Applied to files:

  • components/nft-picker/NftEditRanges.tsx
🧬 Code graph analysis (5)
components/nft-picker/NftPicker.utils.ts (1)
components/nft-picker/NftPicker.types.ts (4)
  • ParseError (81-87)
  • TokenRange (9-9)
  • TokenSelection (11-11)
  • TokenIdBigInt (7-7)
components/nft-picker/useAlchemyClient.ts (2)
services/alchemy-api.ts (5)
  • SearchContractsResult (570-570)
  • TokenMetadataParams (570-570)
  • searchNftCollections (295-326)
  • getContractOverview (328-375)
  • getTokensMetadata (416-455)
components/nft-picker/NftPicker.types.ts (4)
  • ContractOverview (26-29)
  • TokenMetadata (31-37)
  • SupportedChain (3-3)
  • Suggestion (13-24)
components/nft-picker/NftEditRanges.tsx (2)
components/nft-picker/NftPicker.types.ts (2)
  • TokenRange (9-9)
  • ParseError (81-87)
components/nft-picker/NftPicker.utils.ts (2)
  • formatCanonical (374-382)
  • formatBigIntWithSeparators (384-387)
services/alchemy-api.ts (3)
components/nft-picker/NftPicker.types.ts (4)
  • SupportedChain (3-3)
  • Suggestion (13-24)
  • ContractOverview (26-29)
  • TokenMetadata (31-37)
helpers/Helpers.ts (1)
  • isValidEthAddress (218-219)
services/6529api.ts (2)
  • fetchUrl (6-23)
  • postData (38-58)
components/nft-picker/NftPicker.tsx (9)
components/nft-picker/NftPicker.types.ts (8)
  • SupportedChain (3-3)
  • TokenSelection (11-11)
  • TokenRange (9-9)
  • Suggestion (13-24)
  • ContractOverview (26-29)
  • NftPickerProps (64-79)
  • ParseError (81-87)
  • NftPickerSelection (45-53)
components/nft-picker/NftPicker.utils.ts (10)
  • toCanonicalRanges (333-351)
  • formatCanonical (374-382)
  • parseTokenExpressionToRanges (171-245)
  • formatBigIntWithSeparators (384-387)
  • mergeCanonicalRanges (269-280)
  • removeTokenFromRanges (282-310)
  • fromCanonicalRanges (353-372)
  • isRangeTooLargeError (29-35)
  • tryToNumberArray (389-403)
  • MAX_SAFE (7-7)
components/nft-picker/useAlchemyClient.ts (3)
  • useContractOverviewQuery (156-193)
  • useCollectionSearch (99-154)
  • primeContractCache (256-269)
helpers/Helpers.ts (1)
  • isValidEthAddress (218-219)
components/nft-picker/NftSuggestList.tsx (1)
  • NftSuggestList (39-175)
components/nft-picker/NftContractHeader.tsx (1)
  • NftContractHeader (18-102)
components/nft-picker/AllTokensSelectedCard.tsx (1)
  • AllTokensSelectedCard (12-46)
components/nft-picker/NftEditRanges.tsx (1)
  • NftEditRanges (27-235)
components/nft-picker/NftTokenList.tsx (1)
  • NftTokenList (55-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (45)
components/nft-picker/NftEditRanges.tsx (7)

15-25: LGTM!

Props interface correctly uses readonly modifier for all properties, as per coding guidelines.


27-56: LGTM!

Component signature correctly destructures readonly props and memoizes expensive computations with appropriate dependencies.


57-69: LGTM!

Copy status effects are well-structured with proper cleanup and reset behavior on canonical changes.


71-108: LGTM!

Copy handler is robust with modern clipboard API primary path and comprehensive legacy fallback for older browsers. Proper error handling and cleanup throughout.


110-115: LGTM!

Keyboard shortcut for Cmd/Ctrl+Enter to apply changes provides good UX for power users.


117-133: LGTM!

Copy status UI logic and button visibility flags are clear and properly derived from state.


135-234: LGTM!

JSX structure is well-organized with proper accessibility attributes, conditional rendering, and comprehensive error display. TailwindCSS is used consistently.

components/nft-picker/useAlchemyClient.ts (8)

1-31: LGTM!

Imports and cache infrastructure are well-organized with reasonable TTL values for client-side caching.


64-66: LGTM!

Token cache key generation is correct with proper ":" delimiters and sorted token IDs. The stray "$" issue mentioned in past reviews has been resolved.


69-76: LGTM!

Debounce hook is a clean, standard implementation with proper cleanup.


78-97: LGTM!

Parameter types correctly use readonly modifier and are well-structured.


99-154: LGTM!

Collection search hook correctly implements debouncing, dual-layer caching (in-memory + react-query), and GC for expired entries.


156-193: LGTM!

Contract overview query correctly normalizes addresses and implements consistent caching pattern.


195-254: LGTM!

Token metadata query properly deduplicates IDs and uses keepPreviousData for smooth UX during refetches.


256-269: LGTM!

Cache priming utility enables optimistic UI updates by pre-populating contract cache from suggestions.

components/nft-picker/NftPicker.tsx (8)

1-48: LGTM!

Imports and constants are well-organized. EMPTY_SELECTION constant provides a clear empty state reference.


51-68: LGTM!

Helper functions are pure, well-scoped utilities that clarify component logic.


70-108: LGTM!

Component properly handles controlled/uncontrolled modes with appropriate state initialization and refs for tracking hydration.


109-228: LGTM!

Prop synchronization effects correctly handle both controlled and uncontrolled modes with appropriate guards and hydration tracking.


230-564: LGTM!

Event handlers properly manage state transitions and call emitChange when selection or contract changes occur.


583-661: LGTM!

The emitChange function correctly includes the allSelected discriminator in all payloads and handles both number and bigint output modes with appropriate safety checks.


663-673: LGTM!

This effect correctly handles the initial onChange emission for uncontrolled mode with defaultValue. The pendingPropEmitRef guard prevents duplicate emissions that were mentioned in past reviews.


678-824: LGTM!

JSX render is well-structured with proper accessibility attributes, conditional rendering based on state, and consistent TailwindCSS usage throughout.

components/nft-picker/NftPicker.utils.ts (12)

1-11: LGTM!

Constants are well-defined. MAX_ENUMERATION provides the necessary guard against explosive range expansion as noted in past reviews.


12-35: LGTM!

Custom error type for range-too-large cases provides structured error handling with proper type guards.


37-89: LGTM!

Tokenization logic correctly handles both comma and whitespace delimiters and intelligently merges segments around dash operators for range expressions.


91-99: LGTM!

Error creation helper provides consistent ParseError objects with position information.


101-139: LGTM!

Token value parsing correctly validates format, rejects signed values, and handles both hex and decimal representations.


141-169: LGTM!

Range canonicalization correctly sorts and merges adjacent/overlapping ranges using standard algorithm.


171-245: LGTM!

Expression parsing correctly guards against explosive range expansion with both per-range and total limits, addressing past review concerns.


247-267: LGTM!

Expression-to-bigints parser correctly transforms range-too-large errors into ParseError format for consistent error handling.


269-310: LGTM!

Range manipulation functions correctly handle merging and token removal with proper canonicalization.


312-372: LGTM!

Sorting and conversion functions correctly handle bigint comparisons and enforce enumeration limits during range expansion.


374-403: LGTM!

Formatting and conversion utilities provide clear output representations and safely handle bigint-to-number conversions with overflow detection.


405-442: LGTM!

Windowed range expansion efficiently supports virtualization by only expanding the visible subset of ranges, avoiding full enumeration for large selections.

services/alchemy-api.ts (10)

2-12: LGTM!

New imports are appropriate for the NFT API functionality and align with the existing codebase patterns.


15-127: LGTM!

Type definitions comprehensively model Alchemy API responses with proper handling of optional fields and response shape variations.


129-293: LGTM!

Helper functions robustly handle Alchemy API response variations and normalize data into consistent internal types.


295-326: LGTM!

Collection search function correctly validates input, handles pagination, filters spam, and supports request cancellation via AbortSignal.


328-375: LGTM!

Contract overview function properly validates addresses, normalizes data, and enriches suggestions with OpenSea metadata.


377-414: LGTM!

Token metadata helpers correctly parse token IDs as bigints and normalize response data with proper error handling.


416-455: LGTM!

Token metadata fetching correctly batches requests, validates inputs, and supports cancellation. Proper handling of batch responses and normalization.


457-504: LGTM!

Legacy fetch utilities provide proper abort handling and delay mechanisms for retry logic.


506-568: LGTM!

Legacy NFT fetching function correctly adds retry logic with exponential backoff and AbortSignal support for cancellation.


570-570: LGTM!

Type exports provide clean public API surface for consumers of the Alchemy service.

Comment on lines +330 to +331
// TODO: remove once callers migrate to sortAndDedupIds.
export const mergeAndSort = sortAndDedupIds;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

TODO comment for deprecated alias.

The mergeAndSort alias is marked for removal once callers migrate to sortAndDedupIds. Consider tracking this as a tech debt item.


🏁 Script executed:

#!/bin/bash
# Check if mergeAndSort is still used in the codebase
rg -n '\bmergeAndSort\b' --type ts --type tsx -g '!**/NftPicker.utils.ts'

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check if mergeAndSort is still used in the codebase, excluding NftPicker.utils.ts
rg -n '\bmergeAndSort\b' -g '*.ts' -g '*.tsx' -g '!components/nft-picker/NftPicker.utils.ts'

Length of output: 257


Replace mergeAndSort references in tests with sortAndDedupIds and remove deprecated alias
Update the import in __tests__/components/nft-picker/NftPicker.utils.test.ts (around lines 9 and 101) to use sortAndDedupIds, then delete the export const mergeAndSort = sortAndDedupIds; alias in components/nft-picker/NftPicker.utils.ts (line 331).

🤖 Prompt for AI Agents
In components/nft-picker/NftPicker.utils.ts around lines 330-331, remove the
deprecated alias export "export const mergeAndSort = sortAndDedupIds;"; then
update the test file __tests__/components/nft-picker/NftPicker.utils.test.ts
(around lines 9 and 101) to import and reference sortAndDedupIds instead of
mergeAndSort, run the tests to ensure no other callers use mergeAndSort, and
delete the alias line from the utils file.

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>
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: 5

♻️ Duplicate comments (1)
components/nft-picker/NftEditRanges.tsx (1)

6-8: Use FontAwesome for the “copied” state checkmark for consistency.

Import faCheck and render it instead of the raw ✓.

Apply:

-import { faCopy } from "@fortawesome/free-solid-svg-icons";
+import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";
-                  {copyStatus === "copied" ? (
-                    <span className="tw-font-semibold tw-text-emerald-300">✓ Copied!</span>
-                  ) : (
+                  {copyStatus === "copied" ? (
+                    <span className="tw-inline-flex tw-items-center tw-gap-2 tw-font-semibold tw-text-emerald-300">
+                      <FontAwesomeIcon icon={faCheck} />
+                      <span>Copied</span>
+                    </span>
+                  ) : (

Also applies to: 160-167

🧹 Nitpick comments (6)
components/gas-royalties/Royalties.tsx (1)

76-76: Optional: Remove redundant type annotation.

The explicit res: Royalty[] annotation is now redundant since TypeScript infers the type from the generic parameter fetchUrl<Royalty[]>. The code is correct as-is, but you could simplify:

-    fetchUrl<Royalty[]>(getUrlWithParams()).then((res: Royalty[]) => {
+    fetchUrl<Royalty[]>(getUrlWithParams()).then((res) => {
components/gas-royalties/GasRoyalties.tsx (1)

90-91: Optional: Remove redundant type annotation.

Similar to the Royalties component, the explicit res: ApiArtistNameItem[] annotation is now redundant since TypeScript infers it from fetchUrl<ApiArtistNameItem[]>.

-    fetchUrl<ApiArtistNameItem[]>(`${publicEnv.API_ENDPOINT}/api/${path}/artists_names`).then(
-      (res: ApiArtistNameItem[]) => {
+    fetchUrl<ApiArtistNameItem[]>(`${publicEnv.API_ENDPOINT}/api/${path}/artists_names`).then(
+      (res) => {
__tests__/AGENTS.md (1)

61-134: Optional: Fix markdown heading structure.

The linter flags several structural issues that would improve document navigation and tooling support. Consider promoting bold-text subsections to proper headings:

-### Complexity
+## Complexity
 
 * Functions ≤ 15 cognitive complexity.
 * Extract deep ternaries (>3 levels).
 * Break down complex logic.
 
-**Iteration**
+### Iteration

Apply similar changes to Array Access, Strings, Globals, Imports, Accessibility, Modern DOM, Error Handling, and Clarity sections (lines 84, 92, 99, 106, 112, 118, 124, 130) for consistent heading hierarchy.

components/nft-picker/NftTokenList.tsx (1)

35-36: Remove inline comment to satisfy TS/TSX guidelines.

The repo’s TS/TSX guidelines forbid inline comments; this note violates that requirement. Please drop the comment or bake the intent into the constant name instead.

-// Cap prevents virtualization container heights from becoming extreme.

As per coding guidelines

services/6529api.ts (2)

26-37: Replace recursive pagination with an iterative loop to avoid deep stacks.

Large page counts can overflow the stack; use a loop.

Apply:

-export async function fetchAllPages(url: string, data?: any[]): Promise<any[]> {
-  let allData: any[] = [];
-  if (data) {
-    allData = data;
-  }
-  const response = await fetchUrl<DBResponse>(url);
-  allData = [...allData].concat(response.data);
-  if (response.next) {
-    return fetchAllPages(response.next, allData);
-  }
-  return allData;
-}
+export async function fetchAllPages(url: string, data: any[] = []): Promise<any[]> {
+  let allData = data;
+  let next: string | null = url;
+  while (next) {
+    const response = await fetchUrl<DBResponse>(next);
+    allData = allData.concat(response.data ?? []);
+    next = response.next ?? null;
+  }
+  return allData;
+}

23-24: Harden JSON parsing for empty/non‑JSON responses.

Avoid unhandled exceptions on 204/empty or HTML error bodies.

Apply:

-  return (await res.json()) as T;
+  const text = await res.text();
+  return (text ? (JSON.parse(text) as T) : (undefined as unknown as T));

And similarly parse in postData/postFormData before returning response.

Also applies to: 55-60, 73-79

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8b4f2f and a060c8f.

📒 Files selected for processing (20)
  • __tests__/AGENTS.md (1 hunks)
  • components/gas-royalties/Gas.tsx (1 hunks)
  • components/gas-royalties/GasRoyalties.tsx (1 hunks)
  • components/gas-royalties/Royalties.tsx (1 hunks)
  • components/nextGen/collections/collectionParts/NextGenCollectionHeader.tsx (1 hunks)
  • components/nextGen/collections/collectionParts/mint/NextGenMint.tsx (1 hunks)
  • components/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsx (1 hunks)
  • components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsx (1 hunks)
  • components/nft-picker/AllTokensSelectedCard.tsx (1 hunks)
  • components/nft-picker/NftEditRanges.tsx (1 hunks)
  • components/nft-picker/NftPicker.tsx (1 hunks)
  • components/nft-picker/NftPicker.types.ts (1 hunks)
  • components/nft-picker/NftPicker.utils.ts (1 hunks)
  • components/nft-picker/NftSuggestList.tsx (1 hunks)
  • components/nft-picker/NftTokenList.tsx (1 hunks)
  • components/nft-picker/useAlchemyClient.ts (1 hunks)
  • contexts/SeizeSettingsContext.tsx (1 hunks)
  • services/6529api.ts (1 hunks)
  • services/alchemy-api.ts (4 hunks)
  • spec.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/nft-picker/NftSuggestList.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript for implementation code

Files:

  • components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsx
  • components/nextGen/collections/collectionParts/NextGenCollectionHeader.tsx
  • components/gas-royalties/Royalties.tsx
  • services/6529api.ts
  • components/nextGen/collections/collectionParts/mint/NextGenMint.tsx
  • components/nft-picker/NftPicker.types.ts
  • components/gas-royalties/GasRoyalties.tsx
  • components/gas-royalties/Gas.tsx
  • components/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsx
  • components/nft-picker/NftPicker.utils.ts
  • components/nft-picker/NftEditRanges.tsx
  • components/nft-picker/NftPicker.tsx
  • contexts/SeizeSettingsContext.tsx
  • components/nft-picker/useAlchemyClient.ts
  • components/nft-picker/NftTokenList.tsx
  • services/alchemy-api.ts
  • components/nft-picker/AllTokensSelectedCard.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks

Files:

  • components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsx
  • components/nextGen/collections/collectionParts/NextGenCollectionHeader.tsx
  • components/gas-royalties/Royalties.tsx
  • components/nextGen/collections/collectionParts/mint/NextGenMint.tsx
  • components/gas-royalties/GasRoyalties.tsx
  • components/gas-royalties/Gas.tsx
  • components/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsx
  • components/nft-picker/NftEditRanges.tsx
  • components/nft-picker/NftPicker.tsx
  • contexts/SeizeSettingsContext.tsx
  • components/nft-picker/NftTokenList.tsx
  • components/nft-picker/AllTokensSelectedCard.tsx
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/AGENTS.md
🧠 Learnings (3)
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.{ts,tsx} : Always add readonly before props

Applied to files:

  • components/nft-picker/NftPicker.types.ts
  • components/nft-picker/AllTokensSelectedCard.tsx
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons

Applied to files:

  • components/nft-picker/NftEditRanges.tsx
  • components/nft-picker/AllTokensSelectedCard.tsx
📚 Learning: 2025-09-28T12:33:30.941Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-09-28T12:33:30.941Z
Learning: Run `npm run test:cov:changed` for changed-file tests and coverage; use `npm run test` for full suite; ensure `npm run lint` and `npm run type-check` pass

Applied to files:

  • __tests__/AGENTS.md
🧬 Code graph analysis (15)
components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsx (1)
services/6529api.ts (1)
  • fetchUrl (6-24)
components/nextGen/collections/collectionParts/NextGenCollectionHeader.tsx (1)
services/6529api.ts (1)
  • fetchUrl (6-24)
components/gas-royalties/Royalties.tsx (2)
services/6529api.ts (1)
  • fetchUrl (6-24)
entities/IRoyalty.ts (1)
  • Royalty (1-11)
services/6529api.ts (3)
entities/IDBResponse.ts (1)
  • DBResponse (1-6)
services/auth/auth.utils.ts (1)
  • getStagingAuth (77-79)
constants.ts (1)
  • API_AUTH_COOKIE (39-39)
components/nextGen/collections/collectionParts/mint/NextGenMint.tsx (1)
services/6529api.ts (1)
  • fetchUrl (6-24)
components/gas-royalties/GasRoyalties.tsx (2)
services/6529api.ts (1)
  • fetchUrl (6-24)
generated/models/ApiArtistNameItem.ts (1)
  • ApiArtistNameItem (15-41)
components/gas-royalties/Gas.tsx (2)
services/6529api.ts (1)
  • fetchUrl (6-24)
entities/IGas.ts (1)
  • Gas (1-8)
components/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsx (1)
services/6529api.ts (1)
  • fetchUrl (6-24)
components/nft-picker/NftPicker.utils.ts (1)
components/nft-picker/NftPicker.types.ts (3)
  • ParseError (77-83)
  • TokenRange (7-7)
  • TokenSelection (9-9)
components/nft-picker/NftEditRanges.tsx (2)
components/nft-picker/NftPicker.types.ts (2)
  • TokenRange (7-7)
  • ParseError (77-83)
components/nft-picker/NftPicker.utils.ts (2)
  • formatCanonical (392-400)
  • formatBigIntWithSeparators (402-421)
components/nft-picker/NftPicker.tsx (9)
components/nft-picker/NftPicker.types.ts (8)
  • SupportedChain (3-3)
  • TokenSelection (9-9)
  • TokenRange (7-7)
  • Suggestion (11-22)
  • ContractOverview (24-27)
  • NftPickerProps (60-75)
  • ParseError (77-83)
  • NftPickerSelection (43-51)
components/nft-picker/NftPicker.utils.ts (10)
  • toCanonicalRanges (351-369)
  • formatCanonical (392-400)
  • parseTokenExpressionToRanges (187-261)
  • formatBigIntWithSeparators (402-421)
  • mergeCanonicalRanges (284-295)
  • removeTokenFromRanges (297-327)
  • fromCanonicalRanges (371-390)
  • isRangeTooLargeError (43-49)
  • tryToNumberArray (423-437)
  • MAX_SAFE (7-7)
components/nft-picker/useAlchemyClient.ts (3)
  • useContractOverviewQuery (164-201)
  • useCollectionSearch (107-162)
  • primeContractCache (264-277)
helpers/Helpers.ts (1)
  • isValidEthAddress (218-219)
components/nft-picker/NftSuggestList.tsx (1)
  • NftSuggestList (39-191)
components/nft-picker/NftContractHeader.tsx (1)
  • NftContractHeader (18-102)
components/nft-picker/AllTokensSelectedCard.tsx (1)
  • AllTokensSelectedCard (12-44)
components/nft-picker/NftEditRanges.tsx (1)
  • NftEditRanges (44-242)
components/nft-picker/NftTokenList.tsx (1)
  • NftTokenList (55-216)
contexts/SeizeSettingsContext.tsx (2)
services/6529api.ts (1)
  • fetchUrl (6-24)
generated/models/ApiSeizeSettings.ts (1)
  • ApiSeizeSettings (15-48)
components/nft-picker/useAlchemyClient.ts (2)
services/alchemy-api.ts (5)
  • SearchContractsResult (569-569)
  • TokenMetadataParams (569-569)
  • searchNftCollections (294-325)
  • getContractOverview (327-374)
  • getTokensMetadata (415-454)
components/nft-picker/NftPicker.types.ts (4)
  • ContractOverview (24-27)
  • TokenMetadata (29-35)
  • SupportedChain (3-3)
  • Suggestion (11-22)
components/nft-picker/NftTokenList.tsx (4)
components/nft-picker/NftPicker.types.ts (3)
  • SupportedChain (3-3)
  • TokenRange (7-7)
  • TokenMetadata (29-35)
contexts/ScrollPositionContext.tsx (1)
  • useScrollPositionContext (36-40)
components/nft-picker/NftPicker.utils.ts (2)
  • expandRangesWindow (439-479)
  • formatCanonical (392-400)
components/nft-picker/useAlchemyClient.ts (1)
  • useTokenMetadataQuery (203-262)
services/alchemy-api.ts (3)
components/nft-picker/NftPicker.types.ts (4)
  • SupportedChain (3-3)
  • Suggestion (11-22)
  • ContractOverview (24-27)
  • TokenMetadata (29-35)
helpers/Helpers.ts (1)
  • isValidEthAddress (218-219)
services/6529api.ts (2)
  • fetchUrl (6-24)
  • postData (39-60)
🪛 markdownlint-cli2 (0.18.1)
__tests__/AGENTS.md

61-61: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


69-69: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


84-84: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


92-92: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


99-99: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


106-106: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


112-112: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


118-118: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


124-124: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


130-130: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (64)
components/nextGen/collections/collectionParts/NextGenCollectionHeader.tsx (1)

94-94: LGTM - Type-safe fetch call.

Adding the explicit CollectionWithMerkle generic parameter improves type safety and aligns with the updated fetchUrl<T> signature.

contexts/SeizeSettingsContext.tsx (1)

36-36: LGTM - Type-safe fetch call.

Adding the explicit ApiSeizeSettings generic parameter improves type safety and aligns with the updated fetchUrl<T> signature.

components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsx (1)

150-150: LGTM - Type-safe fetch call.

Adding the explicit ProofResponse[] generic parameter improves type safety and correctly reflects the array response from the proofs endpoint.

components/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsx (1)

141-141: LGTM - Type-safe fetch call.

Adding the explicit ProofResponse generic parameter improves type safety and correctly reflects the single-object response from the burn proofs endpoint.

components/gas-royalties/Gas.tsx (1)

69-69: LGTM - Type-safe fetch call.

Adding the explicit Gas[] generic parameter improves type safety and aligns with the updated fetchUrl<T> signature.

components/nft-picker/AllTokensSelectedCard.tsx (1)

1-44: LGTM! Well-structured accessible component.

The component follows all coding guidelines and best practices:

  • Uses FontAwesome for icons as required
  • TailwindCSS for styling
  • Props properly marked as readonly
  • Semantic HTML (<output> element) for status display
  • Comprehensive accessibility with aria-labels
  • Clean functional component with proper ref forwarding
components/nft-picker/NftPicker.types.ts (1)

1-106: LGTM! Comprehensive and well-structured type definitions.

The type definitions follow TypeScript best practices:

  • Proper use of branded types (0x${string})
  • Discriminated unions for NftPickerSelection based on outputMode
  • Readonly modifiers on props as required
  • Clear separation of concerns (base types, selections, metadata, props)
  • Future-proof design (e.g., SupportedChain as union type ready for extension)
components/nextGen/collections/collectionParts/mint/NextGenMint.tsx (1)

230-235: Typed fetchUrl usage looks good.

Thanks for threading the CollectionWithMerkle generic through fetchUrl; this tightens the typing and keeps the then-handler honest without changing runtime behavior.

components/nft-picker/NftEditRanges.tsx (1)

89-127: Copy fallback is solid; minor polish only.

Implementation handles Clipboard API and fallback well; no changes required.

components/nft-picker/useAlchemyClient.ts (3)

64-75: Stable token cache key and dedupe look good.

Sorted, deduped keys prevent cache fragmentation. LGTM.


264-277: Nice: primeContractCache for optimistic UX.

Preloading overview from suggestions is a good touch.


176-201: Approve React Query TTL configuration. staleTime and gcTime correctly use SUGGESTION_TTL, CONTRACT_TTL, and TOKEN_TTL respectively.

services/6529api.ts (1)

6-9: fetchUrl/postData calls don’t pass custom headers—change is safe
All existing call sites invoke fetchUrl, postData, or postFormData with only URL (and body) arguments and never supply additional headers, so the updated implementations won’t affect their behavior.

components/nft-picker/NftPicker.tsx (21)

1-1: "use client" directive is correct for Next.js App Router.

This component uses client-side hooks and interactivity, so the directive is appropriate.


43-47: Constants are well-defined.

The default values and BigInt constants are appropriately scoped and named.


51-56: Helper function logic is correct.

The function correctly handles empty selections and delegates to toCanonicalRanges.


70-92: Component signature and chain resolution look good.

The props are correctly destructured with defaults, and the chain value is properly memoized with fallback logic.


110-112: Prop sync effect is correct.

Synchronizing the hideSpam prop to local state allows for independent toggling while respecting parent updates.


121-132: Contract hydration effect handles edge cases well.

The effect correctly updates selectedContract only when needed and notifies the parent via onContractChange.


175-199: Value prop sync handles controlled mode correctly.

The effect properly syncs the controlled value prop to internal state, including allSelected and previousRangesRef for restore logic.


201-225: DefaultValue hydration is correctly guarded.

The effect only runs once for uncontrolled mode and properly sets pendingPropEmitRef to delay the initial emit until the contract is loaded.


232-247: Suggestion selection logic is comprehensive.

The handler correctly updates state, primes the cache, clears previous selections, and emits the change with an empty selection.


283-347: Token preview computation handles all edge cases.

The memo correctly validates ranges-disabled mode, parses input, catches errors, and returns structured results.


349-353: Add-tokens guard is correct.

The condition properly checks allSelected, non-empty input, no errors, and positive count.


386-420: Helper state logic provides clear user feedback.

The memo correctly handles all states: all-selected, empty input, errors, and success with appropriate tone and messaging.


445-458: addTokenRanges correctly merges and emits.

The function properly merges ranges, clears allSelected, resets errors, and emits the change.


510-522: Select-all flow preserves restore state.

The handler correctly saves current ranges to previousRangesRef for later restoration and focuses the deselect button.


524-542: Deselect-all restores previous ranges when available.

The handler correctly checks for previousRangesRef and restores it, or emits current ranges if none were saved.


585-615: Range-to-selection conversion handles errors gracefully.

The function correctly catches RangeTooLargeError, sets parseErrors for UI feedback, logs a warning, and returns null to prevent emission.


617-671: emitChange logic is comprehensive and correct.

The function properly handles all cases: early return on null contract or selection errors, empty-selection payload, number-mode with unsafe-count check, and bigint-mode with conversion to strings. The allSelected discriminator is correctly included in all payloads.


673-683: Pending-emit effect is correctly guarded.

The effect only emits when pendingPropEmitRef is set and the contract matches, preventing duplicate emissions while handling the uncontrolled defaultValue initialization case.


688-722: Collection input section has proper accessibility.

The input correctly uses ARIA attributes (role="combobox", aria-expanded, aria-controls, aria-activedescendant) for screen-reader support and keyboard navigation.


761-776: Select All button properly integrates FontAwesome icon.

The button correctly uses the FontAwesomeIcon component per coding guidelines and includes proper accessibility attributes.


827-831: Unsafe count warning provides actionable guidance.

The warning correctly appears only when outputMode === "number" and unsafeCount > 0, informing users to switch modes or remove large IDs.

components/nft-picker/NftPicker.utils.ts (15)

7-10: Constants are well-defined with safe limits.

MAX_SAFE and MAX_ENUMERATION (10,000) provide appropriate bounds to prevent memory exhaustion and UI freezes. The past review concern about unbounded range expansion has been addressed.


12-49: Error handling utilities are well-structured.

The custom error types and factories provide clear, structured error information with proper type guards for downstream handling.


61-108: Tokenization logic handles hyphen ranges correctly.

The function properly distinguishes between standalone hyphens (range operators) and hyphen-prefixed tokens, merging them correctly into range expressions.


120-147: Token value parsing is robust.

The function correctly validates empty tokens, rejects signed values, accepts both hex and decimal formats, and returns BigInt.


149-185: Range canonicalization merges correctly.

The function properly sorts ranges and merges adjacent or overlapping ones, producing a minimal canonical set.


187-261: Parsing enforces limits at both per-range and total levels.

The function correctly validates individual range sizes and the total token count against MAX_ENUMERATION, preventing memory exhaustion as flagged in past reviews.


263-282: Bigint parsing wraps range parsing with error conversion.

The function correctly delegates to parseTokenExpressionToRanges and fromCanonicalRanges, catching and re-throwing RangeTooLargeError as a ParseError for UI consumption.


297-327: Token removal handles all range split cases.

The function correctly handles tokens outside ranges, single-token ranges, boundary tokens, and mid-range tokens (splitting into two ranges).


336-346: Deduplication logic is correct.

The function sorts by bigint comparison and removes consecutive duplicates.


351-369: Range conversion from IDs is efficient.

The function correctly builds ranges by detecting consecutive IDs and grouping them.


371-390: Range expansion enforces limits correctly.

The function checks both per-range and cumulative sizes against MAX_ENUMERATION, throwing RangeTooLargeError when exceeded.


392-400: Canonical formatting is concise and readable.

The function correctly formats single tokens and ranges, joining with commas.


402-421: Thousand-separator formatting is correct.

The function properly handles negative numbers and inserts commas every three digits from the right.


423-437: Number array conversion tracks unsafe IDs.

The function correctly converts safe IDs to numbers and counts IDs exceeding MAX_SAFE_INTEGER for the parent to handle.


439-479: Window expansion supports virtualization efficiently.

The function correctly extracts a slice of token IDs from ranges for virtual scrolling, handling start index and count parameters.

services/alchemy-api.ts (15)

1-12: Imports are well-organized.

The imports correctly bring in environment config, helpers, types, viem utilities, and the internal fetchUrl/postData functions.


14-119: Type definitions comprehensively model Alchemy API responses.

The types correctly handle optional fields, unions, and nested structures for contract metadata, OpenSea metadata, and token metadata.


128-138: Network and query helpers are straightforward.

The functions correctly map chains to network strings and validate non-empty queries.


157-176: Floor price parsing handles multiple formats.

The function correctly handles direct numbers, nested objects with eth field (as number or string), and returns null for invalid formats.


178-226: Image pickers implement fallback chains.

Both pickImage and pickThumbnail correctly try multiple sources in priority order, returning the first available URL or null.


228-241: Address normalization validates and checksums.

The function validates with isValidEthAddress, checksums with viem.getAddress, and logs a warning on checksum failure while still returning the address. This is a reasonable fallback.


243-292: Contract extraction unifies multiple metadata sources.

The function correctly aggregates data from contractMetadata, openSeaMetadata, and base contract fields, normalizes addresses, and builds a Suggestion object. Returning null when no valid address is found is appropriate.


294-325: Collection search correctly filters spam.

The function builds the Alchemy URL, fetches results, extracts contracts, and filters spam items when hideSpam is true, returning both visible items and hidden count.


327-374: Contract overview fetches and maps metadata correctly.

The function validates the address, builds the URL, fetches metadata, and constructs a ContractOverview with description and banner from OpenSea metadata.


376-385: Token ID parsing handles hex and decimal formats.

The function correctly trims whitespace and parses both hex (0x-prefixed) and decimal token IDs using BigInt.


387-413: Token metadata normalization handles missing data gracefully.

The function parses the token ID, picks an image from multiple sources, and returns null with a warning if parsing fails.


415-454: Token metadata batching is efficient.

The function correctly batches requests by MAX_BATCH_SIZE (100), uses postData for each batch, and aggregates normalized results.


458-498: Abort utilities are correctly implemented.

createAbortError creates proper error objects, and delayWithAbort implements abortable delays with cleanup listeners. These are essential for cancellable async operations.


505-567: NFT fetch function correctly adds retry and abort support.

The modified function properly handles retries with exponential backoff (line 533: 250 * (retries + 1) ms), pagination, and abort signals. The recursive calls correctly propagate the signal parameter.


530-542: Verify abort signal handling in getNftsForContractAndOwner retries.

  • Internals: delayWithAbort and fetchLegacyUrl both respect and propagate the AbortSignal; the retry call forwards the same signal — aborts will reject with an AbortError and will not be swallowed by the retry logic.
  • Outstanding: I did not find any external callers in the repo to confirm consumer handling — ensure all callsites catch AbortError (check error.name === 'AbortError' or instanceof DOMException) and treat it as a cancellation (do not record/display it as an unexpected error).

File: services/alchemy-api.ts (around lines 530–542).

Comment thread components/nft-picker/NftEditRanges.tsx Outdated
Comment on lines +113 to +116
textarea.setSelectionRange(0, textarea.value.length);
} catch (error) {
/* no-op, best effort for iOS/WebKit */
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove inline comment (TSX rule: no code comments).

Delete the in-line comment inside the try/catch.

Apply:

-      try {
-        textarea.setSelectionRange(0, textarea.value.length);
-      } catch (error) {
-        /* no-op, best effort for iOS/WebKit */
-      }
+      try {
+        textarea.setSelectionRange(0, textarea.value.length);
+      } catch (error) {
+      }
📝 Committable suggestion

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

Suggested change
textarea.setSelectionRange(0, textarea.value.length);
} catch (error) {
/* no-op, best effort for iOS/WebKit */
}
try {
textarea.setSelectionRange(0, textarea.value.length);
} catch (error) {
}
🤖 Prompt for AI Agents
In components/nft-picker/NftEditRanges.tsx around lines 113 to 116, remove the
inline comment inside the catch block (the "/* no-op, best effort for iOS/WebKit
*/") so the catch block contains no comment; leave the empty catch body as-is
(or a minimal no-op expression if your lint rules require one) to satisfy the
TSX no-code-comments rule.

Comment on lines +348 to +349
/** @deprecated Use sortAndDedupIds instead. */
export const mergeAndSort = sortAndDedupIds;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Deprecated alias should be removed after migration.

The mergeAndSort alias is marked deprecated. As per the past review comment, update test imports to use sortAndDedupIds and remove this alias.

Check if mergeAndSort is still referenced outside tests:


🏁 Script executed:

#!/bin/bash
rg -n '\bmergeAndSort\b' -g '*.ts' -g '*.tsx' -g '!components/nft-picker/NftPicker.utils.ts' -g '!**/__tests__/**'

Length of output: 257


Remove deprecated mergeAndSort alias and update test references

  • In tests/components/nft-picker/NftPicker.utils.test.ts (lines 9 and 101), replace all mergeAndSort imports and calls with sortAndDedupIds.
  • In components/nft-picker/NftPicker.utils.ts (lines 348–349), delete the now-unused export const mergeAndSort = sortAndDedupIds; alias.
🤖 Prompt for AI Agents
In components/nft-picker/NftPicker.utils.ts around lines 348 to 349, remove the
deprecated alias export "export const mergeAndSort = sortAndDedupIds;" so the
alias is no longer present; then update
__tests__/components/nft-picker/NftPicker.utils.test.ts (around lines 9 and 101)
to replace any imports and references of mergeAndSort with sortAndDedupIds
(update import names and call sites) and run tests to ensure no remaining
references to mergeAndSort remain.

Comment thread services/6529api.ts
Comment on lines +10 to +15
const baseHeaders = new Headers(init?.headers);
const apiAuth = getStagingAuth();
if (apiAuth) {
headers = { "x-6529-auth": apiAuth };
baseHeaders.set("x-6529-auth", apiAuth);
}
const headers = Object.fromEntries(baseHeaders.entries());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not attach x-6529-auth to external URLs (leaks header to Alchemy; likely CORS failure).

Gate auth header injection to relative/internal URLs only.

Apply:

+const isRelativeUrl = (value: string): boolean =>
+  !/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value) && !value.startsWith("//");
+
 export async function fetchUrl<T = DBResponse>(
   url: string,
   init?: RequestInit
 ): Promise<T> {
   const baseHeaders = new Headers(init?.headers);
-  const apiAuth = getStagingAuth();
+  const apiAuth = isRelativeUrl(url) ? getStagingAuth() : null;
   if (apiAuth) {
     baseHeaders.set("x-6529-auth", apiAuth);
   }

Also mirror this in postData and postFormData (see below).

📝 Committable suggestion

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

Suggested change
const baseHeaders = new Headers(init?.headers);
const apiAuth = getStagingAuth();
if (apiAuth) {
headers = { "x-6529-auth": apiAuth };
baseHeaders.set("x-6529-auth", apiAuth);
}
const headers = Object.fromEntries(baseHeaders.entries());
const isRelativeUrl = (value: string): boolean =>
!/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value) && !value.startsWith("//");
export async function fetchUrl<T = DBResponse>(
url: string,
init?: RequestInit
): Promise<T> {
const baseHeaders = new Headers(init?.headers);
const apiAuth = isRelativeUrl(url) ? getStagingAuth() : null;
if (apiAuth) {
baseHeaders.set("x-6529-auth", apiAuth);
}
const headers = Object.fromEntries(baseHeaders.entries());
// ...rest of implementation
}

Comment thread services/6529api.ts
Comment on lines +39 to 49
export async function postData(url: string, body: any, init?: RequestInit) {
const baseHeaders = new Headers(init?.headers);
if (!baseHeaders.has("Content-Type")) {
baseHeaders.set("Content-Type", "application/json");
}
const apiAuth = getStagingAuth();
if (apiAuth) {
headers = { "x-6529-auth": apiAuth, "Content-Type": "application/json" };
baseHeaders.set("x-6529-auth", apiAuth);
}
const headers = Object.fromEntries(baseHeaders.entries());
const res = await fetch(url, {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Align 401 handling across POST helpers; also gate auth for POSTs.

postData/postFormData should clear the cookie on 401 (like fetchUrl) and avoid sending auth header to externals.

Apply:

 export async function postData(url: string, body: any, init?: RequestInit) {
   const baseHeaders = new Headers(init?.headers);
   if (!baseHeaders.has("Content-Type")) {
     baseHeaders.set("Content-Type", "application/json");
   }
-  const apiAuth = getStagingAuth();
+  const apiAuth = isRelativeUrl(url) ? getStagingAuth() : null;
   if (apiAuth) {
     baseHeaders.set("x-6529-auth", apiAuth);
   }
   const headers = Object.fromEntries(baseHeaders.entries());
   const res = await fetch(url, {
     ...init,
     method: "POST",
     body: JSON.stringify(body),
     headers,
   });
+  if (res.status === 401) {
+    Cookies.remove(API_AUTH_COOKIE);
+  }
 export async function postFormData(url: string, formData: FormData) {
-  let headers: any = {};
-  const apiAuth = getStagingAuth();
+  let headers: any = {};
+  const apiAuth = isRelativeUrl(url) ? getStagingAuth() : null;
   if (apiAuth) {
     headers = { "x-6529-auth": apiAuth };
   }
   const res = await fetch(url, {
     method: "POST",
     body: formData,
     headers: headers,
   });
+  if (res.status === 401) {
+    Cookies.remove(API_AUTH_COOKIE);
+  }

Also applies to: 50-55, 62-67, 68-77

🤖 Prompt for AI Agents
In services/6529api.ts around lines 39-49 (also apply the same changes to lines
50-55, 62-67, 68-77): postData/postFormData currently always attaches
x-6529-auth and does not clear auth cookie on 401; update them to only set the
x-6529-auth header when the target URL is internal (e.g., same origin or matches
our API base) and after receiving the fetch response, if res.status === 401 call
the same cookie-clear routine used by fetchUrl (clearCookie or equivalent)
before returning/throwing; ensure headers construction gates auth header by
checking the URL, and add the 401 handling after the fetch so behavior matches
fetchUrl.

Comment thread spec.md
Comment on lines +474 to +476
<pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded">
{JSON.stringify(selection, null, 2)}
</pre>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the demo JSON.stringify example to avoid runtime crash.

JSON.stringify throws TypeError: Do not know how to serialize a BigInt when run on the emitted selection because tokenIdsRaw is a bigint[]. The demo snippet will crash as soon as the picker emits a value, and anyone copying the spec example will hit the same failure. Please convert bigint fields before stringifying (e.g., via a replacer that calls .toString()), or render the value another way.

-      <pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded">
-        {JSON.stringify(selection, null, 2)}
-      </pre>
+      <pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded">
+        {JSON.stringify(
+          selection,
+          (_, value) => (typeof value === "bigint" ? value.toString() : value),
+          2
+        )}
+      </pre>
📝 Committable suggestion

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

Suggested change
<pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded">
{JSON.stringify(selection, null, 2)}
</pre>
<pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded">
{JSON.stringify(
selection,
(_, value) => (typeof value === "bigint" ? value.toString() : value),
2
)}
</pre>
🤖 Prompt for AI Agents
In spec.md around lines 474 to 476, the demo uses JSON.stringify(selection,
null, 2) which throws when selection contains BigInt (tokenIdsRaw: bigint[]);
fix by converting BigInt values before stringifying — either provide a replacer
to JSON.stringify that converts BigInt to string, or normalize the selection
object (map tokenIdsRaw elements to strings or add a safe-serializable copy) and
then call JSON.stringify on that sanitized object so the demo no longer crashes
at runtime.

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>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Oct 6, 2025

@simo6529 simo6529 changed the base branch from main to xTDH October 6, 2025 09:42
@simo6529 simo6529 merged commit 3395772 into xTDH Oct 6, 2025
6 of 7 checks passed
@simo6529 simo6529 deleted the feature/nft-picker branch October 6, 2025 13:00
This was referenced Oct 27, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Jan 9, 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.

1 participant